diff --git a/examples/structs.bl b/examples/structs.bl index 56e9f8f..3a2015b 100644 --- a/examples/structs.bl +++ b/examples/structs.bl @@ -1,6 +1,7 @@ fn main(): i64 { let user = User{id: 4}; - return user.instance_method(); + let result = user.instance_method() + User::instance_method(user); + return result; } type User struct { diff --git a/goal-examples/webapp/main.bl b/goal-examples/webapp/main.bl new file mode 100644 index 0000000..29a53fb --- /dev/null +++ b/goal-examples/webapp/main.bl @@ -0,0 +1,38 @@ +import net.http as http; +import logging as logging; +import json as json; + + +type ExampleResponse struct { + pub id: i32; + pub name: str; + pub email: str; +} + +type Router struct { + logger: logging::Logger; + + pub fn new(logger: logging::Logger): Router { + return Self{logger: logger}; + } + + pub fn get_user_data(self: Self, req: http.Request): IO[Result[http::Response, http::Error]] { + let response_data = ExampleResponse{ + id: 4, + name: "Andrew", + email: "andrew@boringlang.com", + }; + self.logger.info("getting user data")?; + return ok(http::Response::ok(json::dumps(response_data)?)); + } +} + +pub fn main(args: List[String], os: OS): IO[i32] { + let logger = logging::ConsoleLogger::new(os.console.stdout()); + let router = Router::new(logger); + let app = http::Router::new("").add_route("/myroute", router.get_user_data); + let http_server = http::Server::new(os.net(), "localhost", 8080, app); + let err = http_server.serve_forever()?; + logger.info("error serving: ", err)?; + return 1; +} diff --git a/goal-examples/webapp/models.bl b/goal-examples/webapp/models.bl new file mode 100644 index 0000000..9e662ff --- /dev/null +++ b/goal-examples/webapp/models.bl @@ -0,0 +1,33 @@ +import uuid as uuid; +import orm as orm; + + +#[derive(DeepCopy, Debug, PartialEq, DeriveEntityModel)] +#[orm::model(table_name = "user")] +pub type User struct { + #[orm::model(primary_key)] + pub id: uuid::UUID; + #[orm::model(unique, column_type = "VARCHAR(256)")] + pub email: String; + #[orm::model(nullable)] + pub password_hash: Option[String]; +} + +#[derive(DeepCopy, Debug, PartialEq, DeriveEntityModel)] +#[orm::model(table_name = "todo")] +pub type Todo struct { + #[orm::model(primary_key)] + pub id: uuid::UUID; + + #[orm::model(foreign_key = "user", on_delete="CASCADE")] + pub user_id: uuid::UUID; + + #[orm::model(column_type = "VARCHAR(256)")] + pub title: String; + + #[orm::model(column_type = "TEXT")] + pub description: String; + + pub created_at: DateTime; + pub updated_at: DateTime; +} \ No newline at end of file diff --git a/goal-examples/webapp/router.bl b/goal-examples/webapp/router.bl new file mode 100644 index 0000000..48c5b2c --- /dev/null +++ b/goal-examples/webapp/router.bl @@ -0,0 +1,45 @@ +import logging as logging; +import orm as orm; +import uuid as uuid; +import webapp.models as models; + +#[derive(DeepCopy, Debug, PartialEq)] +type TodoGetParams struct { + #[validate(format="short")] + pub id: uuid.UUID; +} + +#[derive(DeepCopy, Debug, PartialEq, Serialize)] +type TodoResponse struct { + #[validate(format="short")] + pub id: uuid.UUID; + #[validate(format="short")] + pub user_id: uuid.UUID; + #[validate(min_length=1, max_length=256)] + pub title: String; + #[validate(min_length=1, max_length=1000000)] + pub description: String; + pub created_at: DateTime; + pub updated_at: DateTime; +} + +impl Into for models.Todo { + fn into(self: Self): TodoResponse { + return TodoResponse{..self}; + } +} + +type Router struct { + logger: logging::Logger; + db: orm::DB; + + pub fn new(logger: logging::Logger, db: orm::DB): Router { + return Self{logger: logger, db: db}; + } + + pub fn get_todo(self: Self, req: http.Request[TodoGetParams, http::NoQuery, http::NoBody]): IO[Result[http::JsonResponse[TodoResponse], http::Error]] { + let id = req.params.id; + let instance = models::Todo::select().filter(models::Todo::Id::equals(id)).first(self.db)??; + return http::JsonResponse::ok(instance.into()); + } +} diff --git a/packages/boringlang/src/commands/run.ts b/packages/boringlang/src/commands/run.ts index cd3b4fc..5f64266 100644 --- a/packages/boringlang/src/commands/run.ts +++ b/packages/boringlang/src/commands/run.ts @@ -26,14 +26,24 @@ export const run = defineCommand({ if (match.succeeded()) { const adapter = semantics(match); const ast = adapter.toAST(); + // console.log(JSON.stringify(ast, null, 2)); new TraitChecker().withModule(ast); const aliasResolvedAst = new TypeAliasResolver().withModule(ast); const typeSystem = new TypeSystem(); const typeChecker = new TypeChecker(); const typeResolver = new TypeResolver(); + typeChecker.withModule(aliasResolvedAst, typeSystem); - typeSystem.solve(); + try { + typeSystem.solve(); + } catch (e) { + console.log(e); + console.log(JSON.stringify(typeSystem.result, null, 2)); + return; + } + 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)); diff --git a/packages/boringlang/src/interpreter/index.ts b/packages/boringlang/src/interpreter/index.ts index 4d06b57..106d4a8 100644 --- a/packages/boringlang/src/interpreter/index.ts +++ b/packages/boringlang/src/interpreter/index.ts @@ -7,13 +7,14 @@ import { LetStatement, Module, Operation, + Path, ReturnStatement, StructGetter, StructTypeDeclaration, TypeUsage, } from "../parse/ast"; import { contextFromModule } from "./builtins"; -import { Context, Value } from "./context"; +import { Context, NamedType, Value } from "./context"; interface ExpressionResultValue { resultType: "Value"; @@ -74,21 +75,21 @@ export class TreeWalkInterpreter { if (result.resultType === "Return") { return result; } - if (statement.source.expressionType == "VariableUsage") { + if (statement.source.type == "Identifier") { ctx.environment[statement.source.name.text] = { namedEntity: "Variable", value: result.value, }; } - if (statement.source.expressionType == "StructGetter") { - let source = this.withStructGetter(ctx, statement.source); + if (statement.source.type == "StructGetter") { + let source = this.withStructGetter(ctx, statement.source.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; + source.value.fields[statement.source.source.attribute.text] = result.value; } return { resultType: "Value", value: { value: "UnitValue" } }; }; @@ -163,12 +164,13 @@ export class TreeWalkInterpreter { 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 === "Path") { + // const variableValue = ctx.environment[expression.subExpression.name.text]; + // if (!variableValue || variableValue.namedEntity !== "Variable") { + // throw Error(`not found: ${expression.subExpression.name.text}`); + // } + const value = this.withPath(ctx, expression.subExpression); + return { resultType: "Value", value: value }; } if (expression.subExpression.expressionType === "IfExpression") { const condition = this.withExpression(ctx, expression.subExpression.condition); @@ -302,4 +304,42 @@ export class TreeWalkInterpreter { value: { value: "NumericValue", number: left.value.number / right.value.number }, }; }; + + withPath = (ctx: Context, path: Path): Value => { + if (path.value.type == "Identifier") { + const variableValue = ctx.environment[path.value.name.text]; + if (!variableValue || variableValue.namedEntity !== "Variable") { + throw Error(`not found: ${path.value.name.text}`); + } + return variableValue.value; + } + if (path.value.type == "Nested") { + return this.withPathItem(ctx, path) as Value; + } + throw Error(`Impossible path`); + }; + + withPathItem = (ctx: Context, path: Path): NamedType | Value => { + if (path.value.type == "Identifier") { + const envValue = ctx.environment[path.value.name.text]; + if (!envValue || envValue.namedEntity !== "NamedType") { + throw Error(`not found: ${path.value.name.text}`); + } + return envValue; + } + if (path.value.type == "Nested") { + const envValue = this.withPathItem(ctx, path.value.parent) as NamedType; + if (envValue.isA == "Trait") { + throw Error(`Cannot get function impl from raw trait`); + } + for (const impl of envValue.impls) { + for (const [name, method] of Object.entries(impl.functions)) { + if (name == path.value.name.text) { + return { value: "FunctionValue", partial: [], ref: method }; + } + } + } + } + throw Error(`Impossible path`); + }; } diff --git a/packages/boringlang/src/parse/ast.ts b/packages/boringlang/src/parse/ast.ts index a9beb40..ab9f7a9 100644 --- a/packages/boringlang/src/parse/ast.ts +++ b/packages/boringlang/src/parse/ast.ts @@ -57,6 +57,13 @@ export interface StructGetter { type: TypeUsage; } +export interface Path { + expressionType: "Path"; + value: + | { type: "Identifier"; name: Identifier } + | { type: "Nested"; parent: Path; name: Identifier }; +} + export interface Operation { expressionType: "Operation"; left: Expression; @@ -65,12 +72,6 @@ export interface Operation { type: TypeUsage; } -export interface VariableUsage { - expressionType: "VariableUsage"; - name: Identifier; - type: TypeUsage; -} - export interface IfExpression { expressionType: "IfExpression"; condition: Expression; @@ -88,8 +89,8 @@ export interface Expression { | LiteralString | LiteralStruct | FunctionCall - | VariableUsage | IfExpression + | Path | StructGetter | Block | Operation; @@ -110,7 +111,7 @@ export interface LetStatement { export interface AssignmentStatement { statementType: "AssignmentStatement"; - source: VariableUsage | StructGetter; + source: { type: "Identifier"; name: Identifier } | { type: "StructGetter"; source: StructGetter }; expression: Expression; } @@ -139,7 +140,7 @@ export interface Function { block: Block; } -export const functionToType = (fn: FunctionDeclaration): TypeUsage => { +export const functionToType = (fn: FunctionDeclaration): FunctionTypeUsage => { return { typeUsage: "FunctionTypeUsage", arguments: fn.arguments.map((arg) => arg.type), diff --git a/packages/boringlang/src/parse/grammar.ts b/packages/boringlang/src/parse/grammar.ts index a2e085a..233c73b 100644 --- a/packages/boringlang/src/parse/grammar.ts +++ b/packages/boringlang/src/parse/grammar.ts @@ -4,8 +4,8 @@ export const boringGrammar = ohm.grammar(String.raw` Boringlang { ReturnStatement = "return" Expression ";" LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";" - AssignmentStatement = VariableUsage "=" Expression ";" - | StructGetter "=" Expression ";" + AssignmentStatement = identifier "=" Expression ";" -- identifier + | StructGetter "=" Expression ";" -- getter ExpressionStatement = Expression ";" Statement = ExpressionStatement | LetStatement @@ -19,27 +19,33 @@ export const boringGrammar = ohm.grammar(String.raw` LiteralStructField = identifier ":" Expression LiteralStruct = identifier "{" ListOf "}" identifier = (letter | "_")+(letter | digit | "_")* - FunctionCall = Expression "(" ListOf ")" StructGetter = Expression "." identifier - VariableUsage = identifier IfExpression = "if" "(" Expression ")" Block ("else" Block)? - Term = LiteralInt + Path = Path "::" identifier -- nested + | identifier -- base + PrimaryExpression = LiteralInt | LiteralFloat | LiteralBool | LiteralString - | LiteralStruct - | IfExpression - | Block + | Path -- path | "(" Expression ")" -- parens - | VariableUsage - Factor = Factor "*" Term -- mult - | Factor "/" Term -- div - | Term - Expression = Expression "+" Factor -- plus - | Expression "-" Factor -- minus - | StructGetter - | FunctionCall - | Factor + StructExpression = LiteralStruct + | Block + | IfExpression + | PrimaryExpression + MemberExpression = MemberExpression "." identifier -- structGetter + | StructExpression + CallExpression = CallExpression "." identifier -- structGetter + | CallExpression "(" ListOf ")" -- functionCall + | MemberExpression "(" ListOf ")" -- memberFunctionCall + | MemberExpression + MultExpression = MultExpression "*" CallExpression -- mult + | MultExpression "/" CallExpression -- div + | CallExpression + AddExpression = Expression "+" MultExpression -- plus + | Expression "-" MultExpression -- minus + | MultExpression + Expression = AddExpression Block = "{" Statement* Expression? "}" NamedTypeUsage = identifier TypeUsage = NamedTypeUsage diff --git a/packages/boringlang/src/parse/semantics.ts b/packages/boringlang/src/parse/semantics.ts index 42987b6..1392565 100644 --- a/packages/boringlang/src/parse/semantics.ts +++ b/packages/boringlang/src/parse/semantics.ts @@ -22,6 +22,7 @@ import { NamedTypeUsage, newNever, Operation, + Path, ReturnStatement, Statement, StructField, @@ -31,7 +32,6 @@ import { TraitTypeDeclaration, TypeDeclaration, TypeUsage, - VariableUsage, } from "./ast"; import { boringGrammar } from "./grammar"; @@ -44,44 +44,60 @@ function nextUnknown() { export const semantics = boringGrammar.createSemantics(); semantics.addOperation("toAST", { - LiteralInt(a): LiteralInt { + LiteralInt(a): Expression { return { - expressionType: "LiteralInt", - value: this.sourceString, - type: { - typeUsage: "NamedTypeUsage", - name: { text: "i64", spanStart: 0, spanEnd: 0 }, + statementType: "Expression", + subExpression: { + expressionType: "LiteralInt", + value: this.sourceString, + type: { + typeUsage: "NamedTypeUsage", + name: { text: "i64", spanStart: 0, spanEnd: 0 }, + }, }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - LiteralFloat(_1, _2, _3): LiteralFloat { + LiteralFloat(_1, _2, _3): Expression { return { - expressionType: "LiteralFloat", - value: this.sourceString, - type: { - typeUsage: "NamedTypeUsage", - name: { text: "f64", spanStart: 0, spanEnd: 0 }, + statementType: "Expression", + subExpression: { + expressionType: "LiteralFloat", + value: this.sourceString, + type: { + typeUsage: "NamedTypeUsage", + name: { text: "f64", spanStart: 0, spanEnd: 0 }, + }, }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - LiteralBool(_): LiteralBool { + LiteralBool(_): Expression { return { - expressionType: "LiteralBool", - value: this.sourceString, - type: { - typeUsage: "NamedTypeUsage", - name: { text: "bool", spanStart: 0, spanEnd: 0 }, + statementType: "Expression", + subExpression: { + expressionType: "LiteralBool", + value: this.sourceString, + type: { + typeUsage: "NamedTypeUsage", + name: { text: "bool", spanStart: 0, spanEnd: 0 }, + }, }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - LiteralString(_1, text, _3): LiteralString { + LiteralString(_1, text, _3): Expression { return { - expressionType: "LiteralString", - value: text.sourceString, - type: { - typeUsage: "NamedTypeUsage", - name: { text: "String", spanStart: 0, spanEnd: 0 }, + statementType: "Expression", + subExpression: { + expressionType: "LiteralString", + value: text.sourceString, + type: { + typeUsage: "NamedTypeUsage", + name: { text: "String", spanStart: 0, spanEnd: 0 }, + }, }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, LiteralStructField(identifier, _2, expression): StructField { @@ -90,12 +106,16 @@ semantics.addOperation("toAST", { expression: expression.toAST(), }; }, - LiteralStruct(identifier, _2, fields, _4): LiteralStruct { + LiteralStruct(identifier, _2, fields, _4): Expression { return { - expressionType: "LiteralStruct", - name: identifier.toAST(), - fields: fields.asIteration().children.map((c) => c.toAST()), - type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() }, + statementType: "Expression", + subExpression: { + expressionType: "LiteralStruct", + name: identifier.toAST(), + fields: fields.asIteration().children.map((c) => c.toAST()), + type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, identifier(_1, _2): Identifier { @@ -105,95 +125,183 @@ semantics.addOperation("toAST", { spanEnd: this.source.endIdx, }; }, - FunctionCall(expression, _2, args, _4): FunctionCall { - const resolvedArgs = args.asIteration().children.map((c) => c.toAST()); - return { - expressionType: "FunctionCall", - source: expression.toAST(), - arguments: resolvedArgs, - type: { - typeUsage: "UnknownTypeUsage", - name: nextUnknown(), - }, - }; + PrimaryExpression(literal): Expression { + return literal.toAST(); }, - StructGetter(expression, _2, identifier): StructGetter { - return { - expressionType: "StructGetter", - source: expression.toAST(), - attribute: identifier.toAST(), - type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, - }; - }, - VariableUsage(identifier): VariableUsage { - return { - expressionType: "VariableUsage", - name: identifier.toAST(), - type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, - }; - }, - IfExpression(_1, _2, expression, _4, block, _6, elseBlock): IfExpression { - const eb = elseBlock.toAST(); - return { - expressionType: "IfExpression", - condition: expression.toAST(), - block: block.toAST(), - else: eb.length > 0 ? eb[0] : null, - type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, - }; - }, - Term(term): Expression { - return term.toAST(); - }, - Term_parens(_1, term, _3): Expression { - return term.toAST(); - }, - Factor(factor): Expression { - return factor.toAST(); - }, - Expression(expression): Expression { + PrimaryExpression_path(path): Expression { return { statementType: "Expression", - subExpression: expression.toAST(), + subExpression: path.toAST(), type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - Expression_plus(expression, _2, factor): Operation { + PrimaryExpression_parens(_1, term, _3): Expression { + return term.toAST(); + }, + StructExpression(expression): Expression { + return expression.toAST(); + }, + MemberExpression(expression): Expression { + return expression.toAST(); + }, + MemberExpression_structGetter(expression, _2, identifier): Expression { return { - expressionType: "Operation", - left: expression.toAST(), - op: "+", - right: factor.toAST(), + statementType: "Expression", + subExpression: { + expressionType: "StructGetter", + source: expression.toAST(), + attribute: identifier.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - Expression_minus(expression, _2, factor): Operation { + CallExpression(expression): Expression { + return expression.toAST(); + }, + CallExpression_structGetter(expression, _2, identifier): Expression { return { - expressionType: "Operation", - left: expression.toAST(), - op: "-", - right: factor.toAST(), + statementType: "Expression", + subExpression: { + expressionType: "StructGetter", + source: expression.toAST(), + attribute: identifier.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - Factor_mult(factor, _2, term): Operation { + CallExpression_functionCall(expression, _2, args, _4): Expression { + const resolvedArgs = args.asIteration().children.map((c) => c.toAST()); return { - expressionType: "Operation", - left: factor.toAST(), - op: "*", - right: term.toAST(), + statementType: "Expression", + subExpression: { + expressionType: "FunctionCall", + source: expression.toAST(), + arguments: resolvedArgs, + type: { + typeUsage: "UnknownTypeUsage", + name: nextUnknown(), + }, + }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - Factor_div(factor, _2, term): Operation { + CallExpression_memberFunctionCall(expression, _2, args, _4): Expression { + const resolvedArgs = args.asIteration().children.map((c) => c.toAST()); return { - expressionType: "Operation", - left: factor.toAST(), - op: "/", - right: term.toAST(), + statementType: "Expression", + subExpression: { + expressionType: "FunctionCall", + source: expression.toAST(), + arguments: resolvedArgs, + type: { + typeUsage: "UnknownTypeUsage", + name: nextUnknown(), + }, + }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, + StructGetter(expression, _2, identifier): Expression { + return { + statementType: "Expression", + subExpression: { + expressionType: "StructGetter", + source: expression.toAST(), + attribute: identifier.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + IfExpression(_1, _2, expression, _4, block, _6, elseBlock): Expression { + const eb = elseBlock.toAST(); + return { + statementType: "Expression", + subExpression: { + expressionType: "IfExpression", + condition: expression.toAST(), + block: block.toAST(), + else: eb.length > 0 ? eb[0] : null, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + Path_base(identifier): Path { + return { + expressionType: "Path", + value: { type: "Identifier", name: identifier.toAST() }, + }; + }, + Path_nested(basePath, _2, attrIdent): Path { + return { + expressionType: "Path", + value: { type: "Nested", parent: basePath.toAST(), name: attrIdent.toAST() }, + }; + }, + MultExpression(expression): Expression { + return expression.toAST(); + }, + MultExpression_mult(factor, _2, term): Expression { + return { + statementType: "Expression", + subExpression: { + expressionType: "Operation", + left: factor.toAST(), + op: "*", + right: term.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + MultExpression_div(factor, _2, term): Expression { + return { + statementType: "Expression", + subExpression: { + expressionType: "Operation", + left: factor.toAST(), + op: "/", + right: term.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + AddExpression(expression): Expression { + return expression.toAST(); + }, + AddExpression_plus(expression, _2, factor): Expression { + return { + statementType: "Expression", + subExpression: { + expressionType: "Operation", + left: expression.toAST(), + op: "+", + right: factor.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + AddExpression_minus(expression, _2, factor): Expression { + return { + statementType: "Expression", + subExpression: { + expressionType: "Operation", + left: expression.toAST(), + op: "-", + right: factor.toAST(), + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }, + type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, + }; + }, + Expression(expression): Expression { + return expression.toAST(); + }, Statement(statement): Statement { return statement.toAST(); }, @@ -212,10 +320,17 @@ semantics.addOperation("toAST", { type: tu.length > 0 ? tu[0] : { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, }; }, - AssignmentStatement(variable, _2, expression, _4): AssignmentStatement { + AssignmentStatement_identifier(variable, _2, expression, _4): AssignmentStatement { return { statementType: "AssignmentStatement", - source: variable.toAST(), + source: { type: "Identifier", name: variable.toAST() }, + expression: expression.toAST(), + }; + }, + AssignmentStatement_getter(variable, _2, expression, _4): AssignmentStatement { + return { + statementType: "AssignmentStatement", + source: { type: "StructGetter", source: variable.toAST() }, expression: expression.toAST(), }; }, diff --git a/packages/boringlang/src/types/context.ts b/packages/boringlang/src/types/context.ts index fe3a15f..0fb2717 100644 --- a/packages/boringlang/src/types/context.ts +++ b/packages/boringlang/src/types/context.ts @@ -1,8 +1,8 @@ -import { NamedTypeUsage, TypeUsage } from "../parse/ast"; +import { FunctionTypeUsage, NamedTypeUsage, TypeUsage } from "../parse/ast"; interface EnvImpl { trait: string | null; - functions: Record; + functions: Record; } interface NamedType { @@ -35,11 +35,20 @@ export function getAttr(ctx: Context, name: string, field: string) { let results: TypeUsage[] = []; for (const impl of struct.impls) { if (impl.functions[field]) { - results.push(impl.functions[field]); + const fn = impl.functions[field]; + + if ( + fn.arguments.length && + fn.arguments[0].typeUsage == "NamedTypeUsage" && + fn.arguments[0].name.text === name + ) { + const fnCopy = deepCopy(fn); + fnCopy.arguments = fnCopy.arguments.slice(1); + results.push(fnCopy); + } } } if (results.length === 0) { - console.log(JSON.stringify(struct, null, 2)); throw Error(`${name} has no attribue ${field}`); } if (results.length > 1) { diff --git a/packages/boringlang/src/types/type_alias_resolution.ts b/packages/boringlang/src/types/type_alias_resolution.ts index 8a66905..4cc752f 100644 --- a/packages/boringlang/src/types/type_alias_resolution.ts +++ b/packages/boringlang/src/types/type_alias_resolution.ts @@ -16,7 +16,7 @@ import { StructTypeDeclaration, TraitTypeDeclaration, TypeUsage, - VariableUsage, + Path, } from "../parse/ast"; import { deepCopy, replaceType } from "./context"; @@ -121,11 +121,14 @@ export class TypeAliasResolver { withAssignmentStatement = (ctx: AliasContext, statement: AssignmentStatement) => { const result = deepCopy(statement); - if (statement.source.expressionType == "StructGetter") { - result.source = this.withStructGetter(ctx, statement.source); + if (statement.source.type == "StructGetter") { + result.source = { + type: "StructGetter", + source: this.withStructGetter(ctx, statement.source.source), + }; } - if (statement.source.expressionType == "VariableUsage") { - result.source = this.withVariableUsage(ctx, statement.source); + if (statement.source.type == "Identifier") { + result.source = deepCopy(result.source); } result.expression = this.withExpression(ctx, statement.expression); return result; @@ -151,8 +154,8 @@ export class TypeAliasResolver { if (expression.subExpression.expressionType === "FunctionCall") { result.subExpression = this.withFunctionCall(ctx, expression.subExpression); } - if (expression.subExpression.expressionType === "VariableUsage") { - result.subExpression = this.withVariableUsage(ctx, expression.subExpression); + if (expression.subExpression.expressionType === "Path") { + result.subExpression = deepCopy(expression.subExpression); } if (expression.subExpression.expressionType === "IfExpression") { result.subExpression = this.withIfExpression(ctx, expression.subExpression); @@ -196,14 +199,6 @@ export class TypeAliasResolver { return result; }; - withVariableUsage = (ctx: AliasContext, variableUsage: VariableUsage) => { - const result = deepCopy(variableUsage); - for (const [oldName, newType] of Object.entries(ctx.environment)) { - result.type = replaceType(oldName, newType, variableUsage.type); - } - return result; - }; - withIfExpression = (ctx: AliasContext, ifExpression: IfExpression) => { const result = deepCopy(ifExpression); result.condition = this.withExpression(ctx, ifExpression.condition); diff --git a/packages/boringlang/src/types/type_checker.ts b/packages/boringlang/src/types/type_checker.ts index fbdf1d6..b98af63 100644 --- a/packages/boringlang/src/types/type_checker.ts +++ b/packages/boringlang/src/types/type_checker.ts @@ -19,7 +19,9 @@ import { StructTypeDeclaration, TraitTypeDeclaration, TypeUsage, - VariableUsage, + Path, + Identifier, + FunctionTypeUsage, } from "../parse/ast"; import { newContext } from "./builtins"; import { Context, deepCopy, typeExists } from "./context"; @@ -45,17 +47,9 @@ export class TypeChecker { if (ctx.environment[item.name.text]) { throw Error("Duplicate name of trait"); } - const functions: Record = {}; + const functions: Record = {}; for (const fn of item.functions) { - if ( - fn.arguments.length && - fn.arguments[0].type.typeUsage == "NamedTypeUsage" && - fn.arguments[0].type.name.text === item.name.text - ) { - const fnCopy = deepCopy(fn); - fnCopy.arguments = fnCopy.arguments.slice(1); - functions[fn.name.text] = functionToType(fnCopy); - } + functions[fn.name.text] = functionToType(fn); } ctx.environment[item.name.text] = { namedEntity: "NamedType", @@ -86,17 +80,9 @@ export class TypeChecker { if (!struct || struct.namedEntity !== "NamedType" || struct.isA !== "Struct") { throw Error("Impl for non-struct"); } - const functions: Record = {}; + 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 - ) { - const fnCopy = deepCopy(fn.declaration); - fnCopy.arguments = fnCopy.arguments.slice(1); - functions[fn.declaration.name.text] = functionToType(fnCopy); - } + functions[fn.declaration.name.text] = functionToType(fn.declaration); } struct.impls.push({ trait: item.trait?.name.text ?? null, @@ -208,18 +194,25 @@ export class TypeChecker { statement: AssignmentStatement, typeSystem: TypeSystem, ) => { - if (statement.source.expressionType == "StructGetter") { - this.withStructGetter(ctx, statement.source, typeSystem); + if (statement.source.type == "StructGetter") { + this.withStructGetter(ctx, statement.source.source, typeSystem); + typeSystem.compare({ + left: statement.source.source.type, + operation: { operation: "equals" }, + right: statement.expression.type, + }); } - if (statement.source.expressionType == "VariableUsage") { - this.withVariableUsage(ctx, statement.source, typeSystem); + if (statement.source.type == "Identifier") { + typeSystem.compare({ + left: this.withPath(ctx, { + expressionType: "Path", + value: { type: "Identifier", name: statement.source.name }, + }), + operation: { operation: "equals" }, + right: statement.expression.type, + }); } this.withExpression(ctx, statement.expression, typeSystem); - typeSystem.compare({ - left: statement.source.type, - operation: { operation: "equals" }, - right: statement.expression.type, - }); }; withLetStatement = (ctx: Context, statement: LetStatement, typeSystem: TypeSystem) => { @@ -291,12 +284,11 @@ export class TypeChecker { right: expression.subExpression.type, }); } - if (expression.subExpression.expressionType === "VariableUsage") { - this.withVariableUsage(ctx, expression.subExpression, typeSystem); + if (expression.subExpression.expressionType === "Path") { typeSystem.compare({ left: expression.type, operation: { operation: "equals" }, - right: expression.subExpression.type, + right: this.withPath(ctx, expression.subExpression), }); } if (expression.subExpression.expressionType === "IfExpression") { @@ -377,16 +369,41 @@ export class TypeChecker { } }; - withVariableUsage = (ctx: Context, usage: VariableUsage, typeSystem: TypeSystem) => { - const variable = ctx.environment[usage.name.text]; - if (!variable || variable.namedEntity === "NamedType") { - throw new Error(`${usage.name.text} not found.`); + withPath = (ctx: Context, path: Path): TypeUsage => { + const pathList: Identifier[] = []; + while (path.value.type == "Nested") { + pathList.unshift(path.value.name); + path = path.value.parent; } - typeSystem.compare({ - left: variable.type, - operation: { operation: "equals" }, - right: usage.type, - }); + pathList.unshift(path.value.name); + + if (pathList.length === 0 || pathList.length > 2) { + throw new Error(`Namespaces not yet supported`); + } + if (pathList.length === 1) { + const variable = ctx.environment[pathList[0].text]; + if (!variable || variable.namedEntity === "NamedType") { + throw new Error(`${pathList[0].text} not found.`); + } + return variable.type; + } + + let struct = ctx.environment[pathList[0].text]; + if (!struct) { + throw new Error(`Unknown ${pathList[0].text}`); + } + if (struct.namedEntity == "Variable") { + throw new Error(`Cannot "::" variable ${pathList[0].text}`); + } + if (struct.fields[pathList[1].text]) { + return struct.fields[pathList[1].text]; + } + for (const impl of struct.impls) { + if (impl.functions[pathList[1].text]) { + return impl.functions[pathList[1].text]; + } + } + throw new Error(`Could not find attr ${pathList[1].text}`); }; withIfExpression = (ctx: Context, ifExpression: IfExpression, typeSystem: TypeSystem) => { diff --git a/packages/boringlang/src/types/type_resolver.ts b/packages/boringlang/src/types/type_resolver.ts index b160844..1888564 100644 --- a/packages/boringlang/src/types/type_resolver.ts +++ b/packages/boringlang/src/types/type_resolver.ts @@ -19,7 +19,7 @@ import { StructTypeDeclaration, TraitTypeDeclaration, TypeUsage, - VariableUsage, + Path, } from "../parse/ast"; import { newContext } from "./builtins"; import { Context, deepCopy, typeExists } from "./context"; @@ -109,11 +109,11 @@ export class TypeResolver { withAssignmentStatement = (statement: AssignmentStatement, typeSystem: TypeSystem) => { const result = deepCopy(statement); - if (statement.source.expressionType == "StructGetter") { - result.source = this.withStructGetter(statement.source, typeSystem); - } - if (statement.source.expressionType == "VariableUsage") { - result.source = this.withVariableUsage(statement.source, typeSystem); + if (statement.source.type == "StructGetter") { + result.source = { + type: "StructGetter", + source: this.withStructGetter(statement.source.source, typeSystem), + }; } result.expression = this.withExpression(statement.expression, typeSystem); return result; @@ -142,8 +142,8 @@ export class TypeResolver { if (expression.subExpression.expressionType === "FunctionCall") { result.subExpression = this.withFunctionCall(expression.subExpression, typeSystem); } - if (expression.subExpression.expressionType === "VariableUsage") { - result.subExpression = this.withVariableUsage(expression.subExpression, typeSystem); + if (expression.subExpression.expressionType === "Path") { + // paths do not have types } if (expression.subExpression.expressionType === "IfExpression") { result.subExpression = this.withIfExpression(expression.subExpression, typeSystem); @@ -179,12 +179,6 @@ export class TypeResolver { return result; }; - withVariableUsage = (usage: VariableUsage, typeSystem: TypeSystem) => { - const result = deepCopy(usage); - result.type = typeSystem.resolveType(usage.type); - return result; - }; - withIfExpression = (ifExpression: IfExpression, typeSystem: TypeSystem) => { const result = deepCopy(ifExpression); result.type = typeSystem.resolveType(ifExpression.type); diff --git a/packages/boringlang/src/types/type_system.ts b/packages/boringlang/src/types/type_system.ts index 45c53d4..98f3fcd 100644 --- a/packages/boringlang/src/types/type_system.ts +++ b/packages/boringlang/src/types/type_system.ts @@ -4,7 +4,9 @@ import { Context, deepCopy, getAttr } from "./context"; export const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => { if (typeA.typeUsage !== typeB.typeUsage) { - throw Error(`Mismatched types: ${typeA.typeUsage} ${typeB.typeUsage}`); + throw Error( + `Mismatched types: ${JSON.stringify(typeA, null, 2)} ${JSON.stringify(typeB, null, 2)}`, + ); } if (typeA.typeUsage == "NamedTypeUsage" && typeB.typeUsage == "NamedTypeUsage") { if (typeB.name.text === "Never") {