Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
14 changes: 8 additions & 6 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,14 @@ type FnTypeSource = NodeBase & {
type For = NodeBase & {
type: 'for';
label?: string;
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 Expand Up @@ -669,7 +671,7 @@ type Return = NodeBase & {

// @public (undocumented)
export class Scope {
constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name'], nsName?: string);
constructor(layeredStates?: Scope['layeredStates'], parent?: Scope, name?: Scope['name'], nsName?: string);
add(name: string, variable: Variable): void;
assign(name: string, val: Value): void;
// (undocumented)
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
71 changes: 34 additions & 37 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 @@ -430,7 +434,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 @@ -452,19 +456,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 @@ -632,8 +636,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 @@ -660,8 +665,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 @@ -698,28 +704,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 @@ -887,9 +886,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
Loading