diff --git a/examples/strings.bl b/examples/strings.bl index 0397bda..56e9f8f 100644 --- a/examples/strings.bl +++ b/examples/strings.bl @@ -1,6 +1,6 @@ -fn main(): String { - let a = "asdf"; - return a ; +fn main(): i64 { + let user = User{id: 4}; + return user.instance_method(); } type User struct { diff --git a/packages/boringlang/src/commands/run.ts b/packages/boringlang/src/commands/run.ts index c0fce2e..cd3b4fc 100644 --- a/packages/boringlang/src/commands/run.ts +++ b/packages/boringlang/src/commands/run.ts @@ -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 { diff --git a/packages/boringlang/src/interpreter/builtins.ts b/packages/boringlang/src/interpreter/builtins.ts new file mode 100644 index 0000000..75091fe --- /dev/null +++ b/packages/boringlang/src/interpreter/builtins.ts @@ -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 = {}; + 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; +} diff --git a/packages/boringlang/src/interpreter/context.ts b/packages/boringlang/src/interpreter/context.ts new file mode 100644 index 0000000..5a48292 --- /dev/null +++ b/packages/boringlang/src/interpreter/context.ts @@ -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; +} + +export type Value = + | NumericValue + | BoolValue + | StringValue + | FunctionValue + | StructValue + | UnitValue; + +export interface Variable { + namedEntity: "Variable"; + value: Value; +} + +export interface EnvImpl { + trait: string | null; + functions: Record; +} + +export interface NamedType { + namedEntity: "NamedType"; + isA: "Scalar" | "Trait" | "Struct"; + fields: Record; + impls: EnvImpl[]; +} + +export interface Context { + environment: Record; + currentModule: Module; +} diff --git a/packages/boringlang/src/interpreter/index.ts b/packages/boringlang/src/interpreter/index.ts new file mode 100644 index 0000000..4d06b57 --- /dev/null +++ b/packages/boringlang/src/interpreter/index.ts @@ -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 = {}; + 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 }, + }; + }; +}