add trait checking

This commit is contained in:
2025-08-19 21:54:06 -06:00
parent d370fb44a2
commit 90381840af
6 changed files with 109 additions and 14 deletions

View File

@@ -1,6 +1,7 @@
import { defineCommand } from "@bunli/core";
import { boringGrammar } from "../parse/grammar";
import { semantics } from "../parse/semantics";
import TraitChecker from "../types/trait_checker";
export const run = defineCommand({
name: "run",
@@ -20,6 +21,8 @@ export const run = defineCommand({
if (match.succeeded()) {
const adapter = semantics(match);
const ast = adapter.toAST();
new TraitChecker().withModule(ast);
console.log(JSON.stringify(ast, null, 2));
} else {
console.log(match.message);

View File

@@ -3,7 +3,7 @@ import * as ohm from "ohm-js";
export const boringGrammar = ohm.grammar(String.raw`
Boringlang {
ReturnStatement = "return" Expression ";"
LetStatement = "let" Identifier (":" TypeUsage)? "=" Expression ";"
LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";"
AssignmentStatement = VariableUsage "=" Expression ";"
| StructGetter "=" Expression ";"
ExpressionStatement = Expression ";"
@@ -16,12 +16,12 @@ export const boringGrammar = ohm.grammar(String.raw`
LiteralBool = "true" | "false"
LiteralString = "\"" (~"\"" any)* "\""
| "'" (~"'" any)* "'"
LiteralStructField = Identifier ":" Expression
LiteralStruct = Identifier "{" ListOf<LiteralStructField, ","> "}"
Identifier = (letter | "_")+ (letter | digit | "_")*
LiteralStructField = identifier ":" Expression
LiteralStruct = identifier "{" ListOf<LiteralStructField, ","> "}"
identifier = (letter | "_")+(letter | digit | "_")*
FunctionCall = Expression "(" ListOf<Expression, ","> ")"
StructGetter = Expression "." Identifier
VariableUsage = Identifier
StructGetter = Expression "." identifier
VariableUsage = identifier
IfExpression = "if" "(" Expression ")" Block ("else" Block)?
Term = LiteralInt
| LiteralFloat
@@ -41,16 +41,16 @@ export const boringGrammar = ohm.grammar(String.raw`
| FunctionCall
| Factor
Block = "{" Statement* Expression? "}"
NamedTypeUsage = Identifier
NamedTypeUsage = identifier
TypeUsage = NamedTypeUsage
| "fn" "(" ListOf<TypeUsage, ","> ")" ":" TypeUsage -- function_tu
FunctionArgument = Identifier ":" TypeUsage
FunctionDeclaration = "fn" Identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
FunctionArgument = identifier ":" TypeUsage
FunctionDeclaration = "fn" identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
Function = FunctionDeclaration Block
StructTypeField = Identifier ":" TypeUsage
StructTypeDeclaration = "type" Identifier "struct" "{" ListOf<StructTypeField, ","> "}"
StructTypeField = identifier ":" TypeUsage
StructTypeDeclaration = "type" identifier "struct" "{" ListOf<StructTypeField, ","> "}"
TraitMethod = FunctionDeclaration ";"
TraitTypeDeclaration = "type" Identifier "trait" "{" TraitMethod* "}"
TraitTypeDeclaration = "type" identifier "trait" "{" TraitMethod* "}"
TypeDeclaration = StructTypeDeclaration
| TraitTypeDeclaration
Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}"

View File

@@ -98,7 +98,7 @@ semantics.addOperation<any>("toAST", {
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() },
};
},
Identifier(_1, _2): Identifier {
identifier(_1, _2): Identifier {
return {
name: this.sourceString,
spanStart: this.source.startIdx,

View File

@@ -0,0 +1,70 @@
import { Impl, Module, TraitTypeDeclaration, TypeUsage } from "../parse/ast";
interface Context {
environment: Record<string, TraitTypeDeclaration>;
}
const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => {
if (typeA.typeUsage !== typeB.typeUsage) {
throw Error(`Mismatched types: ${typeA.typeUsage} ${typeB.typeUsage}`);
}
if (typeA.typeUsage == "NamedTypeUsage" && typeB.typeUsage == "NamedTypeUsage") {
if (typeA.name.name !== typeB.name.name) {
throw Error(`Mismatched types: ${typeA.name.name} ${typeB.name.name}`);
}
}
if (typeA.typeUsage == "FunctionTypeUsage" && typeB.typeUsage == "FunctionTypeUsage") {
if (typeA.arguments.length !== typeB.arguments.length) {
throw Error(`Mismatched arg lengths: ${typeA.arguments.length} ${typeB.arguments.length}`);
}
for (let i = 0; i < typeA.arguments.length; i++) {
compareTypes(typeA.arguments[i], typeB.arguments[i]);
}
compareTypes(typeA.returnType, typeB.returnType);
}
};
export default class TraitChecker {
withModule = (module: Module) => {
let ctx: Context = { environment: {} };
for (const item of module.items) {
if (item.moduleItem == "TraitTypeDeclaration") {
ctx.environment[item.name.name] = item;
}
}
for (const item of module.items) {
if (item.moduleItem == "Impl") {
this.withImpl(ctx, item);
}
}
};
withImpl = (ctx: Context, impl: Impl) => {
if (new Set(impl.functions.map((fn) => fn.declaration.name)).size !== impl.functions.length) {
throw Error(`Duplicate functions in ${impl.struct.name.name}`);
}
if (impl.trait == null) {
return;
}
const trait = ctx.environment[impl.trait.name.name];
if (!trait) {
throw Error(`No such trait: ${impl.trait.name}`);
}
if (impl.functions.length !== trait.functions.length) {
throw Error(`Mismatched impl/trait len ${impl.trait.name.name} for ${impl.struct.name.name}`);
}
for (let i = 0; i < impl.functions.length; i++) {
if (impl.functions[i].declaration.name.name !== trait.functions[i].name.name) {
throw Error(
`Mismatched impl/trait names ${impl.functions[i].declaration.name} for ${trait.functions[i].name}`,
);
}
for (let j = 0; j < impl.functions[i].declaration.arguments.length; j++) {
compareTypes(
impl.functions[i].declaration.arguments[j].type,
trait.functions[i].arguments[j].type,
);
}
compareTypes(impl.functions[i].declaration.returnType, trait.functions[i].returnType);
}
};
}