added interpreter
This commit is contained in:
@@ -6,6 +6,7 @@ import { TypeAliasResolver } from "../types/type_alias_resolution";
|
||||
import { TypeSystem } from "../types/type_system";
|
||||
import { TypeChecker } from "../types/type_checker";
|
||||
import { TypeResolver } from "../types/type_resolver";
|
||||
import { TreeWalkInterpreter } from "../interpreter";
|
||||
|
||||
export const run = defineCommand({
|
||||
name: "run",
|
||||
@@ -33,7 +34,9 @@ export const run = defineCommand({
|
||||
typeChecker.withModule(aliasResolvedAst, typeSystem);
|
||||
typeSystem.solve();
|
||||
const typeResolvedAst = typeResolver.withModule(aliasResolvedAst, typeSystem);
|
||||
console.log(JSON.stringify(typeResolvedAst, null, 2));
|
||||
const interpreter = new TreeWalkInterpreter();
|
||||
const result = interpreter.withModule(typeResolvedAst);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
// console.log(JSON.stringify(aliasResolvedAst, null, 2));
|
||||
} else {
|
||||
|
||||
92
packages/boringlang/src/interpreter/builtins.ts
Normal file
92
packages/boringlang/src/interpreter/builtins.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Module } from "../parse/ast";
|
||||
import { Context, FunctionRef } from "./context";
|
||||
|
||||
export function contextFromModule(module: Module): Context {
|
||||
const ctx: Context = {
|
||||
environment: {},
|
||||
currentModule: module,
|
||||
};
|
||||
ctx.environment["i8"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["i16"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["i32"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["i64"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["f8"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["f16"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["f32"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["f64"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["String"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["Void"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
ctx.environment["Never"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
|
||||
|
||||
// add functions, structs, and traits to the context
|
||||
for (const item of module.items) {
|
||||
if (item.moduleItem === "StructTypeDeclaration") {
|
||||
ctx.environment[item.name.text] = {
|
||||
namedEntity: "NamedType",
|
||||
isA: "Struct",
|
||||
fields: Object.fromEntries(item.fields.map((field) => [field.name.text, field.type])),
|
||||
impls: [],
|
||||
};
|
||||
}
|
||||
if (item.moduleItem === "TraitTypeDeclaration") {
|
||||
ctx.environment[item.name.text] = {
|
||||
namedEntity: "NamedType",
|
||||
isA: "Trait",
|
||||
fields: {},
|
||||
impls: [
|
||||
{
|
||||
trait: item.name.text,
|
||||
functions: Object.fromEntries(
|
||||
item.functions.map((fn) => {
|
||||
return [
|
||||
fn.name,
|
||||
{
|
||||
functionType: "UserFunction",
|
||||
function: fn,
|
||||
},
|
||||
];
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (item.moduleItem === "Function") {
|
||||
ctx.environment[item.declaration.name.text] = {
|
||||
namedEntity: "Variable",
|
||||
value: {
|
||||
value: "FunctionValue",
|
||||
partial: [],
|
||||
ref: {
|
||||
functionType: "UserFunction",
|
||||
function: item,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
// now that structs and traits are added, add impls
|
||||
for (const item of module.items) {
|
||||
if (item.moduleItem === "Impl") {
|
||||
const struct = ctx.environment[item.struct.name.text];
|
||||
if (!struct || struct.namedEntity !== "NamedType" || struct.isA !== "Struct") {
|
||||
throw Error("Impl for non-struct");
|
||||
}
|
||||
const functions: Record<string, FunctionRef> = {};
|
||||
for (const fn of item.functions) {
|
||||
if (
|
||||
fn.declaration.arguments.length &&
|
||||
fn.declaration.arguments[0].type.typeUsage == "NamedTypeUsage" &&
|
||||
fn.declaration.arguments[0].type.name.text === item.struct.name.text
|
||||
) {
|
||||
functions[fn.declaration.name.text] = { functionType: "UserFunction", function: fn };
|
||||
}
|
||||
}
|
||||
struct.impls.push({
|
||||
trait: item.trait?.name.text ?? null,
|
||||
functions: functions,
|
||||
});
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
74
packages/boringlang/src/interpreter/context.ts
Normal file
74
packages/boringlang/src/interpreter/context.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { TypeUsage, Function, Module } from "../parse/ast";
|
||||
|
||||
export interface NumericValue {
|
||||
value: "NumericValue";
|
||||
number: number;
|
||||
}
|
||||
|
||||
export interface BoolValue {
|
||||
value: "BoolValue";
|
||||
bool: boolean;
|
||||
}
|
||||
|
||||
export interface StringValue {
|
||||
value: "StringValue";
|
||||
string: string;
|
||||
}
|
||||
|
||||
export interface UserFunction {
|
||||
functionType: "UserFunction";
|
||||
function: Function;
|
||||
}
|
||||
|
||||
export interface BuiltinFunction {
|
||||
functionType: "BuiltinFunction";
|
||||
function: (value: Value[]) => Value;
|
||||
}
|
||||
|
||||
export type FunctionRef = UserFunction | BuiltinFunction;
|
||||
|
||||
export interface FunctionValue {
|
||||
value: "FunctionValue";
|
||||
partial: Value[];
|
||||
ref: FunctionRef;
|
||||
}
|
||||
|
||||
export interface UnitValue {
|
||||
value: "UnitValue";
|
||||
}
|
||||
|
||||
export interface StructValue {
|
||||
value: "StructValue";
|
||||
source: NamedType;
|
||||
fields: Record<string, Value>;
|
||||
}
|
||||
|
||||
export type Value =
|
||||
| NumericValue
|
||||
| BoolValue
|
||||
| StringValue
|
||||
| FunctionValue
|
||||
| StructValue
|
||||
| UnitValue;
|
||||
|
||||
export interface Variable {
|
||||
namedEntity: "Variable";
|
||||
value: Value;
|
||||
}
|
||||
|
||||
export interface EnvImpl {
|
||||
trait: string | null;
|
||||
functions: Record<string, FunctionRef>;
|
||||
}
|
||||
|
||||
export interface NamedType {
|
||||
namedEntity: "NamedType";
|
||||
isA: "Scalar" | "Trait" | "Struct";
|
||||
fields: Record<string, TypeUsage>;
|
||||
impls: EnvImpl[];
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
environment: Record<string, NamedType | Variable>;
|
||||
currentModule: Module;
|
||||
}
|
||||
305
packages/boringlang/src/interpreter/index.ts
Normal file
305
packages/boringlang/src/interpreter/index.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import {
|
||||
AssignmentStatement,
|
||||
Block,
|
||||
Expression,
|
||||
Function,
|
||||
FunctionCall,
|
||||
LetStatement,
|
||||
Module,
|
||||
Operation,
|
||||
ReturnStatement,
|
||||
StructGetter,
|
||||
StructTypeDeclaration,
|
||||
TypeUsage,
|
||||
} from "../parse/ast";
|
||||
import { contextFromModule } from "./builtins";
|
||||
import { Context, Value } from "./context";
|
||||
|
||||
interface ExpressionResultValue {
|
||||
resultType: "Value";
|
||||
value: Value;
|
||||
}
|
||||
|
||||
interface ExpressionResultReturn {
|
||||
resultType: "Return";
|
||||
value: Value;
|
||||
}
|
||||
|
||||
type ExpressionResult = ExpressionResultValue | ExpressionResultReturn;
|
||||
|
||||
export class TreeWalkInterpreter {
|
||||
withModule = (module: Module) => {
|
||||
const ctx = contextFromModule(module);
|
||||
const main = ctx.environment["main"];
|
||||
if (
|
||||
!main ||
|
||||
!(main.namedEntity === "Variable") ||
|
||||
!(main.value.value === "FunctionValue") ||
|
||||
!(main.value.ref.functionType === "UserFunction")
|
||||
) {
|
||||
throw Error("No main function");
|
||||
}
|
||||
return this.withFunction(ctx, main.value.ref.function);
|
||||
};
|
||||
|
||||
withFunction = (ctx: Context, fn: Function): Value => {
|
||||
let result = this.withBlock(ctx, fn.block);
|
||||
return result.value;
|
||||
};
|
||||
|
||||
withBlock = (ctx: Context, block: Block): ExpressionResult => {
|
||||
let last: ExpressionResult = { resultType: "Value", value: { value: "UnitValue" } };
|
||||
for (const statement of block.statements) {
|
||||
if (statement.statementType === "AssignmentStatement") {
|
||||
last = this.withAssignmentStatement(ctx, statement);
|
||||
}
|
||||
if (statement.statementType === "LetStatement") {
|
||||
last = this.withLetStatement(ctx, statement);
|
||||
}
|
||||
if (statement.statementType === "Expression") {
|
||||
last = this.withExpression(ctx, statement);
|
||||
}
|
||||
if (statement.statementType === "ReturnStatement") {
|
||||
last = this.withReturnStatement(ctx, statement);
|
||||
}
|
||||
if (last.resultType === "Return") {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
};
|
||||
|
||||
withAssignmentStatement = (ctx: Context, statement: AssignmentStatement): ExpressionResult => {
|
||||
let result = this.withExpression(ctx, statement.expression);
|
||||
if (result.resultType === "Return") {
|
||||
return result;
|
||||
}
|
||||
if (statement.source.expressionType == "VariableUsage") {
|
||||
ctx.environment[statement.source.name.text] = {
|
||||
namedEntity: "Variable",
|
||||
value: result.value,
|
||||
};
|
||||
}
|
||||
if (statement.source.expressionType == "StructGetter") {
|
||||
let source = this.withStructGetter(ctx, statement.source);
|
||||
if (source.resultType === "Return") {
|
||||
return source;
|
||||
}
|
||||
if (source.value.value !== "StructValue") {
|
||||
throw Error("set attr on nonstruct, should never happen due to type system");
|
||||
}
|
||||
source.value.fields[statement.source.attribute.text] = result.value;
|
||||
}
|
||||
return { resultType: "Value", value: { value: "UnitValue" } };
|
||||
};
|
||||
|
||||
withLetStatement = (ctx: Context, statement: LetStatement): ExpressionResult => {
|
||||
let result = this.withExpression(ctx, statement.expression);
|
||||
if (result.resultType === "Return") {
|
||||
return result;
|
||||
}
|
||||
ctx.environment[statement.variableName.text] = {
|
||||
namedEntity: "Variable",
|
||||
value: result.value,
|
||||
};
|
||||
return { resultType: "Value", value: { value: "UnitValue" } };
|
||||
};
|
||||
|
||||
withReturnStatement = (ctx: Context, statement: ReturnStatement): ExpressionResult => {
|
||||
let result = this.withExpression(ctx, statement.source);
|
||||
if (result.resultType === "Return") {
|
||||
return result;
|
||||
}
|
||||
return { resultType: "Return", value: result.value };
|
||||
};
|
||||
|
||||
withExpression = (ctx: Context, expression: Expression): ExpressionResult => {
|
||||
if (expression.subExpression.expressionType === "LiteralInt") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: parseInt(expression.subExpression.value) },
|
||||
};
|
||||
}
|
||||
if (expression.subExpression.expressionType === "LiteralFloat") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: parseFloat(expression.subExpression.value) },
|
||||
};
|
||||
}
|
||||
if (expression.subExpression.expressionType === "LiteralString") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "StringValue", string: expression.subExpression.value },
|
||||
};
|
||||
}
|
||||
if (expression.subExpression.expressionType === "LiteralBool") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "BoolValue", bool: expression.subExpression.value === "true" },
|
||||
};
|
||||
}
|
||||
if (expression.subExpression.expressionType === "LiteralStruct") {
|
||||
const def = ctx.environment[expression.subExpression.name.text];
|
||||
if (def.namedEntity !== "NamedType") {
|
||||
throw Error("Not a struct");
|
||||
}
|
||||
const fields: Record<string, Value> = {};
|
||||
for (const field of expression.subExpression.fields) {
|
||||
const fieldResult = this.withExpression(ctx, field.expression);
|
||||
if (fieldResult.resultType === "Return") {
|
||||
return fieldResult;
|
||||
}
|
||||
fields[field.name.text] = fieldResult.value;
|
||||
}
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: {
|
||||
value: "StructValue",
|
||||
source: def,
|
||||
fields: fields,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (expression.subExpression.expressionType === "FunctionCall") {
|
||||
return this.withFunctionCall(ctx, expression.subExpression);
|
||||
}
|
||||
if (expression.subExpression.expressionType === "VariableUsage") {
|
||||
const variableValue = ctx.environment[expression.subExpression.name.text];
|
||||
if (!variableValue || variableValue.namedEntity !== "Variable") {
|
||||
throw Error(`not found: ${expression.subExpression.name.text}`);
|
||||
}
|
||||
return { resultType: "Value", value: variableValue.value };
|
||||
}
|
||||
if (expression.subExpression.expressionType === "IfExpression") {
|
||||
const condition = this.withExpression(ctx, expression.subExpression.condition);
|
||||
if (condition.resultType === "Return") {
|
||||
return condition;
|
||||
}
|
||||
if (condition.value.value === "BoolValue" && condition.value.bool === true) {
|
||||
return this.withBlock(ctx, expression.subExpression.block);
|
||||
} else {
|
||||
if (expression.subExpression.else) {
|
||||
return this.withBlock(ctx, expression.subExpression.else);
|
||||
} else {
|
||||
return { resultType: "Value", value: { value: "UnitValue" } };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression.subExpression.expressionType === "StructGetter") {
|
||||
return this.withStructGetter(ctx, expression.subExpression);
|
||||
}
|
||||
if (expression.subExpression.expressionType === "Block") {
|
||||
return this.withBlock(ctx, expression.subExpression);
|
||||
}
|
||||
if (expression.subExpression.expressionType === "Operation") {
|
||||
return this.withOperation(ctx, expression.subExpression);
|
||||
}
|
||||
// not actually possible, but makes the type system happy
|
||||
return { resultType: "Value", value: { value: "UnitValue" } };
|
||||
};
|
||||
|
||||
withFunctionCall = (ctx: Context, fnCall: FunctionCall): ExpressionResult => {
|
||||
const source = this.withExpression(ctx, fnCall.source);
|
||||
if (source.resultType === "Return") {
|
||||
return source;
|
||||
}
|
||||
const argValues: Value[] = [];
|
||||
for (const arg of fnCall.arguments) {
|
||||
const argValue = this.withExpression(ctx, arg);
|
||||
if (argValue.resultType === "Return") {
|
||||
return argValue;
|
||||
}
|
||||
argValues.push(argValue.value);
|
||||
}
|
||||
if (source.value.value !== "FunctionValue") {
|
||||
throw Error("type error: function call source must be a function");
|
||||
}
|
||||
if (source.value.ref.functionType === "UserFunction") {
|
||||
const fn = source.value.partial;
|
||||
const fnCtx = contextFromModule(ctx.currentModule);
|
||||
let i = 0;
|
||||
for (const arg of source.value.partial) {
|
||||
fnCtx.environment[source.value.ref.function.declaration.arguments[i].name.text] = {
|
||||
namedEntity: "Variable",
|
||||
value: arg,
|
||||
};
|
||||
i = i + 1;
|
||||
}
|
||||
for (const arg of argValues) {
|
||||
fnCtx.environment[source.value.ref.function.declaration.arguments[i].name.text] = {
|
||||
namedEntity: "Variable",
|
||||
value: arg,
|
||||
};
|
||||
i = i + 1;
|
||||
}
|
||||
return { resultType: "Value", value: this.withFunction(fnCtx, source.value.ref.function) };
|
||||
}
|
||||
// builtin
|
||||
let allValues = source.value.partial.concat(argValues);
|
||||
return { resultType: "Value", value: source.value.ref.function(allValues) };
|
||||
};
|
||||
|
||||
withStructGetter = (ctx: Context, structGetter: StructGetter): ExpressionResult => {
|
||||
const source = this.withExpression(ctx, structGetter.source);
|
||||
if (source.resultType === "Return") {
|
||||
return source;
|
||||
}
|
||||
if (source.value.value !== "StructValue") {
|
||||
throw Error("get attr of non-struct");
|
||||
}
|
||||
if (source.value.fields[structGetter.attribute.text]) {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: source.value.fields[structGetter.attribute.text],
|
||||
};
|
||||
}
|
||||
for (const impl of source.value.source.impls) {
|
||||
for (const [name, method] of Object.entries(impl.functions)) {
|
||||
if (name === structGetter.attribute.text) {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "FunctionValue", partial: [source.value], ref: method },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// not actually possible, but makes the type system happy
|
||||
return { resultType: "Value", value: { value: "UnitValue" } };
|
||||
};
|
||||
|
||||
withOperation = (ctx: Context, op: Operation): ExpressionResult => {
|
||||
const left = this.withExpression(ctx, op.left);
|
||||
if (left.resultType === "Return") {
|
||||
return left;
|
||||
}
|
||||
const right = this.withExpression(ctx, op.left);
|
||||
if (right.resultType === "Return") {
|
||||
return right;
|
||||
}
|
||||
if (left.value.value !== "NumericValue" || right.value.value !== "NumericValue") {
|
||||
throw Error("Operation on a Nan");
|
||||
}
|
||||
if (op.op === "+") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: left.value.number + right.value.number },
|
||||
};
|
||||
}
|
||||
if (op.op === "-") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: left.value.number - right.value.number },
|
||||
};
|
||||
}
|
||||
if (op.op === "*") {
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: left.value.number * right.value.number },
|
||||
};
|
||||
}
|
||||
return {
|
||||
resultType: "Value",
|
||||
value: { value: "NumericValue", number: left.value.number / right.value.number },
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user