diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index b35a710b..6813e93f 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -177,14 +177,6 @@ declare namespace Ast { } export { Ast } -// @public (undocumented) -type Attr_2 = { - attr?: { - name: string; - value: Value; - }[]; -}; - // @public (undocumented) type Attribute = NodeBase & { type: 'attr'; @@ -855,8 +847,6 @@ export class Scope { assign(name: string, val: Value): void; // (undocumented) createChildNamespaceScope(nsName: string, states?: Map, name?: Scope['name']): Scope; - // Warning: (ae-forgotten-export) The symbol "Variable" needs to be exported by the entry point index.d.ts - // // (undocumented) createChildScope(states?: Map, name?: Scope['name']): Scope; exists(name: string): boolean; @@ -968,7 +958,7 @@ function valToJs(val: Value): any; function valToString(val: Value, simple?: boolean): string; // @public (undocumented) -type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue | VError) & Attr_2; +type Value = VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue | VError; declare namespace values { export { @@ -983,7 +973,6 @@ declare namespace values { VBreak, VContinue, VError, - Attr_2 as Attr, Value, NULL, TRUE, @@ -1004,6 +993,30 @@ declare namespace values { } export { values } +// @public (undocumented) +export type Variable = ({ + isMutable: false; + readonly value: Value; +} | { + isMutable: true; + value: Value; +}) & { + type?: Type; + attrs?: Attr_2[]; +}; + +// @public (undocumented) +export const Variable: { + mut(value: Value, opts?: { + type?: Type; + attrs?: Attr_2[]; + }): Variable; + const(value: Value, opts?: { + type?: Type; + attrs?: Attr_2[]; + }): Variable; +}; + // @public (undocumented) type VArr = { type: 'arr'; @@ -1078,6 +1091,11 @@ type VStr = { value: string; }; +// Warnings were encountered during analysis: +// +// src/interpreter/variable.ts:14:2 - (ae-forgotten-export) The symbol "Type" needs to be exported by the entry point index.d.ts +// src/interpreter/variable.ts:15:2 - (ae-forgotten-export) The symbol "Attr_2" 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 a2420552..3aa0df04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { Interpreter } from './interpreter/index.js'; import { Scope } from './interpreter/scope.js'; import * as utils from './interpreter/util.js'; import * as values from './interpreter/value.js'; +import { Variable } from './interpreter/variable.js'; import { Parser, ParserPlugin, PluginType } from './parser/index.js'; import * as Cst from './parser/node.js'; import * as errors from './error.js'; @@ -15,6 +16,7 @@ export { Interpreter }; export { Scope }; export { utils }; export { values }; +export { Variable }; export { Parser }; export { ParserPlugin }; export { PluginType }; diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 23df41ea..f2e03950 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -4,6 +4,7 @@ import { autobind } from '../utils/mini-autobind.js'; import { AiScriptError, NonAiScriptError, AiScriptIndexOutOfRangeError, AiScriptRuntimeError } from '../error.js'; +import { getTypeBySource } from '../type.js'; import { Scope } from './scope.js'; import { std } from './lib/std.js'; import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, expectAny, reprValue } from './util.js'; @@ -370,19 +371,23 @@ export class Interpreter { case 'def': { const value = await this._eval(node.expr, scope); + let attrs: Variable['attrs'] = undefined; if (node.attr.length > 0) { - const attrs: Value['attr'] = []; + attrs = []; for (const nAttr of node.attr) { attrs.push({ name: nAttr.name, value: await this._eval(nAttr.value, scope), }); } - value.attr = attrs; } + let type = undefined; + if (node.varType) type = getTypeBySource(node.varType); scope.add(node.name, { isMutable: node.mut, - value: value, + value, + type, + attrs, }); return NULL; } diff --git a/src/interpreter/value.ts b/src/interpreter/value.ts index 0933bc18..a43301ac 100644 --- a/src/interpreter/value.ts +++ b/src/interpreter/value.ts @@ -67,14 +67,7 @@ export type VError = { info?: Value; }; -export type Attr = { - attr?: { - name: string; - value: Value; - }[]; -}; - -export type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue | VError) & Attr; +export type Value = VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue | VError; export const NULL = { type: 'null' as const, diff --git a/src/interpreter/variable.ts b/src/interpreter/variable.ts index 30252b5d..de2d1e77 100644 --- a/src/interpreter/variable.ts +++ b/src/interpreter/variable.ts @@ -1,6 +1,7 @@ import type { Value } from './value.js'; +import type { Type } from '../type.js'; -export type Variable = +export type Variable = ( | { isMutable: false readonly value: Value @@ -9,18 +10,28 @@ export type Variable = isMutable: true value: Value } +) & { + type?: Type + attrs?: Attr[]; +}; +export type Attr = { + name: string; + value: Value; +}; export const Variable = { - mut(value: Value): Variable { + mut(value: Value, opts?: { type?: Type, attrs?: Attr[] }): Variable { return { isMutable: true, value, + ...opts, }; }, - const(value: Value): Variable { + const(value: Value, opts?: { type?: Type, attrs?: Attr[] }): Variable { return { isMutable: false, value, + ...opts, }; }, }; diff --git a/src/type.ts b/src/type.ts index 5e93acf0..4def411b 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,13 +1,27 @@ -import { AiScriptSyntaxError } from './error.js'; +import { AiScriptTypeError } from './error.js'; import type * as Ast from './node.js'; -// Type (Semantic analyzed) +// Types (Semantically analyzed) + +export type Type = TSimple | TGeneric | TFn; export type TSimple = { type: 'simple'; name: N; } +export type TGeneric = { + type: 'generic'; + name: N; + inners: Type[]; +} + +export type TFn = { + type: 'fn'; + args: Type[]; + result: Type; +}; + export function T_SIMPLE(name: T): TSimple { return { type: 'simple', @@ -15,16 +29,6 @@ export function T_SIMPLE(name: T): TSimple { }; } -export function isAny(x: Type): x is TSimple<'any'> { - return x.type === 'simple' && x.name === 'any'; -} - -export type TGeneric = { - type: 'generic'; - name: N; - inners: Type[]; -} - export function T_GENERIC(name: N, inners: Type[]): TGeneric { return { type: 'generic', @@ -33,12 +37,6 @@ export function T_GENERIC(name: N, inners: Type[]): TGeneric { return x.type === 'simple' && x.name === 'any'; } // Utility +/** + * Checks value of type b is assignable to type a, + * or in other words type a is superset of type b. + */ export function isCompatibleType(a: Type, b: Type): boolean { - if (isAny(a) || isAny(b)) return true; - if (a.type !== b.type) return false; + if (isAny(a)) return true; + if (isAny(b)) return false; + // isTSimple系のif文で分岐すると網羅性チェックができない? switch (a.type) { - case 'simple': { - assertTSimple(b); // NOTE: TypeGuardが効かない - if (a.name !== b.name) return false; - break; - } - case 'generic': { - assertTGeneric(b); // NOTE: TypeGuardが効かない - // name - if (a.name !== b.name) return false; - // inners - if (a.inners.length !== b.inners.length) return false; - for (let i = 0; i < a.inners.length; i++) { + case 'simple' : + if (!isTSimple(b)) return false; + if (!(a.name === b.name)) return false; + return true; + + case 'generic' : + if (!isTGeneric(b)) return false; + if (!(a.name === b.name)) return false; + if (!(a.inners.length !== b.inners.length)) return false; + for (const i in a.inners) { if (!isCompatibleType(a.inners[i]!, b.inners[i]!)) return false; } - break; - } - case 'fn': { - assertTFn(b); // NOTE: TypeGuardが効かない - // fn result + return true; + + case 'fn' : + if (!isTFn(b)) return false; if (!isCompatibleType(a.result, b.result)) return false; - // fn args - if (a.args.length !== b.args.length) return false; - for (let i = 0; i < a.args.length; i++) { + if (!(a.args.length !== b.args.length)) return false; + for (const i in a.args) { if (!isCompatibleType(a.args[i]!, b.args[i]!)) return false; } - break; - } + return true; } - - return true; } +/** + * Type to string representation + */ export function getTypeName(type: Type): string { switch (type.type) { case 'simple': { @@ -151,7 +149,7 @@ export function getTypeBySource(typeSource: Ast.TypeSource): Type { return T_GENERIC(typeSource.name, [innerType]); } } - throw new AiScriptSyntaxError(`Unknown type: '${getTypeNameBySource(typeSource)}'`); + throw new AiScriptTypeError(`Unknown type: '${getTypeNameBySource(typeSource)}'`); } else { const argTypes = typeSource.args.map(arg => getTypeBySource(arg)); return T_FN(argTypes, getTypeBySource(typeSource.result));