diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index bebd9ef7..648af961 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -416,7 +416,7 @@ type Index = NodeBase & { // @public (undocumented) export class Interpreter { - constructor(consts: Record, opts?: { + constructor(globals: Record, opts?: { in?(q: string): Promise; out?(value: Value): void; err?(e: AiScriptError): void; @@ -713,6 +713,9 @@ export class Scope { // @public (undocumented) type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign; +// @public (undocumented) +export const std: Record; + // @public (undocumented) const STR: (str: VStr["value"]) => VStr; @@ -918,7 +921,7 @@ type VUserFn = VFnBase & { // Warnings were encountered during analysis: // -// src/interpreter/index.ts:49:4 - (ae-forgotten-export) The symbol "LogObject" needs to be exported by the entry point index.d.ts +// src/interpreter/index.ts:50:4 - (ae-forgotten-export) The symbol "LogObject" needs to be exported by the entry point index.d.ts // src/interpreter/value.ts:47:2 - (ae-forgotten-export) The symbol "Type" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/index.ts b/src/index.ts index ee600c18..0bdc3994 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ //export * from './interpreter/index'; //export * as utils from './interpreter/util'; //export * as values from './interpreter/value'; -import { Interpreter } from './interpreter/index.js'; +import { Interpreter, std } from './interpreter/index.js'; import { Scope } from './interpreter/scope.js'; import * as utils from './interpreter/util.js'; import * as values from './interpreter/value.js'; @@ -12,7 +12,7 @@ import * as errors from './error.js'; import * as Ast from './node.js'; import { AISCRIPT_VERSION } from './constants.js'; import type { ParserPlugin, PluginType } from './parser/index.js'; -export { Interpreter }; +export { Interpreter, std }; export { Scope }; export { utils }; export { values }; diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 1ea3d16c..b560cbc3 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -7,16 +7,18 @@ import { AiScriptError, NonAiScriptError, AiScriptNamespaceError, AiScriptIndexO import * as Ast from '../node.js'; import { nodeToJs } from '../utils/node-to-js.js'; import { Scope } from './scope.js'; -import { std } from './lib/std.js'; import { RETURN, unWrapRet, BREAK, CONTINUE, assertValue, isControl, type Control, unWrapLabeledBreak } from './control.js'; import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, expectAny, reprValue, isFunction } from './util.js'; import { NULL, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN, ERROR } from './value.js'; import { getPrimProp } from './primitive-props.js'; import { Variable } from './variable.js'; import { Reference } from './reference.js'; +import { stdCore } from './lib/core.js'; import type { JsValue } from './util.js'; import type { Value, VFn, VUserFn } from './value.js'; +export { std } from './lib/std.js'; + export type LogObject = { scope?: string; var?: string; @@ -36,12 +38,11 @@ export class Interpreter { private abortHandlers: (() => void)[] = []; private pauseHandlers: (() => void)[] = []; private unpauseHandlers: (() => void)[] = []; - private vars: Record = {}; private irqRate: number; private irqSleep: () => Promise; constructor( - consts: Record, + globals: Record, private opts: { in?(q: string): Promise; out?(value: Value): void; @@ -67,13 +68,12 @@ export class Interpreter { }), }; - this.vars = Object.fromEntries(Object.entries({ - ...consts, - ...std, - ...io, - }).map(([k, v]) => [k, Variable.const(v)])); - - this.scope = new Scope([new Map(Object.entries(this.vars))]); + this.scope = new Scope([ + new Map( + Object.entries({ ...globals,...stdCore, ...io }) + .map(([k, v]) => [k, Variable.const(v)]), + ), + ]); this.scope.opts.log = (type, params): void => { switch (type) { case 'add': this.log('var:add', params); break; diff --git a/src/interpreter/lib/core.ts b/src/interpreter/lib/core.ts new file mode 100644 index 00000000..fa21c0bd --- /dev/null +++ b/src/interpreter/lib/core.ts @@ -0,0 +1,138 @@ +import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL } from '../value.js'; +import { assertNumber, assertString, assertBoolean, eq, expectAny, reprValue } from '../util.js'; +import { AiScriptUserError } from '../../error.js'; +import { AISCRIPT_VERSION } from '../../constants.js'; +import type { Value } from '../value.js'; + +export const stdCore: Record<`Core:${string}`, Value> = { + 'Core:v': STR(AISCRIPT_VERSION), + + 'Core:ai': STR('kawaii'), + + 'Core:not': FN_NATIVE(([a]) => { + assertBoolean(a); + return a.value ? FALSE : TRUE; + }), + + 'Core:eq': FN_NATIVE(([a, b]) => { + expectAny(a); + expectAny(b); + return eq(a, b) ? TRUE : FALSE; + }), + + 'Core:neq': FN_NATIVE(([a, b]) => { + expectAny(a); + expectAny(b); + return eq(a, b) ? FALSE : TRUE; + }), + + 'Core:and': FN_NATIVE(([a, b]) => { + assertBoolean(a); + if (!a.value) return FALSE; + assertBoolean(b); + return b.value ? TRUE : FALSE; + }), + + 'Core:or': FN_NATIVE(([a, b]) => { + assertBoolean(a); + if (a.value) return TRUE; + assertBoolean(b); + return b.value ? TRUE : FALSE; + }), + + 'Core:add': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return NUM(a.value + b.value); + }), + + 'Core:sub': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return NUM(a.value - b.value); + }), + + 'Core:mul': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return NUM(a.value * b.value); + }), + + 'Core:pow': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + const res = a.value ** b.value; + return NUM(res); + }), + + 'Core:div': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + const res = a.value / b.value; + return NUM(res); + }), + + 'Core:mod': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return NUM(a.value % b.value); + }), + + 'Core:gt': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return a.value > b.value ? TRUE : FALSE; + }), + + 'Core:lt': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return a.value < b.value ? TRUE : FALSE; + }), + + 'Core:gteq': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return a.value >= b.value ? TRUE : FALSE; + }), + + 'Core:lteq': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + return a.value <= b.value ? TRUE : FALSE; + }), + + 'Core:type': FN_NATIVE(([v]) => { + expectAny(v); + return STR(v.type); + }), + + 'Core:to_str': FN_NATIVE(([v]) => { + expectAny(v); + + return STR(reprValue(v)); + }), + + 'Core:range': FN_NATIVE(([a, b]) => { + assertNumber(a); + assertNumber(b); + if (a.value < b.value) { + return ARR(Array.from({ length: (b.value - a.value) + 1 }, (_, i) => NUM(i + a.value))); + } else if (a.value > b.value) { + return ARR(Array.from({ length: (a.value - b.value) + 1 }, (_, i) => NUM(a.value - i))); + } else { + return ARR([a]); + } + }), + + 'Core:sleep': FN_NATIVE(async ([delay]) => { + assertNumber(delay); + await new Promise((r) => setTimeout(r, delay.value)); + return NULL; + }), + + 'Core:abort': FN_NATIVE(async ([message]) => { + assertString(message); + throw new AiScriptUserError(message.value); + }), +}; diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 13648a16..f24a13a9 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -1,9 +1,8 @@ /* eslint-disable no-empty-pattern */ import { v4 as uuid } from 'uuid'; -import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; -import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; -import { AiScriptRuntimeError, AiScriptUserError } from '../../error.js'; -import { AISCRIPT_VERSION } from '../../constants.js'; +import { NUM, STR, FN_NATIVE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; +import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, expectAny, assertArray } from '../util.js'; +import { AiScriptRuntimeError } from '../../error.js'; import { textDecoder } from '../../const.js'; import { stdMath } from './math.js'; import type { Value } from '../value.js'; @@ -13,137 +12,6 @@ export const std: Record = { 'help': STR('SEE: https://github.com/syuilo/aiscript/blob/master/docs/get-started.md'), - //#region Core - 'Core:v': STR(AISCRIPT_VERSION), - - 'Core:ai': STR('kawaii'), - - 'Core:not': FN_NATIVE(([a]) => { - assertBoolean(a); - return a.value ? FALSE : TRUE; - }), - - 'Core:eq': FN_NATIVE(([a, b]) => { - expectAny(a); - expectAny(b); - return eq(a, b) ? TRUE : FALSE; - }), - - 'Core:neq': FN_NATIVE(([a, b]) => { - expectAny(a); - expectAny(b); - return eq(a, b) ? FALSE : TRUE; - }), - - 'Core:and': FN_NATIVE(([a, b]) => { - assertBoolean(a); - if (!a.value) return FALSE; - assertBoolean(b); - return b.value ? TRUE : FALSE; - }), - - 'Core:or': FN_NATIVE(([a, b]) => { - assertBoolean(a); - if (a.value) return TRUE; - assertBoolean(b); - return b.value ? TRUE : FALSE; - }), - - 'Core:add': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return NUM(a.value + b.value); - }), - - 'Core:sub': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return NUM(a.value - b.value); - }), - - 'Core:mul': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return NUM(a.value * b.value); - }), - - 'Core:pow': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - const res = a.value ** b.value; - return NUM(res); - }), - - 'Core:div': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - const res = a.value / b.value; - return NUM(res); - }), - - 'Core:mod': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return NUM(a.value % b.value); - }), - - 'Core:gt': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return a.value > b.value ? TRUE : FALSE; - }), - - 'Core:lt': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return a.value < b.value ? TRUE : FALSE; - }), - - 'Core:gteq': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return a.value >= b.value ? TRUE : FALSE; - }), - - 'Core:lteq': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - return a.value <= b.value ? TRUE : FALSE; - }), - - 'Core:type': FN_NATIVE(([v]) => { - expectAny(v); - return STR(v.type); - }), - - 'Core:to_str': FN_NATIVE(([v]) => { - expectAny(v); - - return STR(reprValue(v)); - }), - - 'Core:range': FN_NATIVE(([a, b]) => { - assertNumber(a); - assertNumber(b); - if (a.value < b.value) { - return ARR(Array.from({ length: (b.value - a.value) + 1 }, (_, i) => NUM(i + a.value))); - } else if (a.value > b.value) { - return ARR(Array.from({ length: (a.value - b.value) + 1 }, (_, i) => NUM(a.value - i))); - } else { - return ARR([a]); - } - }), - 'Core:sleep': FN_NATIVE(async ([delay]) => { - assertNumber(delay); - await new Promise((r) => setTimeout(r, delay.value)); - return NULL; - }), - 'Core:abort': FN_NATIVE(async ([message]) => { - assertString(message); - throw new AiScriptUserError(message.value); - }), - //#endregion - //#region Util 'Util:uuid': FN_NATIVE(() => { return STR(uuid()); @@ -318,12 +186,12 @@ export const std: Record = { 'Uri:encode_full': FN_NATIVE(([v]) => { assertString(v); return STR(encodeURI(v.value)); - }), + }), 'Uri:encode_component': FN_NATIVE(([v]) => { assertString(v); return STR(encodeURIComponent(v.value)); - }), + }), 'Uri:decode_full': FN_NATIVE(([v]) => { assertString(v); diff --git a/test/interpreter.ts b/test/interpreter.ts index 0b8bc0ea..1b1a98e8 100644 --- a/test/interpreter.ts +++ b/test/interpreter.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'; -import { Parser, Interpreter, values, errors, utils, Ast } from '../src'; +import { Parser, Interpreter, std, values, errors, utils, Ast } from '../src'; import { FALSE, NUM, OBJ, STR, TRUE, Value } from '../src/interpreter/value'; let { FN_NATIVE } = values; @@ -261,6 +261,7 @@ describe('pause', () => { let count = 0; const interpreter = new Interpreter({ + ...std, count: values.FN_NATIVE(() => { count++; }), }, {}); diff --git a/test/testutils.ts b/test/testutils.ts index 0b09ac11..feedd52c 100644 --- a/test/testutils.ts +++ b/test/testutils.ts @@ -1,11 +1,11 @@ import { expect as globalExpect } from 'vitest'; -import { Parser, Interpreter } from '../src'; +import { Parser, Interpreter, std } from '../src'; import { Value } from '../src/interpreter/value'; export async function exe(script: string): Promise { const parser = new Parser(); let result = undefined; - const interpreter = new Interpreter({}, { + const interpreter = new Interpreter(std, { out(value) { if (!result) result = value; else if (!Array.isArray(result)) result = [result, value]; @@ -23,7 +23,7 @@ export async function exe(script: string): Promise { export function exeSync(script: string): Value | undefined { const parser = new Parser(); - const interpreter = new Interpreter({}, { + const interpreter = new Interpreter(std, { out(value) { }, log(type, {val}) {