Skip to content

Variableに型とattributeの情報を付与 #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 30 additions & 12 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -855,8 +847,6 @@ export class Scope {
assign(name: string, val: Value): void;
// (undocumented)
createChildNamespaceScope(nsName: string, states?: Map<string, Variable>, 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<string, Variable>, name?: Scope['name']): Scope;
exists(name: string): boolean;
Expand Down Expand Up @@ -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 {
Expand All @@ -983,7 +973,6 @@ declare namespace values {
VBreak,
VContinue,
VError,
Attr_2 as Attr,
Value,
NULL,
TRUE,
Expand All @@ -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';
Expand Down Expand Up @@ -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)

```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -15,6 +16,7 @@ export { Interpreter };
export { Scope };
export { utils };
export { values };
export { Variable };
export { Parser };
export { ParserPlugin };
export { PluginType };
Expand Down
11 changes: 8 additions & 3 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
Expand Down
9 changes: 1 addition & 8 deletions src/interpreter/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 14 additions & 3 deletions src/interpreter/variable.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
};
},
};
98 changes: 48 additions & 50 deletions src/type.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
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<N extends string = string> = {
type: 'simple';
name: N;
}

export type TGeneric<N extends string = string> = {
type: 'generic';
name: N;
inners: Type[];
}

export type TFn = {
type: 'fn';
args: Type[];
result: Type;
};

export function T_SIMPLE<T extends string>(name: T): TSimple<T> {
return {
type: 'simple',
name: name,
};
}

export function isAny(x: Type): x is TSimple<'any'> {
return x.type === 'simple' && x.name === 'any';
}

export type TGeneric<N extends string = string> = {
type: 'generic';
name: N;
inners: Type[];
}

export function T_GENERIC<N extends string>(name: N, inners: Type[]): TGeneric<N> {
return {
type: 'generic',
Expand All @@ -33,12 +37,6 @@ export function T_GENERIC<N extends string>(name: N, inners: Type[]): TGeneric<N
};
}

export type TFn = {
type: 'fn';
args: Type[];
result: Type;
};

export function T_FN(args: Type[], result: Type): TFn {
return {
type: 'fn',
Expand All @@ -47,51 +45,51 @@ export function T_FN(args: Type[], result: Type): TFn {
};
}

export type Type = TSimple | TGeneric | TFn;

function assertTSimple(t: Type): asserts t is TSimple { if (t.type !== 'simple') { throw new TypeError('assertTSimple failed.'); } }
function assertTGeneric(t: Type): asserts t is TGeneric { if (t.type !== 'generic') { throw new TypeError('assertTGeneric failed.'); } }
function assertTFn(t: Type): asserts t is TFn { if (t.type !== 'fn') { throw new TypeError('assertTFn failed.'); } }
function isTSimple(x: Type): x is TSimple { return x.type === 'simple'; }
function isTGeneric(x: Type): x is TGeneric { return x.type === 'generic'; }
function isTFn(x: Type): x is TFn { return x.type === 'fn'; }
function isAny(x: Type): x is TSimple<'any'> { 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': {
Expand Down Expand Up @@ -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));
Expand Down