diff --git a/src/acorn-helpers/isType.ts b/src/acorn-helpers/isType.ts new file mode 100644 index 0000000..b5f1cfe --- /dev/null +++ b/src/acorn-helpers/isType.ts @@ -0,0 +1,29 @@ +import { + Node, + ExpressionStatement, + TypeStrings, + Literal, + NumericLiteral, + StringLiteral, + Program, +} from './types'; + +export function isProgram(node: Node): node is Program { + return node.type === TypeStrings.Program; +} + +export function isExpressionStatement(node: Node): node is ExpressionStatement { + return node.type === TypeStrings.ExpressionStatement; +} + +export function isLiteral(node: Node): node is Literal { + return node.type === TypeStrings.Literal; +} + +export function isNumericLiteral(node: Node): node is NumericLiteral { + return isLiteral(node) && typeof node.value === 'number'; +} + +export function isStringLiteral(node: Node): node is StringLiteral { + return isLiteral(node) && typeof node.value === 'string'; +} diff --git a/src/acorn-helpers/types.ts b/src/acorn-helpers/types.ts new file mode 100644 index 0000000..6d76a38 --- /dev/null +++ b/src/acorn-helpers/types.ts @@ -0,0 +1,36 @@ +import { Node as AcornNode } from 'acorn'; + +export type Node = AcornNode; + +export enum TypeStrings { + Program = 'Program', + ExpressionStatement = 'ExpressionStatement', + Literal = 'Literal', +} + +export interface Program extends Node { + type: TypeStrings.Program; + body: Node[]; +} + +export interface ExpressionStatement extends Node { + type: TypeStrings.ExpressionStatement; + expression: Expression; +} + +export type Expression = Literal | Node; + +export interface Literal extends Node { + type: TypeStrings.Literal; + value: string | boolean | null | number | RegExp; +} + +export interface NumericLiteral extends Node { + type: TypeStrings.Literal; + value: number; +} + +export interface StringLiteral extends Node { + type: TypeStrings.Literal; + value: string; +} diff --git a/src/evaluate.ts b/src/evaluate.ts new file mode 100644 index 0000000..2ee6c19 --- /dev/null +++ b/src/evaluate.ts @@ -0,0 +1,34 @@ +import { Node } from './acorn-helpers/types'; +import { + isExpressionStatement, + isNumericLiteral, + isStringLiteral, + isProgram, +} from './acorn-helpers/isType'; + +import { + NormalCompletion, + CompletionRecord, +} from './internal-types/CompletionRecord'; + +import { NumberValue } from './language-types/NumberValue'; +import { StringValue } from './language-types/StringValue'; + +import { throwUnknownNode } from './throwUnknownNode'; + +export function Evaluate(node: Node): CompletionRecord { + if (isProgram(node)) { + const completions = node.body.map(tmp => Evaluate(tmp)); + return completions[completions.length - 1]; + } else if (isExpressionStatement(node)) { + return Evaluate(node.expression); + } else if (isNumericLiteral(node)) { + return NormalCompletion(new NumberValue(node.value)); + } else if (isStringLiteral(node)) { + return NormalCompletion(new StringValue(node.value)); + } else { + throwUnknownNode(node); + } + + return NormalCompletion(); +} diff --git a/src/internal-types/CompletionRecord.ts b/src/internal-types/CompletionRecord.ts new file mode 100644 index 0000000..f1c950b --- /dev/null +++ b/src/internal-types/CompletionRecord.ts @@ -0,0 +1,31 @@ +import { LanguageType } from '../language-types/allTypes'; + +export const TypeString = '[[Type]]'; +export const ValueString = '[[Value]]'; +export const TargetString = '[[Target]]'; + +export type TType = 'normal' | 'break' | 'continue' | 'return' | 'throw'; +export type TValue = LanguageType | undefined; +export type TTarget = string | undefined; + +export interface TCompletionRecord { + [TypeString]: TType; + [ValueString]: TValue; + [TargetString]: TTarget; +} + +export class CompletionRecord implements TCompletionRecord { + [TypeString]: TType; + [ValueString]: TValue; + [TargetString]: TTarget; + + constructor(type: TType, value?: TValue, target?: TTarget) { + this[TypeString] = type; + this[ValueString] = value; + this[TargetString] = target; + } +} + +export function NormalCompletion(value?: TValue) { + return new CompletionRecord('normal', value); +} diff --git a/src/language-types/NumberValue.ts b/src/language-types/NumberValue.ts new file mode 100644 index 0000000..66775c5 --- /dev/null +++ b/src/language-types/NumberValue.ts @@ -0,0 +1,16 @@ +import { NumberString } from './typeNames'; + +export interface TNumberValue { + type: typeof NumberString; + value: number; +} + +export class NumberValue implements TNumberValue { + type: typeof NumberString; + value: number; + + constructor(value: number) { + this.type = NumberString; + this.value = value; + } +} diff --git a/src/language-types/StringValue.ts b/src/language-types/StringValue.ts new file mode 100644 index 0000000..e76e036 --- /dev/null +++ b/src/language-types/StringValue.ts @@ -0,0 +1,16 @@ +import { StringString } from './typeNames'; + +export interface TStringValue { + type: typeof StringString; + value: string; +} + +export class StringValue implements TStringValue { + type: typeof StringString; + value: string; + + constructor(value: string) { + this.type = StringString; + this.value = value; + } +} diff --git a/src/language-types/allTypes.ts b/src/language-types/allTypes.ts new file mode 100644 index 0000000..50d4c59 --- /dev/null +++ b/src/language-types/allTypes.ts @@ -0,0 +1,4 @@ +import { TNumberValue } from './NumberValue'; +import { TStringValue } from './StringValue'; + +export type LanguageType = TNumberValue | TStringValue; diff --git a/src/language-types/typeNames.ts b/src/language-types/typeNames.ts new file mode 100644 index 0000000..c590473 --- /dev/null +++ b/src/language-types/typeNames.ts @@ -0,0 +1,3 @@ +export const NumberString = 'number'; + +export const StringString = 'string'; diff --git a/src/parse.ts b/src/parse.ts index 57d3f27..a1ad33c 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,5 +1,5 @@ import { parse } from 'acorn'; -export function parseScript(content: string) { +export function ParseScript(content: string) { return parse(content); } diff --git a/src/throwUnknownNode.ts b/src/throwUnknownNode.ts new file mode 100644 index 0000000..3192d4f --- /dev/null +++ b/src/throwUnknownNode.ts @@ -0,0 +1,12 @@ +import { Node } from './acorn-helpers/types'; + +class NotImplementedError extends TypeError {} + +export function throwUnknownNode(node: Node) { + throw new NotImplementedError( + `Unknown type: ${node.type} with text "${node.sourceFile?.slice( + node.start, + node.end + )}"` + ); +}