add trait checking
This commit is contained in:
@@ -7,3 +7,25 @@ fn main(): String {
|
||||
a = (b + c.d()); // comment
|
||||
return a.b() ;
|
||||
}
|
||||
|
||||
type User struct {
|
||||
id: i64
|
||||
}
|
||||
|
||||
type TestTrait trait {
|
||||
fn class_method(id: i64): Self;
|
||||
fn instance_method(self: Self): i64;
|
||||
fn default_impl(self: Self): i64;
|
||||
}
|
||||
|
||||
impl TestTrait for User {
|
||||
fn class_method(id: i64): Self {
|
||||
return Self{id: id};
|
||||
}
|
||||
fn instance_method(self: Self): i64 {
|
||||
return self.get_id();
|
||||
}
|
||||
fn default_impl(self: Self): i64 {
|
||||
return self.instance_method();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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* "}"
|
||||
|
||||
@@ -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,
|
||||
|
||||
70
packages/boringlang/src/types/trait_checker.ts
Normal file
70
packages/boringlang/src/types/trait_checker.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
const config = {
|
||||
trailingComma: "all",
|
||||
singleQuote: false,
|
||||
printWidth: 80,
|
||||
printWidth: 100,
|
||||
semi: true,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user