Compare commits
11 Commits
90381840af
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cad512010 | |||
| 4e981a69a8 | |||
| 126524a9e9 | |||
| dd4f5b9ee6 | |||
| 66c7864df0 | |||
| b2709ffc82 | |||
| 0a315c5615 | |||
| 68e51cf8aa | |||
| 982603aa54 | |||
| 05856f5d07 | |||
| df1083df3b |
@@ -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();
|
||||||
38
goal-examples/webapp/main.bl
Normal file
38
goal-examples/webapp/main.bl
Normal 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;
|
||||||
|
}
|
||||||
33
goal-examples/webapp/models.bl
Normal file
33
goal-examples/webapp/models.bl
Normal 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;
|
||||||
|
}
|
||||||
45
goal-examples/webapp/router.bl
Normal file
45
goal-examples/webapp/router.bl
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
|||||||
92
packages/boringlang/src/interpreter/builtins.ts
Normal file
92
packages/boringlang/src/interpreter/builtins.ts
Normal 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;
|
||||||
|
}
|
||||||
74
packages/boringlang/src/interpreter/context.ts
Normal file
74
packages/boringlang/src/interpreter/context.ts
Normal 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;
|
||||||
|
}
|
||||||
345
packages/boringlang/src/interpreter/index.ts
Normal file
345
packages/boringlang/src/interpreter/index.ts
Normal 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`);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -57,16 +57,18 @@ export interface StructGetter {
|
|||||||
type: TypeUsage;
|
type: TypeUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Path {
|
||||||
|
expressionType: "Path";
|
||||||
|
value:
|
||||||
|
| { type: "Identifier"; name: Identifier }
|
||||||
|
| { type: "Nested"; parent: Path; name: Identifier };
|
||||||
|
}
|
||||||
|
|
||||||
export interface Operation {
|
export interface Operation {
|
||||||
expressionType: "Operation";
|
expressionType: "Operation";
|
||||||
left: Expression;
|
left: Expression;
|
||||||
op: "+" | "-" | "*" | "/";
|
op: "+" | "-" | "*" | "/";
|
||||||
right: Expression;
|
right: Expression;
|
||||||
}
|
|
||||||
|
|
||||||
export interface VariableUsage {
|
|
||||||
expressionType: "VariableUsage";
|
|
||||||
name: Identifier;
|
|
||||||
type: TypeUsage;
|
type: TypeUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +89,8 @@ export interface Expression {
|
|||||||
| LiteralString
|
| LiteralString
|
||||||
| LiteralStruct
|
| LiteralStruct
|
||||||
| FunctionCall
|
| FunctionCall
|
||||||
| VariableUsage
|
|
||||||
| IfExpression
|
| IfExpression
|
||||||
|
| Path
|
||||||
| StructGetter
|
| StructGetter
|
||||||
| Block
|
| Block
|
||||||
| Operation;
|
| Operation;
|
||||||
@@ -109,17 +111,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;
|
||||||
}
|
}
|
||||||
@@ -141,6 +140,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;
|
||||||
@@ -192,3 +199,74 @@ export interface UnknownTypeUsage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TypeUsage = NamedTypeUsage | FunctionTypeUsage | UnknownTypeUsage;
|
export type TypeUsage = NamedTypeUsage | FunctionTypeUsage | UnknownTypeUsage;
|
||||||
|
|
||||||
|
export const newVoid: () => TypeUsage = () => {
|
||||||
|
return {
|
||||||
|
typeUsage: "NamedTypeUsage",
|
||||||
|
name: { text: "Void", spanStart: 0, spanEnd: 0 },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const newNever: () => TypeUsage = () => {
|
||||||
|
return {
|
||||||
|
typeUsage: "NamedTypeUsage",
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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 = identifier "=" Expression ";" -- identifier
|
||||||
| StructGetter "=" Expression ";"
|
| StructGetter "=" Expression ";" -- getter
|
||||||
ExpressionStatement = Expression ";"
|
ExpressionStatement = Expression ";"
|
||||||
Statement = ExpressionStatement
|
Statement = ExpressionStatement
|
||||||
| LetStatement
|
| LetStatement
|
||||||
@@ -19,27 +19,33 @@ export const boringGrammar = ohm.grammar(String.raw`
|
|||||||
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, ","> ")"
|
|
||||||
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 -- nested
|
||||||
|
| identifier -- 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 -- structGetter
|
||||||
| Expression "-" Factor -- minus
|
| StructExpression
|
||||||
| StructGetter
|
CallExpression = CallExpression "." identifier -- 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
|
||||||
TypeUsage = NamedTypeUsage
|
TypeUsage = NamedTypeUsage
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AssignmentStatement,
|
AssignmentStatement,
|
||||||
Block,
|
Block,
|
||||||
|
containsReturn,
|
||||||
Expression,
|
Expression,
|
||||||
Function,
|
Function,
|
||||||
FunctionArgument,
|
FunctionArgument,
|
||||||
@@ -19,7 +20,9 @@ import {
|
|||||||
Module,
|
Module,
|
||||||
ModuleItem,
|
ModuleItem,
|
||||||
NamedTypeUsage,
|
NamedTypeUsage,
|
||||||
|
newNever,
|
||||||
Operation,
|
Operation,
|
||||||
|
Path,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
Statement,
|
Statement,
|
||||||
StructField,
|
StructField,
|
||||||
@@ -29,7 +32,6 @@ import {
|
|||||||
TraitTypeDeclaration,
|
TraitTypeDeclaration,
|
||||||
TypeDeclaration,
|
TypeDeclaration,
|
||||||
TypeUsage,
|
TypeUsage,
|
||||||
VariableUsage,
|
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { boringGrammar } from "./grammar";
|
import { boringGrammar } from "./grammar";
|
||||||
|
|
||||||
@@ -42,46 +44,60 @@ function nextUnknown() {
|
|||||||
|
|
||||||
export const semantics = boringGrammar.createSemantics();
|
export const semantics = boringGrammar.createSemantics();
|
||||||
semantics.addOperation<any>("toAST", {
|
semantics.addOperation<any>("toAST", {
|
||||||
LiteralInt(a): LiteralInt {
|
LiteralInt(a): Expression {
|
||||||
console.log(this);
|
|
||||||
console.log(a.source.startIdx);
|
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "LiteralInt",
|
expressionType: "LiteralInt",
|
||||||
value: this.sourceString,
|
value: this.sourceString,
|
||||||
type: {
|
type: {
|
||||||
typeUsage: "NamedTypeUsage",
|
typeUsage: "NamedTypeUsage",
|
||||||
name: { name: "i64", spanStart: 0, spanEnd: 0 },
|
name: { text: "i64", spanStart: 0, spanEnd: 0 },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
LiteralFloat(_1, _2, _3): LiteralFloat {
|
LiteralFloat(_1, _2, _3): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "LiteralFloat",
|
expressionType: "LiteralFloat",
|
||||||
value: this.sourceString,
|
value: this.sourceString,
|
||||||
type: {
|
type: {
|
||||||
typeUsage: "NamedTypeUsage",
|
typeUsage: "NamedTypeUsage",
|
||||||
name: { name: "f64", spanStart: 0, spanEnd: 0 },
|
name: { text: "f64", spanStart: 0, spanEnd: 0 },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
LiteralBool(_): LiteralBool {
|
LiteralBool(_): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "LiteralBool",
|
expressionType: "LiteralBool",
|
||||||
value: this.sourceString,
|
value: this.sourceString,
|
||||||
type: {
|
type: {
|
||||||
typeUsage: "NamedTypeUsage",
|
typeUsage: "NamedTypeUsage",
|
||||||
name: { name: "bool", spanStart: 0, spanEnd: 0 },
|
name: { text: "bool", spanStart: 0, spanEnd: 0 },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
LiteralString(_1, text, _3): LiteralString {
|
LiteralString(_1, text, _3): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "LiteralString",
|
expressionType: "LiteralString",
|
||||||
value: text.sourceString,
|
value: text.sourceString,
|
||||||
type: {
|
type: {
|
||||||
typeUsage: "NamedTypeUsage",
|
typeUsage: "NamedTypeUsage",
|
||||||
name: { name: "String", spanStart: 0, spanEnd: 0 },
|
name: { text: "String", spanStart: 0, spanEnd: 0 },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
LiteralStructField(identifier, _2, expression): StructField {
|
LiteralStructField(identifier, _2, expression): StructField {
|
||||||
@@ -90,98 +106,202 @@ semantics.addOperation<any>("toAST", {
|
|||||||
expression: expression.toAST(),
|
expression: expression.toAST(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
LiteralStruct(identifier, _2, fields, _4): LiteralStruct {
|
LiteralStruct(identifier, _2, fields, _4): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "LiteralStruct",
|
expressionType: "LiteralStruct",
|
||||||
name: identifier.toAST(),
|
name: identifier.toAST(),
|
||||||
fields: fields.asIteration().children.map((c) => c.toAST()),
|
fields: fields.asIteration().children.map((c) => c.toAST()),
|
||||||
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() },
|
type: { typeUsage: "NamedTypeUsage", 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): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "StructGetter",
|
expressionType: "StructGetter",
|
||||||
source: expression.toAST(),
|
source: expression.toAST(),
|
||||||
attribute: identifier.toAST(),
|
attribute: identifier.toAST(),
|
||||||
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
|
||||||
},
|
},
|
||||||
VariableUsage(identifier): VariableUsage {
|
|
||||||
return {
|
|
||||||
expressionType: "VariableUsage",
|
|
||||||
name: identifier.toAST(),
|
|
||||||
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
IfExpression(_1, _2, expression, _4, block, _6, elseBlock): IfExpression {
|
CallExpression(expression): Expression {
|
||||||
|
return expression.toAST();
|
||||||
|
},
|
||||||
|
CallExpression_structGetter(expression, _2, identifier): Expression {
|
||||||
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
|
expressionType: "StructGetter",
|
||||||
|
source: expression.toAST(),
|
||||||
|
attribute: identifier.toAST(),
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
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(),
|
||||||
|
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 {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "IfExpression",
|
expressionType: "IfExpression",
|
||||||
condition: expression.toAST(),
|
condition: expression.toAST(),
|
||||||
block: block.toAST(),
|
block: block.toAST(),
|
||||||
else: eb.length > 0 ? eb[0] : null,
|
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): Path {
|
||||||
return term.toAST();
|
return {
|
||||||
|
expressionType: "Path",
|
||||||
|
value: { type: "Identifier", name: identifier.toAST() },
|
||||||
|
};
|
||||||
},
|
},
|
||||||
Term_parens(_1, term, _3): Expression {
|
Path_nested(basePath, _2, attrIdent): Path {
|
||||||
return term.toAST();
|
return {
|
||||||
|
expressionType: "Path",
|
||||||
|
value: { type: "Nested", parent: basePath.toAST(), name: attrIdent.toAST() },
|
||||||
|
};
|
||||||
},
|
},
|
||||||
Factor(factor): Expression {
|
MultExpression(expression): Expression {
|
||||||
return factor.toAST();
|
|
||||||
},
|
|
||||||
Expression(expression): Expression {
|
|
||||||
return expression.toAST();
|
return expression.toAST();
|
||||||
},
|
},
|
||||||
Expression_plus(expression, _2, factor): Operation {
|
MultExpression_mult(factor, _2, term): Expression {
|
||||||
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 {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "Operation",
|
expressionType: "Operation",
|
||||||
left: factor.toAST(),
|
left: factor.toAST(),
|
||||||
op: "*",
|
op: "*",
|
||||||
right: term.toAST(),
|
right: term.toAST(),
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Factor_div(factor, _2, term): Operation {
|
MultExpression_div(factor, _2, term): Expression {
|
||||||
return {
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
expressionType: "Operation",
|
expressionType: "Operation",
|
||||||
left: factor.toAST(),
|
left: factor.toAST(),
|
||||||
op: "/",
|
op: "/",
|
||||||
right: term.toAST(),
|
right: term.toAST(),
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
AddExpression(expression): Expression {
|
||||||
|
return expression.toAST();
|
||||||
|
},
|
||||||
|
AddExpression_plus(expression, _2, factor): Expression {
|
||||||
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
|
expressionType: "Operation",
|
||||||
|
left: expression.toAST(),
|
||||||
|
op: "+",
|
||||||
|
right: factor.toAST(),
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
AddExpression_minus(expression, _2, factor): Expression {
|
||||||
|
return {
|
||||||
|
statementType: "Expression",
|
||||||
|
subExpression: {
|
||||||
|
expressionType: "Operation",
|
||||||
|
left: expression.toAST(),
|
||||||
|
op: "-",
|
||||||
|
right: factor.toAST(),
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
},
|
||||||
|
type: { typeUsage: "UnknownTypeUsage", name: nextUnknown() },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Expression(expression): Expression {
|
||||||
|
return expression.toAST();
|
||||||
|
},
|
||||||
Statement(statement): Statement {
|
Statement(statement): Statement {
|
||||||
return statement.toAST();
|
return statement.toAST();
|
||||||
},
|
},
|
||||||
@@ -197,16 +317,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,11 +344,18 @@ 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): NamedTypeUsage {
|
||||||
return {
|
return {
|
||||||
@@ -248,15 +379,7 @@ semantics.addOperation<any>("toAST", {
|
|||||||
type: typeUsage.toAST(),
|
type: typeUsage.toAST(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
FunctionDeclaration(
|
FunctionDeclaration(_1, identifier, _3, args, _4, _5, returnType): FunctionDeclaration {
|
||||||
_1,
|
|
||||||
identifier,
|
|
||||||
_3,
|
|
||||||
args,
|
|
||||||
_4,
|
|
||||||
_5,
|
|
||||||
returnType,
|
|
||||||
): FunctionDeclaration {
|
|
||||||
return {
|
return {
|
||||||
name: identifier.toAST(),
|
name: identifier.toAST(),
|
||||||
arguments: args.asIteration().children.map((c) => c.toAST()),
|
arguments: args.asIteration().children.map((c) => c.toAST()),
|
||||||
@@ -276,14 +399,7 @@ semantics.addOperation<any>("toAST", {
|
|||||||
type: typeUsage.toAST(),
|
type: typeUsage.toAST(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
StructTypeDeclaration(
|
StructTypeDeclaration(_1, identifier, _3, _4, fields, _6): StructTypeDeclaration {
|
||||||
_1,
|
|
||||||
identifier,
|
|
||||||
_3,
|
|
||||||
_4,
|
|
||||||
fields,
|
|
||||||
_6,
|
|
||||||
): StructTypeDeclaration {
|
|
||||||
return {
|
return {
|
||||||
moduleItem: "StructTypeDeclaration",
|
moduleItem: "StructTypeDeclaration",
|
||||||
typeDeclaration: "StructTypeDeclaration",
|
typeDeclaration: "StructTypeDeclaration",
|
||||||
@@ -294,14 +410,7 @@ semantics.addOperation<any>("toAST", {
|
|||||||
TraitMethod(declaration, _2): FunctionDeclaration {
|
TraitMethod(declaration, _2): FunctionDeclaration {
|
||||||
return declaration.toAST();
|
return declaration.toAST();
|
||||||
},
|
},
|
||||||
TraitTypeDeclaration(
|
TraitTypeDeclaration(_1, identifier, _3, _4, methods, _5): TraitTypeDeclaration {
|
||||||
_1,
|
|
||||||
identifier,
|
|
||||||
_3,
|
|
||||||
_4,
|
|
||||||
methods,
|
|
||||||
_5,
|
|
||||||
): TraitTypeDeclaration {
|
|
||||||
return {
|
return {
|
||||||
moduleItem: "TraitTypeDeclaration",
|
moduleItem: "TraitTypeDeclaration",
|
||||||
typeDeclaration: "TraitTypeDeclaration",
|
typeDeclaration: "TraitTypeDeclaration",
|
||||||
|
|||||||
21
packages/boringlang/src/types/builtins.ts
Normal file
21
packages/boringlang/src/types/builtins.ts
Normal 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;
|
||||||
|
}
|
||||||
95
packages/boringlang/src/types/context.ts
Normal file
95
packages/boringlang/src/types/context.ts
Normal 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) {
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -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}`,
|
||||||
);
|
);
|
||||||
|
|||||||
233
packages/boringlang/src/types/type_alias_resolution.ts
Normal file
233
packages/boringlang/src/types/type_alias_resolution.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
455
packages/boringlang/src/types/type_checker.ts
Normal file
455
packages/boringlang/src/types/type_checker.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
205
packages/boringlang/src/types/type_resolver.ts
Normal file
205
packages/boringlang/src/types/type_resolver.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
203
packages/boringlang/src/types/type_system.ts
Normal file
203
packages/boringlang/src/types/type_system.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user