Compare commits

..

13 Commits

Author SHA1 Message Date
8f6200f393 added generics ast 2026-01-20 17:47:36 -07:00
e7eb194b6c added type info to getaddr 2025-10-10 20:49:11 -06:00
7cad512010 fix parsing 2025-10-09 17:40:54 -06:00
4e981a69a8 rename strings 2025-08-31 23:07:37 -06:00
126524a9e9 added interpreter 2025-08-31 23:06:26 -06:00
dd4f5b9ee6 added type resolver 2025-08-30 22:11:19 -06:00
66c7864df0 removed return/arg type comparisons 2025-08-29 22:36:17 -06:00
b2709ffc82 got type system working 2025-08-25 21:51:50 -06:00
0a315c5615 added withFunction 2025-08-20 22:47:26 -06:00
68e51cf8aa started on type checker 2025-08-20 21:42:42 -06:00
982603aa54 fix type system not resolving func args 2025-08-19 23:00:32 -06:00
05856f5d07 add check step to all type comparisons 2025-08-19 22:44:05 -06:00
df1083df3b added type system 2025-08-19 22:40:45 -06:00
19 changed files with 2343 additions and 181 deletions

View File

@@ -2,11 +2,11 @@ type MyTrait trait {}
type Pair[K, V: MyTrait] struct { type Pair[K, V: MyTrait] struct {
k: K, k: K,
v: V, v: V
} }
type Value struct { type Value struct {
value: i64, value: i64
} }
impl MyTrait for Value {} impl MyTrait for Value {}
@@ -21,7 +21,7 @@ impl [K, V: MyTrait] Pair[K, V] {
fn main(): i64 { fn main(): i64 {
let a = Pair[i64, Value]{ let a = Pair[i64, Value]{
k: 4, k: 4,
v: Value{value: 6}, v: Value{value: 6}
}; };
return a.get_value[i64](999).value; return a.get_value[i64](999).value;
} }

View File

@@ -1,11 +1,7 @@
fn main(): String { fn main(): i64 {
let a = 2; let user = User{id: 4};
a; let result = user.instance_method() + User::instance_method(user);
a = 3; return result;
a = if(true) {"asdf"} else {"fdsa"};
a.b.c.d();
a = (b + c.d()); // comment
return a.b() ;
} }
type User struct { type User struct {
@@ -23,7 +19,7 @@ impl TestTrait for User {
return Self{id: id}; return Self{id: id};
} }
fn instance_method(self: Self): i64 { fn instance_method(self: Self): i64 {
return self.get_id(); return self.id;
} }
fn default_impl(self: Self): i64 { fn default_impl(self: Self): i64 {
return self.instance_method(); return self.instance_method();

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<TodoResponse> 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<TodoResponse>::ok(instance.into());
}
}

View File

@@ -2,6 +2,11 @@ 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"; import TraitChecker from "../types/trait_checker";
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({ export const run = defineCommand({
name: "run", name: "run",
@@ -21,9 +26,29 @@ 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();
// console.log(JSON.stringify(ast, null, 2));
new TraitChecker().withModule(ast); new TraitChecker().withModule(ast);
const aliasResolvedAst = new TypeAliasResolver().withModule(ast);
const typeSystem = new TypeSystem();
const typeChecker = new TypeChecker();
const typeResolver = new TypeResolver();
console.log(JSON.stringify(ast, null, 2)); typeChecker.withModule(aliasResolvedAst, typeSystem);
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));
// console.log(JSON.stringify(aliasResolvedAst, null, 2));
} else { } else {
console.log(match.message); console.log(match.message);
// console.log(boringGrammar.trace(text, "Module").toString()); // console.log(boringGrammar.trace(text, "Module").toString());

View File

@@ -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<string, FunctionRef> = {};
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;
}

View File

@@ -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<string, Value>;
}
export type Value =
| NumericValue
| BoolValue
| StringValue
| FunctionValue
| StructValue
| UnitValue;
export interface Variable {
namedEntity: "Variable";
value: Value;
}
export interface EnvImpl {
trait: string | null;
functions: Record<string, FunctionRef>;
}
export interface NamedType {
namedEntity: "NamedType";
isA: "Scalar" | "Trait" | "Struct";
fields: Record<string, TypeUsage>;
impls: EnvImpl[];
}
export interface Context {
environment: Record<string, NamedType | Variable>;
currentModule: Module;
}

View File

@@ -0,0 +1,345 @@
import {
AssignmentStatement,
Block,
Expression,
Function,
FunctionCall,
LetStatement,
Module,
Operation,
Path,
ReturnStatement,
StructGetter,
StructTypeDeclaration,
TypeUsage,
} from "../parse/ast";
import { contextFromModule } from "./builtins";
import { Context, NamedType, 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.type == "Identifier") {
ctx.environment[statement.source.name.text] = {
namedEntity: "Variable",
value: result.value,
};
}
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.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<string, Value> = {};
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 === "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);
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 },
};
};
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`);
};
}

View File

@@ -4,7 +4,7 @@ export interface Spanned {
} }
export interface Identifier extends Spanned { export interface Identifier extends Spanned {
name: string; text: string;
} }
export interface LiteralInt { export interface LiteralInt {
@@ -41,6 +41,7 @@ export interface LiteralStruct {
name: Identifier; name: Identifier;
fields: StructField[]; fields: StructField[];
type: TypeUsage; type: TypeUsage;
typeParameters: GenericUsage;
} }
export interface FunctionCall { export interface FunctionCall {
@@ -55,6 +56,15 @@ export interface StructGetter {
source: Expression; source: Expression;
attribute: Identifier; attribute: Identifier;
type: TypeUsage; type: TypeUsage;
typeParameters: GenericUsage;
}
export interface Path {
expressionType: "Path";
typeParameters: GenericUsage;
value:
| { type: "Identifier"; name: Identifier }
| { type: "Nested"; parent: Path; name: Identifier };
} }
export interface Operation { export interface Operation {
@@ -62,11 +72,6 @@ export interface Operation {
left: Expression; left: Expression;
op: "+" | "-" | "*" | "/"; op: "+" | "-" | "*" | "/";
right: Expression; right: Expression;
}
export interface VariableUsage {
expressionType: "VariableUsage";
name: Identifier;
type: TypeUsage; type: TypeUsage;
} }
@@ -87,8 +92,8 @@ export interface Expression {
| LiteralString | LiteralString
| LiteralStruct | LiteralStruct
| FunctionCall | FunctionCall
| VariableUsage
| IfExpression | IfExpression
| Path
| StructGetter | StructGetter
| Block | Block
| Operation; | Operation;
@@ -109,17 +114,14 @@ export interface LetStatement {
export interface AssignmentStatement { export interface AssignmentStatement {
statementType: "AssignmentStatement"; statementType: "AssignmentStatement";
source: VariableUsage | StructGetter; source: { type: "Identifier"; name: Identifier } | { type: "StructGetter"; source: StructGetter };
expression: Expression; expression: Expression;
} }
export type Statement = export type Statement = ReturnStatement | LetStatement | AssignmentStatement | Expression;
| ReturnStatement
| LetStatement
| AssignmentStatement
| Expression;
export interface Block { export interface Block {
expressionType: "Block";
statements: Statement[]; statements: Statement[];
type: TypeUsage; type: TypeUsage;
} }
@@ -131,6 +133,7 @@ export interface FunctionArgument {
export interface FunctionDeclaration { export interface FunctionDeclaration {
name: Identifier; name: Identifier;
generic: GenericDeclaration;
arguments: FunctionArgument[]; arguments: FunctionArgument[];
returnType: TypeUsage; returnType: TypeUsage;
} }
@@ -141,6 +144,14 @@ export interface Function {
block: Block; block: Block;
} }
export const functionToType = (fn: FunctionDeclaration): FunctionTypeUsage => {
return {
typeUsage: "FunctionTypeUsage",
arguments: fn.arguments.map((arg) => arg.type),
returnType: fn.returnType,
};
};
export interface StructTypeField { export interface StructTypeField {
name: Identifier; name: Identifier;
type: TypeUsage; type: TypeUsage;
@@ -150,6 +161,7 @@ export interface StructTypeDeclaration {
moduleItem: "StructTypeDeclaration"; moduleItem: "StructTypeDeclaration";
typeDeclaration: "StructTypeDeclaration"; typeDeclaration: "StructTypeDeclaration";
name: Identifier; name: Identifier;
generic: GenericDeclaration;
fields: StructTypeField[]; fields: StructTypeField[];
} }
@@ -157,6 +169,7 @@ export interface TraitTypeDeclaration {
moduleItem: "TraitTypeDeclaration"; moduleItem: "TraitTypeDeclaration";
typeDeclaration: "TraitTypeDeclaration"; typeDeclaration: "TraitTypeDeclaration";
name: Identifier; name: Identifier;
generic: GenericDeclaration;
functions: FunctionDeclaration[]; functions: FunctionDeclaration[];
} }
@@ -164,6 +177,7 @@ export type TypeDeclaration = StructTypeDeclaration | TraitTypeDeclaration;
export interface Impl { export interface Impl {
moduleItem: "Impl"; moduleItem: "Impl";
generic: GenericDeclaration;
struct: NamedTypeUsage; struct: NamedTypeUsage;
trait: NamedTypeUsage | null; trait: NamedTypeUsage | null;
functions: Function[]; functions: Function[];
@@ -175,8 +189,29 @@ export interface Module {
items: ModuleItem[]; items: ModuleItem[];
} }
export interface GenericParameter {
name: Identifier;
bounds: Identifier[];
}
export interface GenericDeclaration {
parameters: GenericParameter[];
}
export interface KnownGenericUsage {
genericUsage: "Known";
parameters: TypeUsage[];
}
export interface UnknownGenericUsage {
genericUsage: "Unknown";
}
export type GenericUsage = UnknownGenericUsage | KnownGenericUsage;
export interface NamedTypeUsage { export interface NamedTypeUsage {
typeUsage: "NamedTypeUsage"; typeUsage: "NamedTypeUsage";
typeParameters: GenericUsage;
name: Identifier; name: Identifier;
} }
@@ -192,3 +227,76 @@ export interface UnknownTypeUsage {
} }
export type TypeUsage = NamedTypeUsage | FunctionTypeUsage | UnknownTypeUsage; export type TypeUsage = NamedTypeUsage | FunctionTypeUsage | UnknownTypeUsage;
export const newVoid: () => TypeUsage = () => {
return {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "Void", spanStart: 0, spanEnd: 0 },
};
};
export const newNever: () => TypeUsage = () => {
return {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "Never", spanStart: 0, spanEnd: 0 },
};
};
function containsReturnExpression(expression: Expression) {
if (expression.subExpression.expressionType === "LiteralStruct") {
for (const field of expression.subExpression.fields) {
if (containsReturnExpression(field.expression)) {
return true;
}
}
}
if (expression.subExpression.expressionType === "IfExpression") {
if (containsReturn(expression.subExpression.block)) {
return true;
}
if (expression.subExpression.else && containsReturn(expression.subExpression.else)) {
return true;
}
}
if (expression.subExpression.expressionType === "Block") {
if (containsReturn(expression.subExpression)) {
return true;
}
}
if (expression.subExpression.expressionType === "Operation") {
if (containsReturnExpression(expression.subExpression.left)) {
return true;
}
if (containsReturnExpression(expression.subExpression.right)) {
return true;
}
}
return false;
}
export function containsReturn(block: Block) {
for (const statement of block.statements) {
if (statement.statementType === "ReturnStatement") {
return true;
}
if (statement.statementType === "AssignmentStatement") {
if (containsReturnExpression(statement.expression)) {
return true;
}
}
if (statement.statementType === "LetStatement") {
if (containsReturnExpression(statement.expression)) {
return true;
}
}
if (statement.statementType === "Expression") {
if (containsReturnExpression(statement)) {
return true;
}
}
}
return false;
}

View File

@@ -2,10 +2,14 @@ import * as ohm from "ohm-js";
export const boringGrammar = ohm.grammar(String.raw` export const boringGrammar = ohm.grammar(String.raw`
Boringlang { Boringlang {
GenericUsage = "[" ListOf<TypeUsage, ","> "]"
GenericParameter = identifier ":" ListOf<TypeUsage, "+"> -- conditions
| identifier
GenericDeclaration = "[" ListOf<GenericParameter, ","> "]"
ReturnStatement = "return" Expression ";" ReturnStatement = "return" Expression ";"
LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";" LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";"
AssignmentStatement = VariableUsage "=" Expression ";" AssignmentStatement = identifier "=" Expression ";" -- identifier
| StructGetter "=" Expression ";" | StructGetter "=" Expression ";" -- getter
ExpressionStatement = Expression ";" ExpressionStatement = Expression ";"
Statement = ExpressionStatement Statement = ExpressionStatement
| LetStatement | LetStatement
@@ -17,43 +21,49 @@ export const boringGrammar = ohm.grammar(String.raw`
LiteralString = "\"" (~"\"" any)* "\"" LiteralString = "\"" (~"\"" any)* "\""
| "'" (~"'" any)* "'" | "'" (~"'" any)* "'"
LiteralStructField = identifier ":" Expression LiteralStructField = identifier ":" Expression
LiteralStruct = identifier "{" ListOf<LiteralStructField, ","> "}" LiteralStruct = identifier (GenericUsage)? "{" ListOf<LiteralStructField, ","> "}"
identifier = (letter | "_")+(letter | digit | "_")* identifier = (letter | "_")+(letter | digit | "_")*
FunctionCall = Expression "(" ListOf<Expression, ","> ")"
StructGetter = Expression "." identifier StructGetter = Expression "." identifier
VariableUsage = identifier
IfExpression = "if" "(" Expression ")" Block ("else" Block)? IfExpression = "if" "(" Expression ")" Block ("else" Block)?
Term = LiteralInt Path = Path "::" identifier (GenericUsage)? -- nested
| identifier (GenericUsage)? -- base
PrimaryExpression = LiteralInt
| LiteralFloat | LiteralFloat
| LiteralBool | LiteralBool
| LiteralString | LiteralString
| LiteralStruct | Path -- path
| IfExpression
| Block
| "(" Expression ")" -- parens | "(" Expression ")" -- parens
| VariableUsage StructExpression = LiteralStruct
Factor = Factor "*" Term -- mult | Block
| Factor "/" Term -- div | IfExpression
| Term | PrimaryExpression
Expression = Expression "+" Factor -- plus MemberExpression = MemberExpression "." identifier (GenericUsage)? -- structGetter
| Expression "-" Factor -- minus | StructExpression
| StructGetter CallExpression = CallExpression "." identifier (GenericUsage)? -- structGetter
| FunctionCall | CallExpression "(" ListOf<Expression, ","> ")" -- functionCall
| Factor | MemberExpression "(" ListOf<Expression, ","> ")" -- memberFunctionCall
| MemberExpression
MultExpression = MultExpression "*" CallExpression -- mult
| MultExpression "/" CallExpression -- div
| CallExpression
AddExpression = Expression "+" MultExpression -- plus
| Expression "-" MultExpression -- minus
| MultExpression
Expression = AddExpression
Block = "{" Statement* Expression? "}" Block = "{" Statement* Expression? "}"
NamedTypeUsage = identifier NamedTypeUsage = identifier (GenericUsage)?
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 (GenericDeclaration)? "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
Function = FunctionDeclaration Block Function = FunctionDeclaration Block
StructTypeField = identifier ":" TypeUsage StructTypeField = identifier ":" TypeUsage
StructTypeDeclaration = "type" identifier "struct" "{" ListOf<StructTypeField, ","> "}" StructTypeDeclaration = "type" identifier (GenericDeclaration)? "struct" "{" ListOf<StructTypeField, ","> "}"
TraitMethod = FunctionDeclaration ";" TraitMethod = FunctionDeclaration ";"
TraitTypeDeclaration = "type" identifier "trait" "{" TraitMethod* "}" TraitTypeDeclaration = "type" identifier (GenericDeclaration)? "trait" "{" TraitMethod* "}"
TypeDeclaration = StructTypeDeclaration TypeDeclaration = StructTypeDeclaration
| TraitTypeDeclaration | TraitTypeDeclaration
Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}" Impl = "impl" (GenericDeclaration)? (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}"
ModuleItem = Function ModuleItem = Function
| TypeDeclaration | TypeDeclaration
| Impl | Impl

View File

@@ -1,12 +1,16 @@
import { import {
AssignmentStatement, AssignmentStatement,
Block, Block,
containsReturn,
Expression, Expression,
Function, Function,
FunctionArgument, FunctionArgument,
FunctionCall, FunctionCall,
FunctionDeclaration, FunctionDeclaration,
FunctionTypeUsage, FunctionTypeUsage,
GenericDeclaration,
GenericParameter,
GenericUsage,
Identifier, Identifier,
IfExpression, IfExpression,
Impl, Impl,
@@ -19,7 +23,9 @@ import {
Module, Module,
ModuleItem, ModuleItem,
NamedTypeUsage, NamedTypeUsage,
newNever,
Operation, Operation,
Path,
ReturnStatement, ReturnStatement,
Statement, Statement,
StructField, StructField,
@@ -29,7 +35,6 @@ import {
TraitTypeDeclaration, TraitTypeDeclaration,
TypeDeclaration, TypeDeclaration,
TypeUsage, TypeUsage,
VariableUsage,
} from "./ast"; } from "./ast";
import { boringGrammar } from "./grammar"; import { boringGrammar } from "./grammar";
@@ -42,46 +47,87 @@ function nextUnknown() {
export const semantics = boringGrammar.createSemantics(); export const semantics = boringGrammar.createSemantics();
semantics.addOperation<any>("toAST", { semantics.addOperation<any>("toAST", {
LiteralInt(a): LiteralInt { GenericUsage(_1, typeUsage, _3): GenericUsage {
console.log(this);
console.log(a.source.startIdx);
return { return {
expressionType: "LiteralInt", genericUsage: "Known",
value: this.sourceString, parameters: typeUsage.asIteration().children.map((c) => c.toAST()),
type: {
typeUsage: "NamedTypeUsage",
name: { name: "i64", spanStart: 0, spanEnd: 0 },
},
}; };
}, },
LiteralFloat(_1, _2, _3): LiteralFloat { GenericParameter_conditions(identifier, _2, typeUsage): GenericParameter {
return { return {
expressionType: "LiteralFloat", name: identifier.toAST(),
value: this.sourceString, bounds: typeUsage.asIteration().children.map((c) => c.toAST()),
type: {
typeUsage: "NamedTypeUsage",
name: { name: "f64", spanStart: 0, spanEnd: 0 },
},
}; };
}, },
LiteralBool(_): LiteralBool { GenericParameter(identifier): GenericParameter {
return { return {
expressionType: "LiteralBool", name: identifier.toAST(),
value: this.sourceString, bounds: [],
type: {
typeUsage: "NamedTypeUsage",
name: { name: "bool", spanStart: 0, spanEnd: 0 },
},
}; };
}, },
LiteralString(_1, text, _3): LiteralString { GenericDeclaration(_1, parameters, _2): GenericDeclaration {
return { return {
expressionType: "LiteralString", parameters: parameters.asIteration().children.map((c) => c.toAST()),
value: text.sourceString, };
type: { },
typeUsage: "NamedTypeUsage", LiteralInt(a): Expression {
name: { name: "String", spanStart: 0, spanEnd: 0 }, return {
statementType: "Expression",
subExpression: {
expressionType: "LiteralInt",
value: this.sourceString,
type: {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "i64", spanStart: 0, spanEnd: 0 },
},
}, },
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
};
},
LiteralFloat(_1, _2, _3): Expression {
return {
statementType: "Expression",
subExpression: {
expressionType: "LiteralFloat",
value: this.sourceString,
type: {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "f64", spanStart: 0, spanEnd: 0 },
},
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
};
},
LiteralBool(_): Expression {
return {
statementType: "Expression",
subExpression: {
expressionType: "LiteralBool",
value: this.sourceString,
type: {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "bool", spanStart: 0, spanEnd: 0 },
},
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
};
},
LiteralString(_1, text, _3): Expression {
return {
statementType: "Expression",
subExpression: {
expressionType: "LiteralString",
value: text.sourceString,
type: {
typeUsage: "NamedTypeUsage",
typeParameters: { genericUsage: "Known", parameters: [] },
name: { text: "String", spanStart: 0, spanEnd: 0 },
},
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
LiteralStructField(identifier, _2, expression): StructField { LiteralStructField(identifier, _2, expression): StructField {
@@ -90,98 +136,220 @@ semantics.addOperation<any>("toAST", {
expression: expression.toAST(), expression: expression.toAST(),
}; };
}, },
LiteralStruct(identifier, _2, fields, _4): LiteralStruct { LiteralStruct(identifier, genericUsage, _2, fields, _4): Expression {
const gu = genericUsage.toAST();
return { return {
expressionType: "LiteralStruct", statementType: "Expression",
name: identifier.toAST(), subExpression: {
fields: fields.asIteration().children.map((c) => c.toAST()), expressionType: "LiteralStruct",
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() }, name: identifier.toAST(),
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
fields: fields.asIteration().children.map((c) => c.toAST()),
type: {
typeUsage: "NamedTypeUsage",
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
name: identifier.toAST(),
},
},
type: {
typeUsage: "UnknownTypeUsage",
name: nextUnknown(),
},
}; };
}, },
identifier(_1, _2): Identifier { identifier(_1, _2): Identifier {
return { return {
name: this.sourceString, text: this.sourceString,
spanStart: this.source.startIdx, spanStart: this.source.startIdx,
spanEnd: this.source.endIdx, spanEnd: this.source.endIdx,
}; };
}, },
FunctionCall(expression, _2, args, _4): FunctionCall { PrimaryExpression(literal): Expression {
return literal.toAST();
},
PrimaryExpression_path(path): Expression {
return { return {
expressionType: "FunctionCall", statementType: "Expression",
source: expression.toAST(), subExpression: path.toAST(),
arguments: args.asIteration().children.map((c) => c.toAST()),
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
StructGetter(expression, _2, identifier): StructGetter { 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, genericUsage): Expression {
const gu = genericUsage.toAST();
return { return {
expressionType: "StructGetter", statementType: "Expression",
source: expression.toAST(), subExpression: {
attribute: identifier.toAST(), expressionType: "StructGetter",
source: expression.toAST(),
attribute: identifier.toAST(),
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
VariableUsage(identifier): VariableUsage { CallExpression(expression): Expression {
return expression.toAST();
},
CallExpression_structGetter(expression, _2, identifier, genericUsage): Expression {
const gu = genericUsage.toAST();
return { return {
expressionType: "VariableUsage", statementType: "Expression",
name: identifier.toAST(), subExpression: {
expressionType: "StructGetter",
source: expression.toAST(),
attribute: identifier.toAST(),
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
IfExpression(_1, _2, expression, _4, block, _6, elseBlock): IfExpression { CallExpression_functionCall(expression, _2, args, _4): Expression {
const resolvedArgs = args.asIteration().children.map((c) => c.toAST());
return {
statementType: "Expression",
subExpression: {
expressionType: "FunctionCall",
source: expression.toAST(),
arguments: resolvedArgs,
type: {
typeUsage: "UnknownTypeUsage",
name: nextUnknown(),
},
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
};
},
CallExpression_memberFunctionCall(expression, _2, args, _4): Expression {
const resolvedArgs = args.asIteration().children.map((c) => c.toAST());
return {
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(),
typeParameters: { genericUsage: "Unknown" },
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
};
},
IfExpression(_1, _2, expression, _4, block, _6, elseBlock): Expression {
const eb = elseBlock.toAST(); const eb = elseBlock.toAST();
return { return {
expressionType: "IfExpression", statementType: "Expression",
condition: expression.toAST(), subExpression: {
block: block.toAST(), expressionType: "IfExpression",
else: eb.length > 0 ? eb[0] : null, condition: expression.toAST(),
block: block.toAST(),
else: eb.length > 0 ? eb[0] : null,
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
},
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
Term(term): Expression { Path_base(identifier, genericUsage): Path {
return term.toAST(); const gu = genericUsage.toAST();
return {
expressionType: "Path",
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
value: { type: "Identifier", name: identifier.toAST() },
};
}, },
Term_parens(_1, term, _3): Expression { Path_nested(basePath, _2, attrIdent, genericUsage): Path {
return term.toAST(); const gu = genericUsage.toAST();
return {
expressionType: "Path",
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
value: { type: "Nested", parent: basePath.toAST(), name: attrIdent.toAST() },
};
}, },
Factor(factor): Expression { MultExpression(expression): Expression {
return factor.toAST(); 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 { Expression(expression): Expression {
return expression.toAST(); return expression.toAST();
}, },
Expression_plus(expression, _2, factor): Operation {
return {
expressionType: "Operation",
left: expression.toAST(),
op: "+",
right: factor.toAST(),
};
},
Expression_minus(expression, _2, factor): Operation {
return {
expressionType: "Operation",
left: expression.toAST(),
op: "-",
right: factor.toAST(),
};
},
Factor_mult(factor, _2, term): Operation {
return {
expressionType: "Operation",
left: factor.toAST(),
op: "*",
right: term.toAST(),
};
},
Factor_div(factor, _2, term): Operation {
return {
expressionType: "Operation",
left: factor.toAST(),
op: "/",
right: term.toAST(),
};
},
Statement(statement): Statement { Statement(statement): Statement {
return statement.toAST(); return statement.toAST();
}, },
@@ -197,16 +365,20 @@ semantics.addOperation<any>("toAST", {
statementType: "LetStatement", statementType: "LetStatement",
variableName: ident.toAST(), variableName: ident.toAST(),
expression: expression.toAST(), expression: expression.toAST(),
type: type: tu.length > 0 ? tu[0] : { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
tu.length > 0
? tu[0]
: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
}; };
}, },
AssignmentStatement(variable, _2, expression, _4): AssignmentStatement { AssignmentStatement_identifier(variable, _2, expression, _4): AssignmentStatement {
return { return {
statementType: "AssignmentStatement", 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(), expression: expression.toAST(),
}; };
}, },
@@ -220,15 +392,24 @@ semantics.addOperation<any>("toAST", {
Block(_1, statements, expression, _4): Block { Block(_1, statements, expression, _4): Block {
const lines = statements.asIteration().children.map((c) => c.toAST()); const lines = statements.asIteration().children.map((c) => c.toAST());
const finalExpression = expression.toAST(); const finalExpression = expression.toAST();
lines.push(finalExpression.length > 0 ? finalExpression[0] : null); if (finalExpression.length > 0) {
return { lines.push(finalExpression[0]);
}
const block: Block = {
expressionType: "Block",
statements: lines, statements: lines,
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() }, type: newNever(),
}; };
if (!containsReturn(block)) {
block.type = { typeUsage: "UnknownTypeUsage", name: nextUnknown() };
}
return block;
}, },
NamedTypeUsage(name): NamedTypeUsage { NamedTypeUsage(name, genericUsage): NamedTypeUsage {
const gu = genericUsage.toAST();
return { return {
typeUsage: "NamedTypeUsage", typeUsage: "NamedTypeUsage",
typeParameters: gu.length ? gu[0] : { genericUsage: "Unknown" },
name: name.toAST(), name: name.toAST(),
}; };
}, },
@@ -251,14 +432,17 @@ semantics.addOperation<any>("toAST", {
FunctionDeclaration( FunctionDeclaration(
_1, _1,
identifier, identifier,
_3, genericDeclaration,
args,
_4, _4,
args,
_5, _5,
_6,
returnType, returnType,
): FunctionDeclaration { ): FunctionDeclaration {
const gd = genericDeclaration.toAST();
return { return {
name: identifier.toAST(), name: identifier.toAST(),
generic: gd.length ? gd[0] : { parameters: [] },
arguments: args.asIteration().children.map((c) => c.toAST()), arguments: args.asIteration().children.map((c) => c.toAST()),
returnType: returnType.toAST(), returnType: returnType.toAST(),
}; };
@@ -279,15 +463,18 @@ semantics.addOperation<any>("toAST", {
StructTypeDeclaration( StructTypeDeclaration(
_1, _1,
identifier, identifier,
_3, genericDeclaration,
_4, _4,
_5,
fields, fields,
_6, _7,
): StructTypeDeclaration { ): StructTypeDeclaration {
const gd = genericDeclaration.toAST();
return { return {
moduleItem: "StructTypeDeclaration", moduleItem: "StructTypeDeclaration",
typeDeclaration: "StructTypeDeclaration", typeDeclaration: "StructTypeDeclaration",
name: identifier.toAST(), name: identifier.toAST(),
generic: gd.length ? gd[0] : { parameters: [] },
fields: fields.asIteration().children.map((c) => c.toAST()), fields: fields.asIteration().children.map((c) => c.toAST()),
}; };
}, },
@@ -297,25 +484,30 @@ semantics.addOperation<any>("toAST", {
TraitTypeDeclaration( TraitTypeDeclaration(
_1, _1,
identifier, identifier,
_3, genericDeclaration,
_4, _4,
methods,
_5, _5,
methods,
_7,
): TraitTypeDeclaration { ): TraitTypeDeclaration {
const gd = genericDeclaration.toAST();
return { return {
moduleItem: "TraitTypeDeclaration", moduleItem: "TraitTypeDeclaration",
typeDeclaration: "TraitTypeDeclaration", typeDeclaration: "TraitTypeDeclaration",
name: identifier.toAST(), name: identifier.toAST(),
generic: gd.length ? gd[0] : { parameters: [] },
functions: methods.asIteration().children.map((c) => c.toAST()), functions: methods.asIteration().children.map((c) => c.toAST()),
}; };
}, },
TypeDeclaration(declaration): TypeDeclaration { TypeDeclaration(declaration): TypeDeclaration {
return declaration.toAST(); return declaration.toAST();
}, },
Impl(_1, trait, _3, struct, _4, methods, _5): Impl { Impl(_1, genericDeclaration, trait, _4, struct, _6, methods, _8): Impl {
const tr = trait.toAST(); const tr = trait.toAST();
const gd = genericDeclaration.toAST();
return { return {
moduleItem: "Impl", moduleItem: "Impl",
generic: gd.length ? gd[0] : { parameters: [] },
struct: struct.toAST(), struct: struct.toAST(),
trait: tr.length > 0 ? tr[0] : null, trait: tr.length > 0 ? tr[0] : null,
functions: methods.asIteration().children.map((c) => c.toAST()), functions: methods.asIteration().children.map((c) => c.toAST()),

View File

@@ -0,0 +1,21 @@
import { Context } from "./context";
export function newContext(): Context {
const result: Context = {
currentFunctionReturn: null,
environment: {},
};
result.environment["i8"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["i16"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["i32"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["i64"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["f8"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["f16"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["f32"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["f64"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["String"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["Void"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
result.environment["Never"] = { namedEntity: "NamedType", isA: "Scalar", fields: {}, impls: [] };
return result;
}

View File

@@ -0,0 +1,95 @@
import { FunctionTypeUsage, NamedTypeUsage, TypeUsage } from "../parse/ast";
interface EnvImpl {
trait: string | null;
functions: Record<string, FunctionTypeUsage>;
}
interface NamedType {
namedEntity: "NamedType";
isA: "Scalar" | "Trait" | "Struct";
fields: Record<string, TypeUsage>;
impls: EnvImpl[];
}
interface Variable {
namedEntity: "Variable";
type: TypeUsage;
}
type NamedEntity = NamedType | Variable;
export interface Context {
currentFunctionReturn: TypeUsage | null;
environment: Record<string, NamedEntity>;
}
export function getAttr(ctx: Context, name: string, field: string): TypeUsage {
const struct = ctx.environment[name];
if (!struct || struct.namedEntity !== "NamedType") {
throw Error(`Unknown type ${name}`);
}
if (struct.fields[field]) {
return struct.fields[field];
}
let results: TypeUsage[] = [];
for (const impl of struct.impls) {
if (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) {
throw Error(`${name} has no attribue ${field}`);
}
if (results.length > 1) {
throw Error(`${name} has multiple attribues ${field}, use universal function call syntax`);
}
return results[0];
}
export function typeExists(ctx: Context, type: TypeUsage) {
if (type.typeUsage === "NamedTypeUsage") {
if (
!ctx.environment[type.name.text] ||
ctx.environment[type.name.text].namedEntity !== "NamedType"
) {
throw Error(`${type.name.text} is not a type.`);
}
}
if (type.typeUsage === "FunctionTypeUsage") {
for (const arg of type.arguments) {
typeExists(ctx, arg);
}
typeExists(ctx, type.returnType);
}
}
export function replaceType(oldName: string, newType: TypeUsage, inType: TypeUsage) {
if (inType.typeUsage === "NamedTypeUsage") {
if (inType.name.text === oldName) {
return deepCopy(newType);
}
}
if (inType.typeUsage === "FunctionTypeUsage") {
const result = deepCopy(inType);
for (const [i, arg] of inType.arguments.entries()) {
result.arguments[i] = replaceType(oldName, newType, arg);
}
result.returnType = replaceType(oldName, newType, inType.returnType);
}
return deepCopy(inType);
}
export const deepCopy = <T>(o: T) => {
return JSON.parse(JSON.stringify(o)) as T;
};

View File

@@ -1,35 +1,16 @@
import { Impl, Module, TraitTypeDeclaration, TypeUsage } from "../parse/ast"; import { Impl, Module, TraitTypeDeclaration, TypeUsage } from "../parse/ast";
import { compareTypes } from "./type_system";
interface Context { interface Context {
environment: Record<string, TraitTypeDeclaration>; 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 { export default class TraitChecker {
withModule = (module: Module) => { withModule = (module: Module) => {
let ctx: Context = { environment: {} }; let ctx: Context = { environment: {} };
for (const item of module.items) { for (const item of module.items) {
if (item.moduleItem == "TraitTypeDeclaration") { if (item.moduleItem == "TraitTypeDeclaration") {
ctx.environment[item.name.name] = item; ctx.environment[item.name.text] = item;
} }
} }
for (const item of module.items) { for (const item of module.items) {
@@ -38,22 +19,33 @@ export default class TraitChecker {
} }
} }
}; };
withTrait = (trait: TraitTypeDeclaration) => {
for (const fn of trait.functions) {
if (fn.arguments.length === 0) {
throw new Error("First argument of trait method must be Self");
}
const firstArg = fn.arguments[0];
if (firstArg.type.typeUsage !== "NamedTypeUsage" || firstArg.type.name.text !== "Self") {
throw new Error("First argument of trait method must be Self");
}
}
};
withImpl = (ctx: Context, impl: Impl) => { withImpl = (ctx: Context, impl: Impl) => {
if (new Set(impl.functions.map((fn) => fn.declaration.name)).size !== impl.functions.length) { if (new Set(impl.functions.map((fn) => fn.declaration.name)).size !== impl.functions.length) {
throw Error(`Duplicate functions in ${impl.struct.name.name}`); throw Error(`Duplicate functions in ${impl.struct.name.text}`);
} }
if (impl.trait == null) { if (impl.trait == null) {
return; return;
} }
const trait = ctx.environment[impl.trait.name.name]; const trait = ctx.environment[impl.trait.name.text];
if (!trait) { if (!trait) {
throw Error(`No such trait: ${impl.trait.name}`); throw Error(`No such trait: ${impl.trait.name}`);
} }
if (impl.functions.length !== trait.functions.length) { if (impl.functions.length !== trait.functions.length) {
throw Error(`Mismatched impl/trait len ${impl.trait.name.name} for ${impl.struct.name.name}`); throw Error(`Mismatched impl/trait len ${impl.trait.name.text} for ${impl.struct.name.text}`);
} }
for (let i = 0; i < impl.functions.length; i++) { for (let i = 0; i < impl.functions.length; i++) {
if (impl.functions[i].declaration.name.name !== trait.functions[i].name.name) { if (impl.functions[i].declaration.name.text !== trait.functions[i].name.text) {
throw Error( throw Error(
`Mismatched impl/trait names ${impl.functions[i].declaration.name} for ${trait.functions[i].name}`, `Mismatched impl/trait names ${impl.functions[i].declaration.name} for ${trait.functions[i].name}`,
); );

View File

@@ -0,0 +1,233 @@
import {
AssignmentStatement,
Block,
Expression,
Function,
FunctionCall,
FunctionDeclaration,
IfExpression,
Impl,
LetStatement,
LiteralStruct,
Module,
Operation,
ReturnStatement,
StructGetter,
StructTypeDeclaration,
TraitTypeDeclaration,
TypeUsage,
Path,
} from "../parse/ast";
import { deepCopy, replaceType } from "./context";
interface AliasContext {
environment: Record<string, TypeUsage>;
}
export class TypeAliasResolver {
withModule = (module: Module) => {
const ctx: AliasContext = { environment: {} };
const result = deepCopy(module);
for (const [i, item] of module.items.entries()) {
if (item.moduleItem === "TraitTypeDeclaration") {
let traitCtx = deepCopy(ctx);
traitCtx.environment["Self"] = { typeUsage: "NamedTypeUsage", name: item.name };
result.items[i] = this.withTraitTypeDeclaration(traitCtx, item);
}
if (item.moduleItem === "Impl") {
let implCtx = deepCopy(ctx);
implCtx.environment["Self"] = { typeUsage: "NamedTypeUsage", name: item.struct.name };
result.items[i] = this.withImpl(implCtx, item);
}
if (item.moduleItem === "StructTypeDeclaration") {
let structCtx = deepCopy(ctx);
structCtx.environment["Self"] = { typeUsage: "NamedTypeUsage", name: item.name };
result.items[i] = this.withStructDeclaration(structCtx, item);
}
}
return result;
};
withTraitTypeDeclaration = (ctx: AliasContext, trait: TraitTypeDeclaration) => {
const result = deepCopy(trait);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
for (const [i, fn] of trait.functions.entries()) {
result.functions[i] = this.withFunctionDeclaration(ctx, fn);
}
}
return result;
};
withImpl = (ctx: AliasContext, impl: Impl) => {
const result = deepCopy(impl);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
for (const [i, fn] of impl.functions.entries()) {
result.functions[i] = this.withFunction(ctx, fn);
}
}
return result;
};
withStructDeclaration = (ctx: AliasContext, struct: StructTypeDeclaration) => {
const result = deepCopy(struct);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
for (const [i, field] of struct.fields.entries()) {
result.fields[i].type = replaceType(oldName, newType, field.type);
}
}
return result;
};
withFunctionDeclaration = (ctx: AliasContext, fn: FunctionDeclaration) => {
const result = deepCopy(fn);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
for (const [i, arg] of fn.arguments.entries()) {
result.arguments[i].type = replaceType(oldName, newType, arg.type);
}
result.returnType = replaceType(oldName, newType, result.returnType);
}
return result;
};
withFunction = (ctx: AliasContext, fn: Function) => {
const result = deepCopy(fn);
result.declaration = this.withFunctionDeclaration(ctx, fn.declaration);
result.block = this.withBlock(ctx, fn.block);
return result;
};
withBlock = (ctx: AliasContext, block: Block) => {
const result = deepCopy(block);
for (const [i, statement] of block.statements.entries()) {
if (statement.statementType === "AssignmentStatement") {
result.statements[i] = this.withAssignmentStatement(ctx, statement);
}
if (statement.statementType === "LetStatement") {
result.statements[i] = this.withLetStatement(ctx, statement);
}
if (statement.statementType === "Expression") {
result.statements[i] = this.withExpression(ctx, statement);
}
if (statement.statementType === "ReturnStatement") {
result.statements[i] = this.withReturnStatement(ctx, statement);
}
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, block.type);
}
}
return result;
};
withAssignmentStatement = (ctx: AliasContext, statement: AssignmentStatement) => {
const result = deepCopy(statement);
if (statement.source.type == "StructGetter") {
result.source = {
type: "StructGetter",
source: this.withStructGetter(ctx, statement.source.source),
};
}
if (statement.source.type == "Identifier") {
result.source = deepCopy(result.source);
}
result.expression = this.withExpression(ctx, statement.expression);
return result;
};
withLetStatement = (ctx: AliasContext, statement: LetStatement) => {
const result = deepCopy(statement);
result.expression = this.withExpression(ctx, statement.expression);
return result;
};
withReturnStatement = (ctx: AliasContext, statement: ReturnStatement) => {
const result = deepCopy(statement);
result.source = this.withExpression(ctx, statement.source);
return result;
};
withExpression = (ctx: AliasContext, expression: Expression) => {
const result = deepCopy(expression);
if (expression.subExpression.expressionType === "LiteralStruct") {
result.subExpression = this.withLiteralStruct(ctx, expression.subExpression);
}
if (expression.subExpression.expressionType === "FunctionCall") {
result.subExpression = this.withFunctionCall(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);
}
if (expression.subExpression.expressionType === "StructGetter") {
result.subExpression = this.withStructGetter(ctx, expression.subExpression);
}
if (expression.subExpression.expressionType === "Block") {
result.subExpression = this.withBlock(ctx, expression.subExpression);
}
if (expression.subExpression.expressionType === "Operation") {
result.subExpression = this.withOperation(ctx, expression.subExpression);
}
return result;
};
withLiteralStruct = (ctx: AliasContext, literalStruct: LiteralStruct) => {
const result = deepCopy(literalStruct);
for (const [i, field] of literalStruct.fields.entries()) {
result.fields[i].expression = this.withExpression(ctx, field.expression);
}
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, literalStruct.type);
if (result.name.text === oldName && newType.typeUsage === "NamedTypeUsage") {
result.name = newType.name;
}
}
return result;
};
withFunctionCall = (ctx: AliasContext, fnCall: FunctionCall) => {
const result = deepCopy(fnCall);
for (const [i, arg] of fnCall.arguments.entries()) {
result.arguments[i] = this.withExpression(ctx, arg);
}
result.source = this.withExpression(ctx, fnCall.source);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, fnCall.type);
}
return result;
};
withIfExpression = (ctx: AliasContext, ifExpression: IfExpression) => {
const result = deepCopy(ifExpression);
result.condition = this.withExpression(ctx, ifExpression.condition);
result.block = this.withBlock(ctx, ifExpression.block);
if (ifExpression.else) {
result.else = this.withBlock(ctx, ifExpression.else);
}
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, ifExpression.type);
}
return result;
};
withStructGetter = (ctx: AliasContext, structGetter: StructGetter) => {
const result = deepCopy(structGetter);
result.source = this.withExpression(ctx, structGetter.source);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, structGetter.type);
}
return result;
};
withOperation = (ctx: AliasContext, op: Operation) => {
const result = deepCopy(op);
result.left = this.withExpression(ctx, op.left);
result.right = this.withExpression(ctx, op.right);
for (const [oldName, newType] of Object.entries(ctx.environment)) {
result.type = replaceType(oldName, newType, op.type);
}
return result;
};
}

View File

@@ -0,0 +1,455 @@
import {
AssignmentStatement,
Block,
containsReturn,
Expression,
Function,
FunctionCall,
FunctionDeclaration,
functionToType,
IfExpression,
Impl,
LetStatement,
LiteralStruct,
Module,
newVoid,
Operation,
ReturnStatement,
StructGetter,
StructTypeDeclaration,
TraitTypeDeclaration,
TypeUsage,
Path,
Identifier,
FunctionTypeUsage,
} from "../parse/ast";
import { newContext } from "./builtins";
import { Context, deepCopy, typeExists } from "./context";
import { TypeSystem } from "./type_system";
export class TypeChecker {
withModule = (module: Module, typeSystem: TypeSystem) => {
const ctx: Context = newContext();
// add functions, structs, and traits to the context
for (const item of module.items) {
if (item.moduleItem === "StructTypeDeclaration") {
if (ctx.environment[item.name.text]) {
throw Error("Duplicate name of struct");
}
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") {
if (ctx.environment[item.name.text]) {
throw Error("Duplicate name of trait");
}
const functions: Record<string, FunctionTypeUsage> = {};
for (const fn of item.functions) {
functions[fn.name.text] = functionToType(fn);
}
ctx.environment[item.name.text] = {
namedEntity: "NamedType",
isA: "Trait",
fields: {},
impls: [
{
trait: item.name.text,
functions: functions,
},
],
};
}
if (item.moduleItem === "Function") {
if (ctx.environment[item.declaration.name.text]) {
throw Error("Duplicate name of function");
}
ctx.environment[item.declaration.name.text] = {
namedEntity: "Variable",
type: functionToType(item.declaration),
};
}
}
// 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<string, FunctionTypeUsage> = {};
for (const fn of item.functions) {
functions[fn.declaration.name.text] = functionToType(fn.declaration);
}
struct.impls.push({
trait: item.trait?.name.text ?? null,
functions: functions,
});
}
}
typeSystem.context = deepCopy(ctx);
// environment set up, actually recurse.
for (const item of module.items) {
if (item.moduleItem === "Function") {
this.withFunction(ctx, item, typeSystem);
}
if (item.moduleItem === "Impl") {
this.withImpl(ctx, item, typeSystem);
}
if (item.moduleItem === "StructTypeDeclaration") {
this.withStructDeclaration(ctx, item, typeSystem);
}
if (item.moduleItem === "TraitTypeDeclaration") {
this.withTrait(ctx, item, typeSystem);
}
}
};
withFunction = (ctx: Context, fn: Function, typeSystem: TypeSystem) => {
this.withFunctionDeclaration(ctx, fn.declaration, typeSystem);
const fnCtx = deepCopy(ctx);
fnCtx.currentFunctionReturn = fn.declaration.returnType;
for (const arg of fn.declaration.arguments) {
fnCtx.environment[arg.name.text] = {
namedEntity: "Variable",
type: arg.type,
};
}
this.withBlock(fnCtx, fn.block, typeSystem);
typeSystem.compare({
left: fn.declaration.returnType,
operation: { operation: "equals" },
right: fn.block.type,
});
};
withFunctionDeclaration = (ctx: Context, def: FunctionDeclaration, typeSystem: TypeSystem) => {
for (const arg of def.arguments) {
typeExists(ctx, arg.type);
}
typeExists(ctx, def.returnType);
};
withImpl = (ctx: Context, impl: Impl, typeSystem: TypeSystem) => {
for (const fn of impl.functions) {
this.withFunction(ctx, fn, typeSystem);
}
};
withStructDeclaration = (ctx: Context, struct: StructTypeDeclaration, typeSystem: TypeSystem) => {
for (const field of struct.fields) {
typeExists(ctx, field.type);
}
};
withTrait = (ctx: Context, trait: TraitTypeDeclaration, typeSystem: TypeSystem) => {
for (const method of trait.functions) {
this.withFunctionDeclaration(ctx, method, typeSystem);
}
};
withBlock = (ctx: Context, block: Block, typeSystem: TypeSystem) => {
const blockCtx = deepCopy(ctx);
for (const statement of block.statements) {
if (statement.statementType === "AssignmentStatement") {
this.withAssignmentStatement(blockCtx, statement, typeSystem);
}
if (statement.statementType === "LetStatement") {
this.withLetStatement(blockCtx, statement, typeSystem);
blockCtx.environment[statement.variableName.text] = {
namedEntity: "Variable",
type: statement.type,
};
}
if (statement.statementType === "Expression") {
this.withExpression(blockCtx, statement, typeSystem);
}
if (statement.statementType === "ReturnStatement") {
this.withReturnStatement(blockCtx, statement, typeSystem);
}
if (!containsReturn(block)) {
const lastStatement = block.statements[block.statements.length - 1] ?? null;
if (lastStatement && lastStatement.statementType == "Expression") {
typeSystem.compare({
left: block.type,
operation: { operation: "equals" },
right: lastStatement.type,
});
} else {
typeSystem.compare({
left: block.type,
operation: { operation: "equals" },
right: newVoid(),
});
}
}
}
};
withAssignmentStatement = (
ctx: Context,
statement: AssignmentStatement,
typeSystem: 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.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);
};
withLetStatement = (ctx: Context, statement: LetStatement, typeSystem: TypeSystem) => {
this.withExpression(ctx, statement.expression, typeSystem);
typeSystem.compare({
left: statement.type,
operation: { operation: "equals" },
right: statement.expression.type,
});
};
withReturnStatement = (ctx: Context, statement: ReturnStatement, typeSystem: TypeSystem) => {
this.withExpression(ctx, statement.source, typeSystem);
if (ctx.currentFunctionReturn) {
typeSystem.compare({
left: ctx.currentFunctionReturn,
operation: { operation: "equals" },
right: statement.source.type,
});
}
};
withExpression = (ctx: Context, expression: Expression, typeSystem: TypeSystem) => {
if (expression.subExpression.expressionType === "LiteralInt") {
// LiteralInt always has type
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "LiteralFloat") {
// LiteralFloat always has type
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "LiteralString") {
// LiteralString always has type
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "LiteralBool") {
// LiteralBool always has type
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "LiteralStruct") {
this.withLiteralStruct(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "FunctionCall") {
this.withFunctionCall(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "Path") {
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: this.withPath(ctx, expression.subExpression),
});
}
if (expression.subExpression.expressionType === "IfExpression") {
this.withIfExpression(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "StructGetter") {
this.withStructGetter(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "Block") {
this.withBlock(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
if (expression.subExpression.expressionType === "Operation") {
this.withOperation(ctx, expression.subExpression, typeSystem);
typeSystem.compare({
left: expression.type,
operation: { operation: "equals" },
right: expression.subExpression.type,
});
}
};
withLiteralStruct = (ctx: Context, literal: LiteralStruct, typeSystem: TypeSystem) => {
const definition = ctx.environment[literal.name.text];
if (!definition || definition.namedEntity !== "NamedType" || !(definition.isA === "Struct")) {
throw new Error(`${literal.name.text} not found.`);
}
if (Object.keys(definition.fields).length !== literal.fields.length) {
throw new Error(`${literal.name.text} has mismatched fields.`);
}
if (new Set(Object.keys(definition.fields)).size !== literal.fields.length) {
throw new Error(`${literal.name.text} has repeated fields.`);
}
for (const field of literal.fields) {
const definitionField = definition.fields[field.name.text];
if (!definitionField) throw new Error(`Unknown field ${field.name.text}`);
this.withExpression(ctx, field.expression, typeSystem);
typeSystem.compare({
left: definitionField,
operation: { operation: "equals" },
right: field.expression.type,
});
}
typeSystem.compare({
left: { typeUsage: "NamedTypeUsage", name: literal.name },
operation: { operation: "equals" },
right: literal.type,
});
};
withFunctionCall = (ctx: Context, fnCall: FunctionCall, typeSystem: TypeSystem) => {
this.withExpression(ctx, fnCall.source, typeSystem);
typeSystem.compare({
left: fnCall.source.type,
operation: { operation: "equals" },
right: {
typeUsage: "FunctionTypeUsage",
arguments: fnCall.arguments.map((arg) => arg.type),
returnType: fnCall.type,
},
});
for (const [i, arg] of fnCall.arguments.entries()) {
this.withExpression(ctx, arg, typeSystem);
}
};
withPath = (ctx: Context, path: Path): TypeUsage => {
const pathList: Identifier[] = [];
while (path.value.type == "Nested") {
pathList.unshift(path.value.name);
path = path.value.parent;
}
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) => {
this.withExpression(ctx, ifExpression.condition, typeSystem);
typeSystem.compare({
left: { typeUsage: "NamedTypeUsage", name: { text: "bool", spanStart: 0, spanEnd: 0 } },
operation: { operation: "equals" },
right: ifExpression.condition.type,
});
this.withBlock(ctx, ifExpression.block, typeSystem);
typeSystem.compare({
left: ifExpression.block.type,
operation: { operation: "equals" },
right: ifExpression.type,
});
if (ifExpression.else) {
this.withBlock(ctx, ifExpression.else, typeSystem);
typeSystem.compare({
left: ifExpression.else.type,
operation: { operation: "equals" },
right: ifExpression.type,
});
}
};
withStructGetter = (ctx: Context, structGetter: StructGetter, typeSystem: TypeSystem) => {
this.withExpression(ctx, structGetter.source, typeSystem);
typeSystem.compare({
left: structGetter.source.type,
operation: { operation: "field", name: structGetter.attribute.text },
right: structGetter.type,
});
};
withOperation = (ctx: Context, op: Operation, typeSystem: TypeSystem) => {
this.withExpression(ctx, op.left, typeSystem);
this.withExpression(ctx, op.right, typeSystem);
typeSystem.compare({
left: op.left.type,
operation: { operation: "equals" },
right: op.type,
});
typeSystem.compare({
left: op.right.type,
operation: { operation: "equals" },
right: op.type,
});
};
}

View File

@@ -0,0 +1,205 @@
import {
AssignmentStatement,
Block,
containsReturn,
Expression,
Function,
FunctionCall,
FunctionDeclaration,
functionToType,
IfExpression,
Impl,
LetStatement,
LiteralStruct,
Module,
newVoid,
Operation,
ReturnStatement,
StructGetter,
StructTypeDeclaration,
TraitTypeDeclaration,
TypeUsage,
Path,
} from "../parse/ast";
import { newContext } from "./builtins";
import { Context, deepCopy, typeExists } from "./context";
import { TypeSystem } from "./type_system";
export class TypeResolver {
withModule = (module: Module, typeSystem: TypeSystem) => {
const result = deepCopy(module);
// environment set up, actually recurse.
for (const [i, item] of module.items.entries()) {
if (item.moduleItem === "Function") {
result.items[i] = this.withFunction(item, typeSystem);
}
if (item.moduleItem === "Impl") {
result.items[i] = this.withImpl(item, typeSystem);
}
if (item.moduleItem === "StructTypeDeclaration") {
result.items[i] = this.withStructDeclaration(item, typeSystem);
}
if (item.moduleItem === "TraitTypeDeclaration") {
result.items[i] = this.withTrait(item, typeSystem);
}
}
return result;
};
withFunction = (fn: Function, typeSystem: TypeSystem) => {
const result = deepCopy(fn);
result.declaration = this.withFunctionDeclaration(fn.declaration, typeSystem);
result.block = this.withBlock(fn.block, typeSystem);
return result;
};
withFunctionDeclaration = (def: FunctionDeclaration, typeSystem: TypeSystem) => {
const result = deepCopy(def);
result.arguments = def.arguments.map((arg) => {
const resultArg = deepCopy(arg);
resultArg.type = typeSystem.resolveType(arg.type);
return resultArg;
});
return result;
};
withImpl = (impl: Impl, typeSystem: TypeSystem) => {
const result = deepCopy(impl);
for (const [i, fn] of impl.functions.entries()) {
result.functions[i] = this.withFunction(fn, typeSystem);
}
return result;
};
withStructDeclaration = (struct: StructTypeDeclaration, typeSystem: TypeSystem) => {
const result = deepCopy(struct);
for (const [i, field] of struct.fields.entries()) {
result.fields[i] = { name: field.name, type: typeSystem.resolveType(field.type) };
}
return result;
};
withTrait = (trait: TraitTypeDeclaration, typeSystem: TypeSystem) => {
const result = deepCopy(trait);
for (const [i, method] of trait.functions.entries()) {
result.functions[i] = this.withFunctionDeclaration(method, typeSystem);
}
return result;
};
withBlock = (block: Block, typeSystem: TypeSystem) => {
const result = deepCopy(block);
for (const [i, statement] of block.statements.entries()) {
if (statement.statementType === "AssignmentStatement") {
result.statements[i] = this.withAssignmentStatement(statement, typeSystem);
}
if (statement.statementType === "LetStatement") {
result.statements[i] = this.withLetStatement(statement, typeSystem);
}
if (statement.statementType === "Expression") {
result.statements[i] = this.withExpression(statement, typeSystem);
}
if (statement.statementType === "ReturnStatement") {
result.statements[i] = this.withReturnStatement(statement, typeSystem);
}
result.type = typeSystem.resolveType(block.type);
}
return result;
};
withAssignmentStatement = (statement: AssignmentStatement, typeSystem: TypeSystem) => {
const result = deepCopy(statement);
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;
};
withLetStatement = (statement: LetStatement, typeSystem: TypeSystem) => {
const result = deepCopy(statement);
result.expression = this.withExpression(statement.expression, typeSystem);
result.type = typeSystem.resolveType(statement.type);
return result;
};
withReturnStatement = (statement: ReturnStatement, typeSystem: TypeSystem) => {
const result = deepCopy(statement);
result.source = this.withExpression(statement.source, typeSystem);
return result;
};
withExpression = (expression: Expression, typeSystem: TypeSystem) => {
const result = deepCopy(expression);
result.type = typeSystem.resolveType(expression.type);
if (expression.subExpression.expressionType === "LiteralStruct") {
result.subExpression = this.withLiteralStruct(expression.subExpression, typeSystem);
}
if (expression.subExpression.expressionType === "FunctionCall") {
result.subExpression = this.withFunctionCall(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);
}
if (expression.subExpression.expressionType === "StructGetter") {
result.subExpression = this.withStructGetter(expression.subExpression, typeSystem);
}
if (expression.subExpression.expressionType === "Block") {
result.subExpression = this.withBlock(expression.subExpression, typeSystem);
}
if (expression.subExpression.expressionType === "Operation") {
result.subExpression = this.withOperation(expression.subExpression, typeSystem);
}
return result;
};
withLiteralStruct = (literal: LiteralStruct, typeSystem: TypeSystem) => {
const result = deepCopy(literal);
for (const [i, field] of literal.fields.entries()) {
result.fields[i].expression = this.withExpression(field.expression, typeSystem);
}
result.type = typeSystem.resolveType(literal.type);
return result;
};
withFunctionCall = (fnCall: FunctionCall, typeSystem: TypeSystem) => {
const result = deepCopy(fnCall);
result.type = typeSystem.resolveType(fnCall.type);
result.source = this.withExpression(fnCall.source, typeSystem);
for (const [i, arg] of fnCall.arguments.entries()) {
result.arguments[i] = this.withExpression(arg, typeSystem);
}
return result;
};
withIfExpression = (ifExpression: IfExpression, typeSystem: TypeSystem) => {
const result = deepCopy(ifExpression);
result.type = typeSystem.resolveType(ifExpression.type);
result.condition = this.withExpression(ifExpression.condition, typeSystem);
result.block = this.withBlock(ifExpression.block, typeSystem);
result.else = ifExpression.else ? this.withBlock(ifExpression.else, typeSystem) : null;
return result;
};
withStructGetter = (structGetter: StructGetter, typeSystem: TypeSystem) => {
const result = deepCopy(structGetter);
result.type = typeSystem.resolveType(structGetter.type);
result.source = this.withExpression(structGetter.source, typeSystem);
return result;
};
withOperation = (op: Operation, typeSystem: TypeSystem) => {
const result = deepCopy(op);
result.type = typeSystem.resolveType(op.type);
result.left = this.withExpression(op.left, typeSystem);
result.right = this.withExpression(op.right, typeSystem);
return result;
};
}

View File

@@ -0,0 +1,203 @@
import { FunctionTypeUsage, TypeUsage, UnknownTypeUsage } from "../parse/ast";
import { newContext } from "./builtins";
import { Context, deepCopy, getAttr } from "./context";
export const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => {
if (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") {
// never matches with everything
return;
}
if (typeA.name.text !== typeB.name.text) {
throw Error(
`Mismatched types: ${typeA.name.text}:${typeA.name.spanStart}:${typeA.name.spanEnd} ${typeB.name.text}:${typeB.name.spanStart}:${typeB.name.spanEnd}`,
);
}
}
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);
}
};
interface Equals {
operation: "equals";
}
interface Field {
operation: "field";
name: string;
}
interface Comparison {
left: TypeUsage;
operation: Equals | Field;
right: TypeUsage;
}
export class TypeSystem {
comparisons: Comparison[];
result: Record<string, TypeUsage>;
context: Context;
constructor() {
this.comparisons = [];
this.result = {};
this.context = newContext();
}
compare = (comparison: Comparison) => {
this.comparisons.push(comparison);
};
solve = () => {
let foundUpdate = false;
while (true) {
foundUpdate = false;
for (const comparison of this.comparisons) {
// if already found, just update
comparison.left = this.resolveType(comparison.left);
comparison.right = this.resolveType(comparison.right);
// equals
if (comparison.operation.operation === "equals") {
const [result, found] = this.equateTypes(comparison.left, comparison.right);
if (found) {
comparison.left = result;
comparison.right = result;
foundUpdate = true;
}
// check
if (
comparison.left.typeUsage !== "UnknownTypeUsage" &&
comparison.right.typeUsage !== "UnknownTypeUsage"
) {
compareTypes(comparison.left, comparison.right);
}
}
// field
if (comparison.operation.operation === "field") {
if (comparison.left.typeUsage === "UnknownTypeUsage") {
// cannot yet be resolved
continue;
}
if (comparison.left.typeUsage !== "NamedTypeUsage") {
throw Error("field on something that isn't a named type.");
}
// cannot be solved left
// solve right
if (comparison.right.typeUsage === "UnknownTypeUsage") {
foundUpdate = true;
const attrType = getAttr(
this.context,
comparison.left.name.text,
comparison.operation.name,
);
const [result, found] = this.equateTypes(attrType, comparison.right);
if (found) {
comparison.right = result;
foundUpdate = true;
}
}
// check
if (comparison.right.typeUsage !== "UnknownTypeUsage") {
const attrType = getAttr(
this.context,
comparison.left.name.text,
comparison.operation.name,
);
compareTypes(attrType, comparison.right);
}
}
}
let containsUnknown = false;
for (const comparison of this.comparisons) {
if (this.containsUnknown(comparison.left) || this.containsUnknown(comparison.right)) {
containsUnknown = true;
}
}
if (!foundUpdate && containsUnknown) {
throw Error("Type system failed to resolve all unknowns");
}
if (!foundUpdate) {
break;
}
}
return this.result;
};
equateTypes = (left: TypeUsage, right: TypeUsage): [TypeUsage, boolean] => {
if (left.typeUsage === "UnknownTypeUsage" && right.typeUsage !== "UnknownTypeUsage") {
this.result[left.name] = right;
return [right, true];
}
if (left.typeUsage !== "UnknownTypeUsage" && right.typeUsage === "UnknownTypeUsage") {
this.result[right.name] = left;
return [left, true];
}
if (left.typeUsage === "FunctionTypeUsage" && right.typeUsage === "FunctionTypeUsage") {
if (left.arguments.length !== right.arguments.length) {
throw Error(`Mismatched arg lengths: ${left.arguments.length} ${right.arguments.length}`);
}
let found = false;
let fnResult: FunctionTypeUsage = deepCopy(left);
for (let i = 0; i < left.arguments.length; i++) {
const [result, wasFound] = this.equateTypes(left.arguments[i], right.arguments[i]);
if (wasFound) {
found = true;
}
fnResult.arguments[i] = result;
}
const [result, wasFound] = this.equateTypes(left.returnType, right.returnType);
if (wasFound) {
found = true;
}
fnResult.returnType = result;
return [fnResult, found];
}
return [left, false];
};
resolveType = (type: TypeUsage): TypeUsage => {
if (type.typeUsage === "UnknownTypeUsage") {
if (this.result[type.name]) {
return this.result[type.name];
}
return type;
}
if (type.typeUsage === "NamedTypeUsage") {
return type;
}
return {
typeUsage: "FunctionTypeUsage",
arguments: type.arguments.map((arg) => this.resolveType(arg)),
returnType: this.resolveType(type.returnType),
};
};
containsUnknown = (type: TypeUsage) => {
if (type.typeUsage === "UnknownTypeUsage") {
return true;
}
if (type.typeUsage === "FunctionTypeUsage") {
for (const arg of type.arguments) {
if (this.containsUnknown(arg)) {
return true;
}
}
if (this.containsUnknown(type.returnType)) {
return true;
}
}
return false;
};
}