add trait checking
This commit is contained in:
@@ -7,3 +7,25 @@ fn main(): String {
|
|||||||
a = (b + c.d()); // comment
|
a = (b + c.d()); // comment
|
||||||
return a.b() ;
|
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 { defineCommand } from "@bunli/core";
|
||||||
import { boringGrammar } from "../parse/grammar";
|
import { boringGrammar } from "../parse/grammar";
|
||||||
import { semantics } from "../parse/semantics";
|
import { semantics } from "../parse/semantics";
|
||||||
|
import TraitChecker from "../types/trait_checker";
|
||||||
|
|
||||||
export const run = defineCommand({
|
export const run = defineCommand({
|
||||||
name: "run",
|
name: "run",
|
||||||
@@ -20,6 +21,8 @@ export const run = defineCommand({
|
|||||||
if (match.succeeded()) {
|
if (match.succeeded()) {
|
||||||
const adapter = semantics(match);
|
const adapter = semantics(match);
|
||||||
const ast = adapter.toAST();
|
const ast = adapter.toAST();
|
||||||
|
new TraitChecker().withModule(ast);
|
||||||
|
|
||||||
console.log(JSON.stringify(ast, null, 2));
|
console.log(JSON.stringify(ast, null, 2));
|
||||||
} else {
|
} else {
|
||||||
console.log(match.message);
|
console.log(match.message);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as ohm from "ohm-js";
|
|||||||
export const boringGrammar = ohm.grammar(String.raw`
|
export const boringGrammar = ohm.grammar(String.raw`
|
||||||
Boringlang {
|
Boringlang {
|
||||||
ReturnStatement = "return" Expression ";"
|
ReturnStatement = "return" Expression ";"
|
||||||
LetStatement = "let" Identifier (":" TypeUsage)? "=" Expression ";"
|
LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";"
|
||||||
AssignmentStatement = VariableUsage "=" Expression ";"
|
AssignmentStatement = VariableUsage "=" Expression ";"
|
||||||
| StructGetter "=" Expression ";"
|
| StructGetter "=" Expression ";"
|
||||||
ExpressionStatement = Expression ";"
|
ExpressionStatement = Expression ";"
|
||||||
@@ -16,12 +16,12 @@ export const boringGrammar = ohm.grammar(String.raw`
|
|||||||
LiteralBool = "true" | "false"
|
LiteralBool = "true" | "false"
|
||||||
LiteralString = "\"" (~"\"" any)* "\""
|
LiteralString = "\"" (~"\"" any)* "\""
|
||||||
| "'" (~"'" any)* "'"
|
| "'" (~"'" any)* "'"
|
||||||
LiteralStructField = Identifier ":" Expression
|
LiteralStructField = identifier ":" Expression
|
||||||
LiteralStruct = Identifier "{" ListOf<LiteralStructField, ","> "}"
|
LiteralStruct = identifier "{" ListOf<LiteralStructField, ","> "}"
|
||||||
Identifier = (letter | "_")+ (letter | digit | "_")*
|
identifier = (letter | "_")+(letter | digit | "_")*
|
||||||
FunctionCall = Expression "(" ListOf<Expression, ","> ")"
|
FunctionCall = Expression "(" ListOf<Expression, ","> ")"
|
||||||
StructGetter = Expression "." Identifier
|
StructGetter = Expression "." identifier
|
||||||
VariableUsage = Identifier
|
VariableUsage = identifier
|
||||||
IfExpression = "if" "(" Expression ")" Block ("else" Block)?
|
IfExpression = "if" "(" Expression ")" Block ("else" Block)?
|
||||||
Term = LiteralInt
|
Term = LiteralInt
|
||||||
| LiteralFloat
|
| LiteralFloat
|
||||||
@@ -41,16 +41,16 @@ export const boringGrammar = ohm.grammar(String.raw`
|
|||||||
| FunctionCall
|
| FunctionCall
|
||||||
| Factor
|
| Factor
|
||||||
Block = "{" Statement* Expression? "}"
|
Block = "{" Statement* Expression? "}"
|
||||||
NamedTypeUsage = Identifier
|
NamedTypeUsage = identifier
|
||||||
TypeUsage = NamedTypeUsage
|
TypeUsage = NamedTypeUsage
|
||||||
| "fn" "(" ListOf<TypeUsage, ","> ")" ":" TypeUsage -- function_tu
|
| "fn" "(" ListOf<TypeUsage, ","> ")" ":" TypeUsage -- function_tu
|
||||||
FunctionArgument = Identifier ":" TypeUsage
|
FunctionArgument = identifier ":" TypeUsage
|
||||||
FunctionDeclaration = "fn" Identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
|
FunctionDeclaration = "fn" identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
|
||||||
Function = FunctionDeclaration Block
|
Function = FunctionDeclaration Block
|
||||||
StructTypeField = Identifier ":" TypeUsage
|
StructTypeField = identifier ":" TypeUsage
|
||||||
StructTypeDeclaration = "type" Identifier "struct" "{" ListOf<StructTypeField, ","> "}"
|
StructTypeDeclaration = "type" identifier "struct" "{" ListOf<StructTypeField, ","> "}"
|
||||||
TraitMethod = FunctionDeclaration ";"
|
TraitMethod = FunctionDeclaration ";"
|
||||||
TraitTypeDeclaration = "type" Identifier "trait" "{" TraitMethod* "}"
|
TraitTypeDeclaration = "type" identifier "trait" "{" TraitMethod* "}"
|
||||||
TypeDeclaration = StructTypeDeclaration
|
TypeDeclaration = StructTypeDeclaration
|
||||||
| TraitTypeDeclaration
|
| TraitTypeDeclaration
|
||||||
Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}"
|
Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}"
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ semantics.addOperation<any>("toAST", {
|
|||||||
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() },
|
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Identifier(_1, _2): Identifier {
|
identifier(_1, _2): Identifier {
|
||||||
return {
|
return {
|
||||||
name: this.sourceString,
|
name: this.sourceString,
|
||||||
spanStart: this.source.startIdx,
|
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 = {
|
const config = {
|
||||||
trailingComma: "all",
|
trailingComma: "all",
|
||||||
singleQuote: false,
|
singleQuote: false,
|
||||||
printWidth: 80,
|
printWidth: 100,
|
||||||
semi: true,
|
semi: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user