Skip to content

Commit

Permalink
to/from-json/base count-while/until safe-eval deref
Browse files Browse the repository at this point in the history
  • Loading branch information
phunanon committed Oct 5, 2023
1 parent 322518f commit b250870
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 12 deletions.
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Inspired by [Chika](https://github.com/phunanon/Chika),
[Epizeuxis](https://github.com/phunanon/Epizeuxis), and
[Kuan](https://github.com/phunanon/Kuan).
Pronounced /ɪnˈsɪtjuːɪks/ in the International Phonetic Alphabet.

- [**Main NPM package**](https://www.npmjs.com/package/insitux) and its [Github repository](https://github.com/phunanon/Insitux)
- [**Roblox-TS NPM package**](https://www.npmjs.com/package/@rbxts/insitux) and its [Github repository](https://github.com/insitux/rbxts-Insitux)
Expand All @@ -44,7 +45,7 @@ $ ix #open a REPL session (exit with Ctrl+D or Ctrl+C)
$ ix . #execute entry.ix in the working directory
$ ix file.ix #execute file.ix in the working directory
$ ix -e "PI" #execute provided string
$ ix -b #disable REPL budgets (loops, recur, etc)
$ ix -nb #disable REPL budgets (loops, recur, etc)
$ ix -nc #turn off "colour mode" for REPL errors, etc
$ ix -unv #generate unvisited.txt of unvisited code line:column
$ ix [args] -r #… then open a REPL session
Expand Down Expand Up @@ -384,6 +385,16 @@ etc
;Same as str, but ignores null arguments
(strn "Hello" null ", world!") → "Hello, world!"

;Converts number into string of chosen base from 2 to 36
(to-base 2 10) → "1010"
(to-base 16 10) → "a"
(to-base 36 100) → "2s"

;Converts string of chosen base from 2 to 36 into number
(from-base 2 "1010") → 10
(from-base 16 "a") → 10
(from-base 36 "2s") → 100

;Returns the average of numbers in a provided vector
(average [1 2 -3]) → 0
(average [1 2 4]) → 2.33333
Expand Down Expand Up @@ -629,6 +640,8 @@ etc
(count (= 1) [1 1 2 3 3]) → 2
(count (comp 1 odd?) {:a 1 :b 2 :c 3})
2
(count-until odd? [0 2 4 5 6 7]) → 3
(count-while odd? [1 3 2 4 5 7]) → 2

;Returns item or character from vector, dictionary, or string which returns the
; highest or lowest number by predicate
Expand Down Expand Up @@ -939,6 +952,20 @@ etc
(... + 0 1 2 3 [4 5 6])
21

;Returns a string JSON representation of an Insitux value
;Note: if you just need to serialise and deserialise Insitux values, use
; str and eval (safely), as it preserves complex data types
(to-json {:a 1 :b 2}) → "{\":a\":1,\":b\":2}"
(to-json [1 2 3]) → "[1,2,3]"
(to-json "hello") → "\"hello\""

;Returns an Insitux value from a JSON string
;Note: JSON is unable to provide lossless serialisation of Insitux values
(from-json "{\":a\":1,\":b\":2}") → {":a" 1 ":b" 2}
(from-json "[1,2,3]") → [1 2 3]
(from-json "\"hello\"") → "hello"
(from-json ":bad JSON:") → null

;Evaluates all but its final argument and returns the penultimate argument's
; value if no runtime errors occurred, else populates the let `errors` and
; returns the evaluation of the final argument
Expand All @@ -964,6 +991,17 @@ etc
;Evaluates a string as code, returning any values returned or null
(eval "(+ 2 2)") → 4

;Evaluates a string as an Insitux value, without executing any code
(safe-eval "{:a 'Hello' :b 123}") → {:a "Hello", :b 123}
(safe-eval "(+ 2 2)") → null

;Efficiently dereferences a string into a var/let/function value
;Note: this can throw reference errors
(let my-let 123)
(deref "my-let")
123
(deref "+") → +

;Returns arity, type, and other information about specified function
(about +)
→ {:name "+", :external? false, :has-effects? false, :minimum-arity 2,
Expand Down Expand Up @@ -1258,6 +1296,9 @@ vector item or string character is "destructured" into.

## Various examples

Check out our [Rosetta Code entries](https://rosettacode.org/wiki/Insitux) for
70+ other examples.

```clj
; Test if 2D point is inside 2D area
(function inside-2d? X Y areaX areaY areaW areaH
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "insitux",
"version": "23.10.2",
"version": "23.10.5",
"description": "Extensible scripting language written in portable TypeScript.",
"main": "dist/invoker.js",
"types": "dist/invoker.d.ts",
Expand Down Expand Up @@ -36,5 +36,11 @@
"bugs": {
"url": "https://github.com/phunanon/Insitux/issues"
},
"homepage": "https://insitux.github.io/"
"homepage": "https://insitux.github.io/",
"keywords": [
"insitux",
"script",
"scripting language",
"programming language"
]
}
75 changes: 70 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const insituxVersion = 231002;
export const insituxVersion = 231005;
import { asBoo } from "./checks";
import { arityCheck, keyOpErr, numOpErr, typeCheck, typeErr } from "./checks";
import { isLetter, isDigit, isSpace, isPunc } from "./checks";
Expand All @@ -7,18 +7,19 @@ import { parse } from "./parse";
import * as pf from "./poly-fills";
const { abs, sign, sqrt, floor, ceil, round, max, min, logn, log2, log10 } = pf;
const { cos, sin, tan, acos, asin, atan, sinh, cosh, tanh } = pf;
const { concat, has, flat, push, reverse, slice, splice, sortBy } = pf;
const { len, concat, has, flat, push, reverse, slice, splice, sortBy } = pf;
const { ends, slen, starts, sub, subIdx, substr, upperCase, lowerCase } = pf;
const { trim, trimStart, trimEnd, strIdx, replace, rreplace } = pf;
const { charCode, codeChar, getTimeMs, randInt, randNum } = pf;
const { isNum, len, objKeys, range, toNum, isArray, isObj } = pf;
const { isNum, objKeys, range, toNum, isArray, isObj, toRadix, fromRadix } = pf;
import { doTests } from "./test";
import { Env, InvokeError, InvokeResult, InvokeValResult } from "./types";
import { assertUnreachable, ExternalFunctions, syntaxes } from "./types";
import { Ctx, Dict, ErrCtx, Func, Ins, Val, ops, typeNames } from "./types";
import { asArray, dictDrops, isEqual, num, stringify, val2str } from "./val";
import { dic, vec, dictDrop, dictGet, dictSet, toDict, pathSet } from "./val";
import { _boo, _num, _str, _key, _vec, _dic, _nul, _fun, str } from "./val";
import { ixToJson, jsonToIx } from "./val-translate";

let lets: { [key: string]: Val } = {};
let recurArgs: undefined | Val[];
Expand All @@ -42,6 +43,14 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val {
return _str(stringify(args));
case "strn":
return _str(stringify(args.filter(a => a.t !== "null")));
case "to-base":
case "from-base": {
const base = max(min(num(args[0]), 36), 2);
if (op === "to-base") {
return _str(toRadix(num(args[1]), base));
}
return _num(fromRadix(str(args[1]), num(args[0])));
}
case "print":
case "print-str":
ctx.print(stringify(args), op === "print");
Expand Down Expand Up @@ -578,14 +587,23 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val {
case "take-while":
case "take-until":
case "skip-while":
case "skip-until": {
case "skip-until":
case "count-while":
case "count-until": {
const isTake = op === "take-while" || op === "take-until";
const isUntil = op === "take-until" || op === "skip-until";
const isUntil =
op === "take-until" || op === "skip-until" || op === "count-until";
const closure = getExe(ctx, args[0], errCtx);
const array = asArray(args[1]);

let i = 0;
for (let lim = len(array); i < lim; ++i)
if (asBoo(closure([array[i]])) === isUntil) break;

if (op === "count-while" || op === "count-until") {
return _num(i);
}

const sliced = isTake ? slice(array, 0, i) : slice(array, i);
return args[1].t === "str"
? _str(sliced.map(str).join(""))
Expand Down Expand Up @@ -1073,6 +1091,10 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val {
return _str(codeChar(num(args[0])));
}
}
case "to-json":
return _str(ixToJson(args[0]));
case "from-json":
return jsonToIx(str(args[0]));
case "time":
return _num(getTimeMs());
case "version":
Expand Down Expand Up @@ -1101,6 +1123,49 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val {
throw e;
}
}
case "safe-eval": {
delete ctx.env.funcs["entry"];
const parsed = parse(str(args[0]), "safe-eval");

if (len(parsed.errors)) {
return _throw(parsed.errors);
}

//Ensure the only executed operations are vec/dict
const { entry } = parsed.funcs;
let dangerous = false;
let prevIns: Ins | undefined = undefined;
for (const ins of entry.ins) {
if (ins.typ === "exe" || ins.typ === "exa") {
if (!prevIns) {
dangerous = true;
break;
}
if (
prevIns.typ !== "val" ||
!(
prevIns.value.t === "func" &&
(prevIns.value.v === "vec" || prevIns.value.v === "dict")
)
) {
dangerous = true;
break;
}
}
prevIns = ins;
}

if (dangerous) {
return _nul();
}

return exeFunc(ctx, parsed.funcs.entry, [], false);
}
case "deref": {
const derefIns: Ins = { typ: "ref", value: str(args[0]), errCtx };
const derefStack = exeFunc(ctx, { ins: [derefIns] }, [], true);
return derefStack[0];
}
case "about": {
const func = str(args[0]);
const entry = ops[func];
Expand Down
6 changes: 5 additions & 1 deletion src/poly-fills.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const isObj = (x: unknown): x is object => typeof x === "object";
export const isStr = (x: unknown): x is string => typeof x === "string";
export const toNum = (x: unknown): number => Number(x); //Should also support 0b and 0x
export const toNum = (x: unknown): number => Number(x); //Must also support 0b and 0x
export const fromRadix = (x: string, r: number): number => parseInt(x, r);
export const toRadix = (x: number, r: number): string => x.toString(r);
export const slice = <T>(arr: T[], start?: number, end?: number): T[] =>
arr.slice(start, end);
export const splice = <T>(arr: T[], start: number, numDel?: number): T[] =>
Expand Down Expand Up @@ -42,6 +44,8 @@ export const range = (len: number) => [...Array(len).keys()];
export const objKeys = (x: object) => Object.keys(x);
export const objVals = (x: object) => Object.values(x);
export const getTimeMs = () => new Date().getTime();
export const toJson = (x: unknown) => JSON.stringify(x);
export const fromJson = (x: string) => JSON.parse(x);
export const abs = Math.abs;
export const min = Math.min;
export const max = Math.max;
Expand Down
2 changes: 1 addition & 1 deletion src/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ async function processCliArguments(args: string[]) {
}

const openReplAfter = extractSwitch(args, "-r");
const disableBudgets = extractSwitch(args, "-b");
const disableBudgets = extractSwitch(args, "-nb");
colourMode = !extractSwitch(args, "-nc");

if (disableBudgets) {
Expand Down
18 changes: 17 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,16 @@ export const ops: {
params: ["any", ["vec", "dict", "str"]],
returns: ["num"],
},
"count-while": {
exactArity: 2,
params: ["any", ["vec", "dict", "str"]],
returns: ["num"],
},
"count-until": {
exactArity: 2,
params: ["any", ["vec", "dict", "str"]],
returns: ["num"],
},
"max-by": {
exactArity: 2,
params: ["any", ["vec", "dict", "str"]],
Expand Down Expand Up @@ -362,6 +372,8 @@ export const ops: {
times: { minArity: 2, params: ["num", "any"] },
str: { returns: ["str"] },
strn: { returns: ["str"] },
"to-base": { exactArity: 2, params: ["num", "num"], returns: ["str"] },
"from-base": { exactArity: 2, params: ["num", "str"], returns: ["num"] },
rand: { maxArity: 2, numeric: true, returns: ["num"] },
"rand-int": { maxArity: 2, numeric: true, returns: ["num"] },
".": { minArity: 1 },
Expand Down Expand Up @@ -545,11 +557,15 @@ export const ops: {
params: [["str", "num"], "num"],
returns: ["str", "num", "null"],
},
"to-json": { exactArity: 1, returns: ["str"] },
"from-json": { exactArity: 1, params: ["str"] },
time: { exactArity: 0, returns: ["num"] },
version: { exactArity: 0, returns: ["num"] },
tests: { minArity: 0, maxArity: 1, params: ["bool"], returns: ["str"] },
symbols: { exactArity: 0, returns: ["vec"] },
eval: { exactArity: 1, params: ["str"] },
"safe-eval": { exactArity: 1, params: ["str"] },
deref: { exactArity: 1, params: ["str"] },
about: { exactArity: 1, params: [["str", "func", "unm"]], returns: ["dict"] },
reset: { exactArity: 0 },
recur: {},
Expand All @@ -561,7 +577,7 @@ export const ops: {

export const syntaxes = [
...["function", "fn", "var", "let", "var!", "let!", "if", "if-not"],
...["return", "return-when", "return-unless"],
...["return", "return-when", "return-unless", "continue", "break"],
...["when", "unless", "while", "loop", "for", "match", "satisfy"],
...["catch", "args", "E", "PI"],
];
Expand Down
12 changes: 11 additions & 1 deletion src/val-translate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isArray, isNum, isObj, isStr, objKeys, objVals } from "./poly-fills";
import { fromJson, toJson } from "./poly-fills";
import { Val } from "./types";
import { val2str } from "./val";
import { _nul, val2str } from "./val";

/** Incomplete. */
export function jsToIx(
Expand Down Expand Up @@ -67,3 +68,12 @@ export function ixToJs(
}
return ifUndetermined(v);
}

export const jsonToIx = (x: string) => {
try {
return jsToIx(fromJson(x));
} catch (e) {
return _nul();
}
};
export const ixToJson = (x: Val) => toJson(ixToJs(x));

0 comments on commit b250870

Please sign in to comment.