From 6f2aa985c79b8a16ff5c3c276e316370607c5d08 Mon Sep 17 00:00:00 2001 From: Patrick Bowen Date: Fri, 4 Aug 2023 18:12:18 +0100 Subject: [PATCH] `find-idx`; improve `var let` error messages; m. --- README.md | 5 ++ package.json | 2 +- src/index.ts | 45 ++++++++-------- src/parse.ts | 27 +++++++--- src/types.ts | 8 ++- src/val-translate.ts | 123 +++++++++++++++++++++---------------------- src/val.ts | 1 - 7 files changed, 116 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 659cecd..6ff9b7f 100644 --- a/README.md +++ b/README.md @@ -582,6 +582,11 @@ etc (find (< 5) [4 5 6 7]) → 6 (find ["a" "b"] "Able") → "b" +;Returns the index of the first item or character in a vector or string +; matching a predicate. +(find-idx odd? [6 8 9 0]) → 2 +(find-idx (< 5) [2 3 4 5 6 7]) → 4 + ;Returns the number of vector items, string characters, or dictionary entries ; matching a predicate. (count odd? (range 10)) → 5 diff --git a/package.json b/package.json index 71dc3de..9cc4488 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "insitux", - "version": "23.7.23", + "version": "23.7.28", "description": "Extensible scripting language written in portable TypeScript.", "main": "dist/invoker.js", "types": "dist/invoker.d.ts", diff --git a/src/index.ts b/src/index.ts index b41bee8..d9672c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export const insituxVersion = 230723; +export const insituxVersion = 230728; import { asBoo } from "./checks"; import { arityCheck, keyOpErr, numOpErr, typeCheck, typeErr } from "./checks"; import { isLetter, isDigit, isSpace, isPunc } from "./checks"; @@ -13,8 +13,8 @@ const { trim, trimStart, trimEnd, strIdx, replace, rreplace } = pf; const { charCode, codeChar, getTimeMs, randInt, randNum } = pf; const { isNum, len, objKeys, range, toNum, isArray, isObj } = pf; import { doTests } from "./test"; -import { assertUnreachable, Env, InvokeError, InvokeResult } from "./types"; -import { ExternalFunctions, syntaxes } from "./types"; +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"; @@ -407,30 +407,31 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val { return { t: "clo", v: { name, ins } }; } case "map": + case "for": { + const collections = slice(args, 1); + const badArg = collections.findIndex( + ({ t }) => t !== "vec" && t !== "str" && t !== "dict", + ); + if (badArg !== -1) { + const badType = typeNames[collections[badArg].t]; + throwTypeErr( + `argument ${ + badArg + 2 + } must be either: string, vector, dictionary, not ${badType}`, + errCtx, + ); + } + } case "flat-map": - case "for": case "reduce": case "reductions": case "filter": case "remove": case "find": + case "find-idx": case "count": case "all?": { const closure = getExe(ctx, args.shift()!, errCtx); - if (op === "map" || op === "for") { - const badArg = args.findIndex( - ({ t }) => t !== "vec" && t !== "str" && t !== "dict", - ); - if (badArg !== -1) { - const badType = typeNames[args[badArg].t]; - throwTypeErr( - `argument ${ - badArg + 2 - } must be either: string, vector, dictionary, not ${badType}`, - errCtx, - ); - } - } if (op === "for") { const arrays = args.map(asArray); @@ -475,7 +476,7 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val { if (op !== "reduce" && op !== "reductions") { const array = asArray(args[0]); const isRemove = op === "remove", - isFind = op === "find", + isFind = op === "find" || op === "find-idx", isCount = op === "count", isAll = op === "all?"; const filtered: Val[] = []; @@ -490,7 +491,7 @@ function exeOp(op: string, args: Val[], ctx: Ctx, errCtx: ErrCtx): Val { count += b ? 1 : 0; } else if (isFind) { if (b) { - return array[i]; + return op === "find" ? array[i] : _num(i); } } else if (b !== isRemove) { filtered.push(array[i]); @@ -1588,7 +1589,7 @@ function parseAndExe( function ingestExternalOperations(functions: ExternalFunctions) { objKeys(functions).forEach(name => { if (ops[name] && !ops[name].external) { - throw "Redefining internal operations is disallowed."; + throw `Redefining internal operations (${name}) is disallowed.`; } ops[name] = { ...functions[name].definition, external: true }; }); @@ -1696,7 +1697,7 @@ export function invokeVal( errCtx: ErrCtx, val: Val, params: Val[], -): InvokeResult { +): InvokeValResult { const ins: Ins[] = [ ...params.map(value => { typ: "val", value, errCtx }), { typ: "val", value: val, errCtx }, diff --git a/src/parse.ts b/src/parse.ts index e1c197d..2381bae 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,11 +1,11 @@ import { arityCheck, keyOpErr, numOpErr, typeCheck } from "./checks"; import { makeClosure } from "./closure"; import * as pf from "./poly-fills"; -const { has, flat, push, slice, splice } = pf; +const { has, flat, push, slice, splice, isNum, len, toNum } = pf; const { slen, starts, sub, substr, strIdx, subIdx } = pf; -const { isNum, len, toNum } = pf; import { ParamsShape, Func, Funcs, Ins, ops, Val, syntaxes } from "./types"; import { assertUnreachable, InvokeError, ErrCtx } from "./types"; +import { val2str } from "./val"; export type Token = { typ: "str" | "num" | "sym" | "rem" | "(" | ")"; @@ -453,23 +453,34 @@ function parseForm( return err("provide a value after each declaration name"); } const ins: ParserIns[] = []; - const symErrMsg = `${op} name must be a new symbol or destructuring`; for (let d = 0, lim = len(defs); d < lim; ++d) { push(ins, nodeParser(vals[d])); const def = defs[d]; if (isToken(def)) { - const defIns = nodeParser(defs[d]); - if (len(defIns) > 1 || defIns[0].typ !== "ref") { - return err(symErrMsg, defIns[0].errCtx); + const [defIns] = nodeParser(defs[d]); + if (defIns.typ === "ref") { + if (has(syntaxes, defIns.value)) { + return err( + `"${defIns.value}" cannot be redefined: already exists`, + ); + } + ins.push({ typ: op, value: defIns.value, errCtx }); + continue; } - ins.push({ typ: op, value: defIns[0].value, errCtx }); + const errMsg = + defIns.typ === "val" + ? `"${val2str(defIns.value)}" cannot be redefined: already exists` + : `invalid ${op} name`; + return err(errMsg, defIns.errCtx); } else { const { shape, errors } = parseParams([def], true); if (len(errors)) { return errors; } if (!len(shape)) { - return err(symErrMsg); + return err( + `${op} name must be a symbol or destructuring, not expression`, + ); } const typ = op === "var" ? "dva" : "dle"; ins.push({ typ, value: shape, errCtx }); diff --git a/src/types.ts b/src/types.ts index eb12ac3..7e80c4e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ export type Val = | { t: "vec"; v: Val[] } - | { t: "str" | "func" | "key" | "ref" | "unm"; v: string } + | { t: "str" | "func" | "key" | "unm"; v: string } | { t: "null"; v: undefined } | { t: "wild"; v: undefined } | { t: "bool"; v: boolean } @@ -21,6 +21,7 @@ export type InvokeResult = | { kind: "empty" } | Val | { kind: "errors"; errors: InvokeError[] }; +export type InvokeValResult = Val | { kind: "errors"; errors: InvokeError[] }; export type Dict = { keys: Val[]; @@ -282,6 +283,11 @@ export const ops: { returns: ["vec", "str", "dict"], }, find: { exactArity: 2, params: ["any", ["vec", "dict", "str"]] }, + "find-idx": { + exactArity: 2, + params: ["any", ["vec", "dict", "str"]], + returns: ["num"], + }, "take-while": { exactArity: 2, params: ["any", ["vec", "str"]], diff --git a/src/val-translate.ts b/src/val-translate.ts index 645d494..100127f 100644 --- a/src/val-translate.ts +++ b/src/val-translate.ts @@ -4,67 +4,66 @@ import { val2str } from "./val"; /** Incomplete. */ export function jsToIx( - v: unknown, - ifUndetermined = (x: unknown) => { t: "str", v: `${x}` }, - ): Val { - if (isStr(v)) { - return { t: "str", v }; - } - if (isNum(v)) { - return { t: "num", v }; - } - if (v === true || v === false) { - return { t: "bool", v }; - } - if (v === null) { - return { t: "null", v: undefined }; - } - const mapper = (v: unknown[]) => v.map(x => jsToIx(x, ifUndetermined)); - if (isArray(v)) { - return { t: "vec", v: mapper(v) }; - } - if (isObj(v)) { - return { - t: "dict", - v: { keys: mapper(objKeys(v)), vals: mapper(objVals(v)) }, - }; - } - return ifUndetermined(v); + v: unknown, + ifUndetermined = (x: unknown) => { t: "str", v: `${x}` }, +): Val { + if (isStr(v)) { + return { t: "str", v }; } - - /** Incomplete. */ - export function ixToJs( - v: Val, - ifUndetermined = (x: Val) => x.v, - ): - | string - | number - | boolean - | null - | Record - | unknown[] - | unknown { - if (v.t === "str" || v.t === "num" || v.t === "bool" || v.t === "key") { - return v.v; - } - if (v.t === "vec") { - return v.v.map(x => ixToJs(x, ifUndetermined)); - } - if (v.t === "null") { - return null; - } - if (v.t === "dict") { - const keys = v.v.keys.map(x => val2str(x)); - const vals = v.v.vals.map(x => ixToJs(x, ifUndetermined)); - const obj: Record = {}; - keys.forEach((k, i) => { - obj[k] = vals[i]; - }); - return obj; - } - if (v.t === "ext") { - return v.v; - } - return ifUndetermined(v); + if (isNum(v)) { + return { t: "num", v }; } - \ No newline at end of file + if (v === true || v === false) { + return { t: "bool", v }; + } + if (v === null) { + return { t: "null", v: undefined }; + } + const mapper = (v: unknown[]) => v.map(x => jsToIx(x, ifUndetermined)); + if (isArray(v)) { + return { t: "vec", v: mapper(v) }; + } + if (isObj(v)) { + return { + t: "dict", + v: { keys: mapper(objKeys(v)), vals: mapper(objVals(v)) }, + }; + } + return ifUndetermined(v); +} + +/** Incomplete. */ +export function ixToJs( + v: Val, + ifUndetermined = (x: Val) => x.v, +): + | string + | number + | boolean + | null + | Record + | unknown[] + | unknown { + if (v.t === "str" || v.t === "num" || v.t === "bool" || v.t === "key") { + return v.v; + } + if (v.t === "vec") { + return v.v.map(x => ixToJs(x, ifUndetermined)); + } + if (v.t === "null") { + return null; + } + if (v.t === "dict") { + const keys = v.v.keys.map(x => val2str(x)); + const vals = v.v.vals.map(x => ixToJs(x, ifUndetermined)); + const obj: Record = {}; + keys.forEach((k, i) => { + obj[k] = vals[i]; + }); + return obj; + } + if (v.t === "ext") { + return v.v; + } + return ifUndetermined(v); +} diff --git a/src/val.ts b/src/val.ts index 6668d34..0e686be 100644 --- a/src/val.ts +++ b/src/val.ts @@ -39,7 +39,6 @@ export const isEqual = (a: Val, b: Val) => { return len(a.v.keys) === len(bd.keys) && isVecEqual(a.v.keys, bd.keys); } case "str": - case "ref": case "key": case "func": return str(a) === str(b);