Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,14 @@ type FnTypeSource = NodeBase & {
// @public (undocumented)
type For = NodeBase & {
type: 'for';
var?: string;
from?: Expression;
to?: Expression;
times?: Expression;
for: Statement | Expression;
};
} & ({
var: string;
from: Expression;
to: Expression;
} | {
times: Expression;
});

// @public (undocumented)
function getLangVersion(input: string): string | null;
Expand Down
10 changes: 6 additions & 4 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TokenKind } from './parser/token.js';
import type { Pos } from './node.js';

export abstract class AiScriptError extends Error {
Expand All @@ -25,9 +24,12 @@ export abstract class AiScriptError extends Error {
export class NonAiScriptError extends AiScriptError {
public name = 'Internal';
constructor(error: unknown) {
const message = String(
(error as { message?: unknown } | null | undefined)?.message ?? error,
);
let message: string;
if (error != null && typeof error === 'object' && 'message' in error) {
message = String(error.message);
} else {
message = String(error);
}
super(message, error);
}
}
Expand Down
73 changes: 35 additions & 38 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getPrimProp } from './primitive-props.js';
import { Variable } from './variable.js';
import { Reference } from './reference.js';
import type { JsValue } from './util.js';
import type { Value, VFn, VUserFn } from './value.js';
import type { Value, VFn, VFnParam } from './value.js';

export type LogObject = {
scope?: string;
Expand Down Expand Up @@ -61,7 +61,7 @@ export class Interpreter {
const q = args[0];
assertString(q);
if (this.opts.in == null) return NULL;
const a = await this.opts.in!(q.value);
const a = await this.opts.in(q.value);
return STR(a);
}),
};
Expand Down Expand Up @@ -280,15 +280,19 @@ export class Interpreter {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return result ?? NULL;
} else {
const fnScope = fn.scope!.createChildScope();
const fnScope = fn.scope.createChildScope();
for (const [i, param] of fn.params.entries()) {
const arg = args[i];
if (!param.default) expectAny(arg);
this.define(fnScope, param.dest, arg ?? param.default!, true);
let arg = args[i];
if (!param.default) {
expectAny(arg);
} else if (!arg) {
arg = param.default;
}
this.define(fnScope, param.dest, arg, true);
}

const info: CallInfo = { name: fn.name ?? '<anonymous>', pos };
return unWrapRet(await this._run(fn.statements!, fnScope, [...callStack, info]));
return unWrapRet(await this._run(fn.statements, fnScope, [...callStack, info]));
}
}

Expand Down Expand Up @@ -423,7 +427,7 @@ export class Interpreter {
}

case 'for': {
if (node.times) {
if ('times' in node) {
const times = await this._eval(node.times, scope, callStack);
if (isControl(times)) {
return times;
Expand All @@ -438,19 +442,19 @@ export class Interpreter {
}
}
} else {
const from = await this._eval(node.from!, scope, callStack);
const from = await this._eval(node.from, scope, callStack);
if (isControl(from)) {
return from;
}
const to = await this._eval(node.to!, scope, callStack);
const to = await this._eval(node.to, scope, callStack);
if (isControl(to)) {
return to;
}
assertNumber(from);
assertNumber(to);
for (let i = from.value; i < from.value + to.value; i++) {
const v = await this._eval(node.for, scope.createChildScope(new Map([
[node.var!, {
[node.var, {
isMutable: false,
value: NUM(i),
}],
Expand Down Expand Up @@ -604,8 +608,9 @@ export class Interpreter {
return target;
}
if (isObject(target)) {
if (target.value.has(node.name)) {
return target.value.get(node.name)!;
const value = target.value.get(node.name);
if (value != null) {
return value;
} else {
return NULL;
}
Expand All @@ -632,8 +637,9 @@ export class Interpreter {
return item;
} else if (isObject(target)) {
assertString(i);
if (target.value.has(i.value)) {
return target.value.get(i.value)!;
const value = target.value.get(i.value);
if (value != null) {
return value;
} else {
return NULL;
}
Expand Down Expand Up @@ -670,28 +676,21 @@ export class Interpreter {
}

case 'fn': {
const params = await Promise.all(node.params.map(async (param) => {
return {
const params: VFnParam[] = [];
for (const param of node.params) {
const defaultValue = param.default ? await this._eval(param.default, scope, callStack) :
param.optional ? NULL :
undefined;
if (defaultValue != null && isControl(defaultValue)) {
return defaultValue;
}
params.push({
dest: param.dest,
default:
param.default ? await this._eval(param.default, scope, callStack) :
param.optional ? NULL :
undefined,
default: defaultValue,
// type: (TODO)
};
}));
const control = params
.map((param) => param.default)
.filter((value) => value != null)
.find(isControl);
if (control != null) {
return control;
}
return FN(
params as VUserFn['params'],
node.children,
scope,
);
});
}
return FN(params, node.children, scope);
}

case 'block': {
Expand Down Expand Up @@ -851,9 +850,7 @@ export class Interpreter {

let v: Value | Control = NULL;

for (let i = 0; i < program.length; i++) {
const node = program[i]!;

for (const node of program) {
v = await this._eval(node, scope, callStack);
if (v.type === 'return') {
this.log('block:return', { scope: scope.name, val: v.value });
Expand Down
43 changes: 21 additions & 22 deletions src/interpreter/primitive-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@ import type { Value, VArr, VFn, VNum, VStr, VError } from './value.js';
type VWithPP = VNum|VStr|VArr|VError;

const PRIMITIVE_PROPS: {
[key in VWithPP['type']]: { [key: string]: (target: Value) => Value }
[key in VWithPP['type']]: Map<string, (target: Value) => Value>;
} & {
[key in (Exclude<Value, VWithPP>)['type']]?: never;
} = {
num: {
num: new Map(Object.entries({
to_str: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => {
return STR(target.value.toString());
}),

to_hex: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => {
return STR(target.value.toString(16));
}),
},
})),

str: {
str: new Map(Object.entries({
to_num: (target: VStr): VFn => FN_NATIVE(async (_, _opts) => {
const parsed = parseInt(target.value, 10);
if (isNaN(parsed)) return NULL;
Expand Down Expand Up @@ -168,9 +170,9 @@ const PRIMITIVE_PROPS: {

return STR(target.value.padEnd(width.value, s));
}),
},
})),

arr: {
arr: new Map(Object.entries({
len: (target: VArr): VNum => NUM(target.value.length),

push: (target: VArr): VFn => FN_NATIVE(async ([val], _opts) => {
Expand Down Expand Up @@ -219,9 +221,8 @@ const PRIMITIVE_PROPS: {

filter: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
const vals = [] as Value[];
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
const vals: Value[] = [];
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) vals.push(item);
Expand All @@ -243,8 +244,7 @@ const PRIMITIVE_PROPS: {

find: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) return item;
Expand Down Expand Up @@ -382,8 +382,7 @@ const PRIMITIVE_PROPS: {

every: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (!res.value) return FALSE;
Expand All @@ -393,8 +392,7 @@ const PRIMITIVE_PROPS: {

some: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => {
assertFunction(fn);
for (let i = 0; i < target.value.length; i++) {
const item = target.value[i]!;
for (const [i, item] of target.value.entries()) {
const res = await opts.call(fn, [item, NUM(i)]);
assertBoolean(res);
if (res.value) return TRUE;
Expand Down Expand Up @@ -423,20 +421,21 @@ const PRIMITIVE_PROPS: {
assertNumber(index);
return target.value.at(index.value) ?? otherwise ?? NULL;
}),
},
})),

error: {
error: new Map(Object.entries({
name: (target: VError): VStr => STR(target.value),

info: (target: VError): Value => target.info ?? NULL,
},
})),
} as const;

export function getPrimProp(target: Value, name: string): Value {
if (Object.hasOwn(PRIMITIVE_PROPS, target.type)) {
const props = PRIMITIVE_PROPS[target.type as VWithPP['type']];
if (Object.hasOwn(props, name)) {
return props[name]!(target);
const props = PRIMITIVE_PROPS[target.type];
if (props != null) {
const prop = props.get(name);
if (prop != null) {
return prop(target);
} else {
throw new AiScriptRuntimeError(`No such prop (${name}) in ${target.type}.`);
}
Expand Down
23 changes: 13 additions & 10 deletions src/interpreter/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import type { Value } from './value.js';
import type { Variable } from './variable.js';
import type { LogObject } from './index.js';

export type LayerdStates = [Map<string, Variable>, ...Map<string, Variable>[]]

export class Scope {
private parent?: Scope;
private layerdStates: Map<string, Variable>[];
private layerdStates: LayerdStates;
public name: string;
public opts: {
log?(type: string, params: LogObject): void;
onUpdated?(name: string, value: Value): void;
} = {};
public nsName?: string;

constructor(layerdStates: Scope['layerdStates'] = [], parent?: Scope, name?: Scope['name'], nsName?: string) {
constructor(layerdStates: Scope['layerdStates'] = [new Map()], parent?: Scope, name?: Scope['name'], nsName?: string) {
this.layerdStates = layerdStates;
this.parent = parent;
this.name = name || (layerdStates.length === 1 ? '<root>' : '<anonymous>');
Expand All @@ -41,13 +43,13 @@ export class Scope {

@autobind
public createChildScope(states: Map<string, Variable> = new Map(), name?: Scope['name']): Scope {
const layer = [states, ...this.layerdStates];
const layer: LayerdStates = [states, ...this.layerdStates];
return new Scope(layer, this, name);
}

@autobind
public createChildNamespaceScope(nsName: string, states: Map<string, Variable> = new Map(), name?: Scope['name']): Scope {
const layer = [states, ...this.layerdStates];
const layer: LayerdStates = [states, ...this.layerdStates];
return new Scope(layer, this, name, nsName);
}

Expand All @@ -58,8 +60,9 @@ export class Scope {
@autobind
public get(name: string): Value {
for (const layer of this.layerdStates) {
if (layer.has(name)) {
const state = layer.get(name)!.value;
const value = layer.get(name);
if (value) {
const state = value.value;
this.log('read', { var: name, val: state });
return state;
}
Expand Down Expand Up @@ -103,7 +106,7 @@ export class Scope {
public getAll(): Map<string, Variable> {
const vars = this.layerdStates.reduce((arr, layer) => {
return [...arr, ...layer];
}, [] as [string, Variable][]);
}, [] satisfies [string, Variable][]);
return new Map(vars);
}

Expand All @@ -115,7 +118,7 @@ export class Scope {
@autobind
public add(name: string, variable: Variable): void {
this.log('add', { var: name, val: variable });
const states = this.layerdStates[0]!;
const states = this.layerdStates[0];
if (states.has(name)) {
throw new AiScriptRuntimeError(
`Variable '${name}' already exists in scope '${this.name}'`,
Expand All @@ -135,8 +138,8 @@ export class Scope {
public assign(name: string, val: Value): void {
let i = 1;
for (const layer of this.layerdStates) {
if (layer.has(name)) {
const variable = layer.get(name)!;
const variable = layer.get(name);
if (variable != null) {
if (!variable.isMutable) {
throw new AiScriptRuntimeError(`Cannot assign to an immutable variable ${name}.`);
}
Expand Down
Loading