const std = @import("std"); const token = @import("token.zig"); const TokenType = token.TokenType; const Token = token.Token; const Lexer = @import("lexer.zig").Lexer; const AST = @import("ast.zig"); const Common = @import("common.zig"); const Str = Common.Str; const Loc = Common.Location; const UniqueGen = @import("UniqueGen.zig"); const Unique = UniqueGen.Unique; const @"error" = @import("error.zig"); const Error = @"error".Error; const Errors = @"error".Errors; const stack = @import("stack.zig"); const TypeContext = @import("TypeContext.zig"); const Prelude = @import("Prelude.zig"); const Set = @import("Set.zig").Set; const Module = @import("Module.zig"); const Modules = @import("Modules.zig"); const Intrinsic = @import("Intrinsic.zig"); // fuck it. let's do it one pass. arena: std.mem.Allocator, errors: *Errors, name: Str, // parser zone lexer: Lexer, currentToken: Token, mode: ParsingMode, // A bit of a hack. I think we should utilize the zigger's polymorphism to make different parsers (akin to the type parser) // resolver zone gen: *Modules.Gen, scope: Scope, env: ?AST.EnvFun, base: Module.BasePath, topLevels: std.ArrayList(AST.Function.Use), // type zone typeContext: *TypeContext, prelude: ?Prelude, returnType: ?AST.Type, returned: ReturnStatus, // statement level check if something was returned. triedReturningAtAll: bool, // function level check if we even tried returning something. if this is true, returnType must not be null. selfType: ?AST.Type, // this is used in class and instance context. Whenever it's defined, the user is able to reference the instance type by '_'. In nested instances, obviously points to the innermost one. associations: std.ArrayList(Association), modules: *Modules, importedModules: Imports, const ReturnStatus = enum { Nah, Returned, Errored, fn alternative(self: @This(), other: @This()) @This() { if (self == .Nah or other == .Nah) return .Nah; if (self == .Errored or other == .Errored) return .Errored; return .Returned; } }; const Imports = std.HashMap(Module.Path, ?Module, Module.PathCtx, std.hash_map.default_max_load_percentage); const Self = @This(); pub fn init(l: Lexer, prelude: ?Prelude, base: Module.BasePath, moduleName: Str, modules: *Modules, errors: *Errors, context: *TypeContext, arena: std.mem.Allocator) !Self { var parser = Self{ .arena = arena, .errors = errors, // TODO: use GPA .name = moduleName, // parser .lexer = l, .currentToken = undefined, .mode = .{ .Simple = .Normal }, // resolver .scope = Scope.init(arena), // TODO: use GPA .env = null, .gen = &modules.gen, .base = base, .topLevels = std.ArrayList(AST.Function.Use).init(arena), // typeshit .typeContext = context, .returnType = null, .triedReturningAtAll = false, .returned = .Nah, .selfType = null, .prelude = prelude, .associations = std.ArrayList(Association).init(arena), .modules = modules, .importedModules = Imports.init(arena), }; parser.currentToken = parser.lexer.nextToken(); return parser; } const Export = struct { const ConsDef = union(enum) { Cons: []Token, AllCons, fn explicitlyHasConstructors(self: @This()) bool { return switch (self) { .Cons => |cons| cons.len > 0, .AllCons => false, }; } }; const Thing = union(enum) { Wildcard, VariableOrFunction: Token, Type: struct { typename: Token, constructors: ConsDef, }, }; externalModule: Module.Path, qualifierLoc: ?Loc, exportedThing: Thing, }; pub fn parse(self: *Self) !Module { var decs = std.ArrayList(*AST.Stmt).init(self.arena); self.consumeSeps(); // first collect exports // we must parse it, BUT EVALUATE IT AT THE END! var definedExports = std.ArrayList(Export).init(self.arena); if (self.check(.EXPORT)) { defer self.consumeSeps(); if (self.check(.INDENT)) { while (true) { const endedInIndent = try self.oneHotExport(&definedExports, &.{}); if (endedInIndent) { if (self.check(.DEDENT)) break; continue; } if (!self.check(.COMMA) or self.isEndStmt()) { try self.endStmt(); if (self.check(.DEDENT)) break; } } } else { while (true) { // maybe disallow indentation in a single export? const endedInIndent = try self.oneHotExport(&definedExports, &.{}); if (endedInIndent) break; // TODO(trailing commas) if (!self.check(.COMMA)) break; } } } while (self.consume(.EOF) == null) { const dec = self.statement() catch |e| { std.debug.print("Err {s}.\n", .{self.name}); // TEMP var fakeNewline: bool = undefined; const fakeHackCtx = AST.Ctx.init(&fakeNewline, self.typeContext); fakeNewline = false; // SIKE (but obv. temporary) for (self.modules.errors.items) |err| { err.err.print(fakeHackCtx, err.module); } return e; }; // consume statement separators self.consumeSeps(); if (dec != null) try decs.append(dec.?); } // dont add a return here, because we don't know if its the last file. try self.solveAvailableConstraintsAndApplyDefaultsIfPossible(); if (self.associations.items.len > 0) { try self.reportError(.{ .ConstraintsLeft = self.associations.items, }); } // export stuff. const exports = try self.exportListToExports(definedExports.items); return .{ .ast = AST{ .toplevel = decs.items }, .exports = exports, .calls = self.topLevels.items, }; } fn dataDef(self: *Self, typename: Token, tvarToks: []Token, annotations: []AST.Annotation) !void { const dataName = typename.literal(self.lexer.source); const data = try Common.allocOne(self.arena, AST.Data{ .uid = self.gen.vars.newUnique(), .name = dataName, .scheme = undefined, .outerTVars = undefined, // ERROR: when self referencing the type, there will be an undefined value. I guess we can make it a pointer, which we will instantiate later. .stuff = undefined, .annotations = annotations, }); b: { self.beginScope(); defer self.endScope(); // tvars var tvars = try std.ArrayList(AST.TVarOrNum).initCapacity(self.arena, tvarToks.len); for (tvarToks) |t| { if (t.type == .NUMTYNAME) { const numtv = try self.newTNum(t.literal(self.lexer.source)[1..], .{ .Data = data.uid }); try tvars.append(.{ .TNum = numtv }); } else if (t.type == .IDENTIFIER) { const tv = try self.newTVar(t.literal(self.lexer.source), .{ .Data = data.uid }); try tvars.append(.{ .TVar = tv }); } else { unreachable; } } const ddt = self.peek(); if (!(ddt.type == .COLON or ddt.type == .INDENT)) { // Add type without any constructors data.* = AST.Data{ .uid = data.uid, .name = data.name, .stuff = .{ .cons = &.{} }, .scheme = .{ .tvars = tvars.items, .envVars = &.{}, .associations = &.{}, }, .outerTVars = &.{}, .annotations = data.annotations, }; break :b; } self.skip(); var cons = std.ArrayList(AST.Con).init(self.arena); var recs = std.ArrayList(AST.Data.DecRecord).init(self.arena); var tag: u32 = 0; var assocs = std.ArrayList(AST.Association).init(self.arena); const tyconstr = Type.Constrain{ .Data = .{ .uid = data.uid, .assocs = &assocs } }; var ftvs = TypeContext.FTVs.init(self.arena, self.typeContext); // TODO: this is slow. We should add a pointer to an arraylist to the Type(..) constructor. FTV also does deduplication which is not needed here. var outerTVarSet = Set(AST.TVarOrNum, AST.TVarOrNum.comparator()).init(self.arena); if (ddt.type == .INDENT) { while (!self.check(.DEDENT)) { const conAnnotations = try self.parseAnnotation(); if (self.consume(.IDENTIFIER)) |recname| { // record const t = try Type.init(self, tyconstr).sepTyo(); try self.typeContext.ftvs(&ftvs, t.e); try self.typeContext.getOuterTVars(.{ .Data = data.uid }, &outerTVarSet, t.e); try recs.append(.{ .rec = .{ .field = recname.literal(self.lexer.source), .t = t.e, }, .anns = conAnnotations, }); try self.endStmt(); } else if (self.consume(.TYPE)) |conName| { // constructor var tys = std.ArrayList(AST.Type).init(self.arena); while (!(self.check(.STMT_SEP) or (self.peek().type == .DEDENT))) { // we must not consume the last DEDENT, as it's used to terminate the whole type declaration. const ty = try Type.init(self, tyconstr).typ(); try self.typeContext.ftvs(&ftvs, ty.e); try self.typeContext.getOuterTVars(.{ .Data = data.uid }, &outerTVarSet, ty.e); try tys.append(ty.e); } self.consumeSeps(); try cons.append(.{ .uid = self.gen.cons.newUnique(), .name = conName.literal(self.lexer.source), .tys = tys.items, .data = data, .tagValue = tag, .anns = conAnnotations, }); tag += 1; } else { unreachable; // TODO: ERROR! } } } else { std.debug.assert(ddt.type == .COLON); const l = self.foldFromHere(); { // constructor var tys = std.ArrayList(AST.Type).init(self.arena); while (!(self.peek().type == .STMT_SEP or (self.peek().type == .DEDENT))) { // we must not consume the last DEDENT, as it's used to terminate the whole type declaration. const ty = try Type.init(self, tyconstr).typ(); try self.typeContext.ftvs(&ftvs, ty.e); try self.typeContext.getOuterTVars(.{ .Data = data.uid }, &outerTVarSet, ty.e); try tys.append(ty.e); } try cons.append(.{ .uid = self.gen.cons.newUnique(), .name = dataName, .tys = tys.items, .data = data, .tagValue = tag, .anns = &.{}, // TODO(29.05.26): parse annotations bruh. }); tag += 1; } try self.finishFold(l); } // scheme // don't forget to add associations at the end!!!! for (assocs.items) |assoc| { try tvars.append(.{ .TVar = assoc.depends }); } var envs = std.ArrayList(AST.UnionRef).init(self.arena); var envIter = ftvs.envs.iterator(); while (envIter.next()) |env| { try envs.append(env.*); } data.scheme = .{ .tvars = tvars.items, .envVars = envs.items, .associations = assocs.items, }; // outer tvars var outerTVars = std.ArrayList(AST.TVarOrNum).init(self.arena); var otvIt = outerTVarSet.iterator(); while (otvIt.next()) |tv| { try outerTVars.append(tv.*); } data.outerTVars = outerTVars.items; if (cons.items.len > 0 and recs.items.len > 0) { try self.reportError(.{ .RecordsAndConstructorsPresent = .{} }); } if (cons.items.len > 0) { data.stuff = .{ .cons = cons.items }; } else { data.stuff = .{ .recs = recs.items }; } } try self.newData(data); } fn typeSynonym(self: *Self, typename: Token, tvarToks: []Token, annotations: []AST.Annotation) !void { _ = annotations; const uid = self.gen.types.newUnique(); const name = typename.literal(self.lexer.source); var tvars = try std.ArrayList(AST.TVarOrNum).initCapacity(self.arena, tvarToks.len); const synonym = try b: { self.beginScope(); defer self.endScope(); for (tvarToks) |t| { if (t.type == .NUMTYNAME) { const numtv = try self.newTNum(t.literal(self.lexer.source)[1..], .{ .Data = uid }); try tvars.append(.{ .TNum = numtv }); } else if (t.type == .IDENTIFIER) { const tv = try self.newTVar(t.literal(self.lexer.source), .{ .Data = uid }); try tvars.append(.{ .TVar = tv }); } else { unreachable; } } var assocs = std.ArrayList(AST.Association).init(self.arena); const tyconstr = Type.Constrain{ .Data = .{ .uid = uid, .assocs = &assocs } }; const t = try Type.init(self, tyconstr).sepTyo(); // COPYPASTA: don't forget to add associations at the end!!!! for (assocs.items) |assoc| { try tvars.append(.{ .TVar = assoc.depends }); } break :b Common.allocOne(self.arena, AST.TypeSynonym{ .uid = uid, .scheme = .{ .tvars = tvars.items, .envVars = &.{}, .associations = assocs.items, }, .t = t.e, }); }; try self.newTypeSynonym(name, synonym); } fn function(self: *Self, fun: *AST.Function, nameLoc: Loc) !*AST.Function { // std.debug.print("Level: {}\n", .{self.level()}); const oldTriedReturningAtAll = self.triedReturningAtAll; const oldReturned = self.returned; defer { self.triedReturningAtAll = oldTriedReturningAtAll; self.returned = oldReturned; } self.triedReturningAtAll = false; self.returned = .Nah; // already begin env const env = try self.beginEnv(fun); var params = std.ArrayList(AST.DeconBase).init(self.arena); const tyconstr = Type.Constrain{ .Function = .{ .uid = fun.name.uid } }; if (!self.check(.RIGHT_PAREN)) { while (true) { const refvar = self.deconRefVar(); const decon = try self.deconstruction(refvar); const nextTok = self.peek().type; if (nextTok != .COMMA and nextTok != .RIGHT_PAREN) { const pt = try Type.init( self, tyconstr, ).sepTyo(); try self.typeContext.unify(decon.t, pt.e, &.{ .l = decon.l, .r = pt.l }); } try params.append(.{ .d = decon, .refvar = refvar }); if (!self.check(.COMMA)) break; } try self.devour(.RIGHT_PAREN); } // prepare params for a function type. const paramTs = try self.arena.alloc(AST.Type, params.items.len); for (params.items, 0..) |et, i| { paramTs[i] = et.d.t; } // -> ty const ret = try self.typeContext.fresh(); if (self.check(.RIGHT_ARROW)) { const retTy = try Type.init(self, tyconstr).sepTyo(); try self.typeContext.unify(ret, retTy.e, null); } const constraints_ = try self.constraints(); // NOTE: make sure everything what's needed is assigned in case of recursive calls. fun.* = AST.Function{ .name = fun.name, .params = params.items, .ret = ret, .scheme = AST.Scheme.empty(), // in recursive calls, the scheme should be empty .temp__isRecursive = true, .env = env, .body = undefined, .temp__calls = fun.temp__calls, .temp__finishedParsing = fun.temp__finishedParsing, .temp__mono = fun.temp__mono, }; const fnBody = if (self.check(.COLON)) b: { const pm = self.foldFromHere(); const expr = try self.expression(); try self.finishFold(pm); const stmts = try self.arena.alloc(*AST.Stmt, 1); stmts[0] = try Common.allocOne(self.arena, AST.Stmt{ .Return = expr, }); try self.typeContext.unify(ret, expr.t, &.{ .l = expr.l }); break :b stmts; } else b: { // set return and parse body const oldReturnType = self.returnType; self.returnType = ret; const fnBodyAndReturnStatus = try self.body(); var fnBody = fnBodyAndReturnStatus.stmts; const returnStatus = fnBodyAndReturnStatus.returnStatus; try self.finishBodyAndInferReturnType(&fnBody, returnStatus, nameLoc); self.returnType = oldReturnType; break :b fnBody.items; }; // const lastStmt = fnBody[fnBody.len - 1]; try self.solveAvailableConstraints(); // after typechecking inside the function, create scheme. // TODO: unfinished, we don't care about the environment yet. const definedTVars = self.scope.currentScope().tvars; self.endScope(); // finish env. const scheme = try self.mkSchemeForFunction(&definedTVars, params.items, ret, env, fun.name.uid, &constraints_); // NOTE: I assign all at once so tha the compiler ensures I leave no field uninitialized. fun.* = AST.Function{ .name = fun.name, .params = params.items, .ret = ret, .body = fnBody, .scheme = scheme, .env = env, .temp__isRecursive = false, .temp__calls = fun.temp__calls, .temp__finishedParsing = true, .temp__mono = fun.temp__mono, }; return fun; } fn finishBodyAndInferReturnType(self: *Self, fnBody: *std.ArrayList(*AST.Stmt), returnStatus: ReturnStatus, l: Loc) !void { // TODO: factor it out! if (!self.triedReturningAtAll) { try fnBody.append(try Common.allocOne(self.arena, try self.unitReturn(l))); // TODO: add special location type for "midlines" // eg. // askjdklasjdk // |-> ---------- // | aksdlkasjdkj // L some footnote } else if (returnStatus == .Nah) retcheck: { // We know we returned somewhere, but for this main program branch we did not for some reason. // If the return type is Unit, we can just insert a return. // Otherwise, error obv. switch (self.typeContext.getType(self.returnType.?)) { .Con => |c| if (c.type.uid == (try self.defined(.Unit)).data.uid) { try fnBody.append(try Common.allocOne(self.arena, try self.unitReturn(l))); break :retcheck; }, else => { // otherwise try self.reportError(.{ .MissingReturn = .{} }); }, } } } // Should body() set .mode? fn body(self: *Self) !struct { stmts: std.ArrayList(*AST.Stmt), returnStatus: ReturnStatus } { std.debug.assert(self.returned != .Returned); self.mode = .{ .Simple = .Normal }; const oldReturned = self.returned; defer self.returned = oldReturned; try self.devour(.INDENT); self.beginScope(); var stmts = std.ArrayList(*AST.Stmt).init(self.arena); while (!self.check(.DEDENT)) { const stmt = try self.statement(); if (stmt) |s| { try stmts.append(s); } } self.endScope(); return .{ .stmts = stmts, .returnStatus = self.returned }; } fn statement(self: *Self) ParserError!?*AST.Stmt { return self.statement_() catch |e| switch (e) { error.ParseError => { self.skip(); // maybe extract it to a new function. // sync to end of line (naive n simple) while (!self.isEndStmt() and self.peek().type != .INDENT) { self.skip(); } // what to do in case of sudden indent?? // maybe nothing? like, handle all indenting statemenets separately (if, case, etc.) switch (self.mode) { .Simple => |nmode| switch (nmode) { .Normal => try self.endStmt(), .CountIndent => try self.finishFold(.{ .Simple = .Normal }), }, else => unreachable, } return null; }, else => return e, }; } fn statement_(self: *Self) ParserError!?*AST.Stmt { if (self.returned == .Returned) { try self.reportError(.{ .UnreachableCode = .{} }); self.returned = .Errored; } const annotations = try self.parseAnnotation(); const stmtVal: ?AST.Stmt = b: { if (self.check(.PASS)) { // pass here is a half debug statement. it can also take an integer, which should be printed at runtime (this is for matching in a debugger.) var label: ?i64 = null; if (self.consume(.INTEGER)) |sint| { label = std.fmt.parseInt(i64, sint.literal(self.lexer.source), 10) catch unreachable; } try self.endStmt(); break :b .{ .Pass = label }; } // pass else if (self.consume(.RETURN)) |retTok| { var l = self.loc(retTok); const expr = if (!self.isEndStmt()) bb: { const pm = self.foldFromHere(); const e = try self.expression(); l = l.between(e.l); try self.finishFold(pm); break :bb e; } else bb: { try self.endStmt(); const t = try self.definedType(.Unit); const con = &self.typeContext.getType(t).Con.type.stuff.cons[0]; // WARNING: funny casts break :bb try self.allocExpr(.{ .t = t, .l = self.loc(retTok), .e = .{ .Con = con }, }); }; self.triedReturningAtAll = true; if (self.returned != .Errored) self.returned = .Returned; try self.typeContext.unify(expr.t, try self.getReturnType(), &.{ .l = l }); break :b .{ .Return = expr }; } // return else if (self.check(.BREAK)) { try self.endStmt(); break :b .{ .Break = .{} }; // } // break else if (self.check(.USE)) { var modpath = std.ArrayList(Str).init(self.arena); const firstMod = try self.expect(.TYPE); var l = self.loc(firstMod); try modpath.append(firstMod.literal(self.lexer.source)); while (self.check(.DOT)) { const mod = try self.expect(.TYPE); try modpath.append(mod.literal(self.lexer.source)); l = l.between(self.loc(mod)); } const mmodule = try self.loadModuleFromPath(modpath.items, l); // should re-calling `use` reexport instances (in case they were overriden?) if (self.check(.AS)) { const synonym = try self.expect(.TYPE); const path = try self.arena.alloc([]const u8, 1); path[0] = synonym.literal(self.lexer.source); try self.importedModules.put(path, mmodule); } // IMPORT LIST YO. if (self.check(.LEFT_PAREN)) { while (!self.check(.RIGHT_PAREN)) { try self.importThing(mmodule, modpath.items); if (!self.check(.COMMA)) { try self.devour(.RIGHT_PAREN); break; } } try self.endStmt(); } // else if (self.check(.INDENT)) { while (true) { try self.importThing(mmodule, modpath.items); if (!self.check(.COMMA) or self.isEndStmt()) { try self.endStmt(); if (self.check(.DEDENT)) break; } } } else { try self.endStmt(); } break :b null; } // use else if (self.check(.FN)) { const v = try self.expect(.IDENTIFIER); try self.devour(.LEFT_PAREN); const funptr = try self.newFunction(v); const fun = try self.function(funptr, self.loc(v)); const fndec = AST.Stmt{ .Function = fun }; break :b fndec; } // function else if (self.consume(.IDENTIFIER)) |v| { // here, choose between identifier and call if (self.check(.EQUALS)) { const pm = self.foldFromHere(); const expr = try self.expression(); const vt = try self.newVar(v, null); try self.typeContext.unify(vt.t, expr.t, &.{ .l = self.loc(v), .r = expr.l }); try self.finishFold(pm); break :b .{ .VarDec = .{ .varDef = vt.v.v, .varValue = expr, } }; } else if (self.check(.LTEQ)) { // basic mutation (different token.) // COPYPASTA const pm = self.foldFromHere(); const e = try self.expression(); try self.finishFold(pm); const vtsc = try self.lookupVar(&.{}, v); const vv = switch (vtsc.vorf) { .Var => |vt| vt, else => bb: { try self.reportError(.{ .TryingToMutateNonVar = .{} }); break :bb try self.newVar(v, null); }, }; try self.addToEnvIfPossible(self.env, .{ .v = .{ .Var = vv.v }, .m = &AST.Match.Empty, .t = vv.t, .l = vtsc.level, }, false); try self.typeContext.unify(vv.t, e.t, &.{ .l = self.loc(v), .r = e.l }); break :b .{ .VarMut = .{ .varRef = vv.v, .locality = locality(self.env, vtsc.level), .accessors = &.{}, .varValue = e, } }; } else if (self.check(.LT)) { // TODO: maybe I should use instantiateVar for this? or somehow deal with it to not copy-paste this. const vtsc = try self.lookupVar(&.{}, v); const vv = switch (vtsc.vorf) { .Var => |vt| vt, else => bb: { try self.reportError(.{ .TryingToMutateNonVar = .{} }); break :bb try self.newVar(v, null); }, }; try self.addToEnvIfPossible(self.env, .{ .v = .{ .Var = vv.v }, .m = &AST.Match.Empty, .t = vv.t, .l = vtsc.level, }, false); var innerTy = vv.t; var accessors = std.ArrayList(AST.Stmt.Accessor).init(self.arena); while (true) { if (self.consume(.REF)) |reftok| { try accessors.append(.{ .tBefore = innerTy, .acc = .Deref }); const ptr = (try self.defined(.Ptr)).dataInst; try self.typeContext.unify(innerTy, ptr.t, &.{ .l = self.loc(v), .r = self.loc(reftok) }); innerTy = ptr.tyArgs[0].Type; } else if (self.consume(.DOT)) |dottok| { const name = try self.expect(.IDENTIFIER); const field = name.literal(self.lexer.source); try accessors.append(.{ .tBefore = innerTy, .acc = .{ .Access = field }, }); const ft = try self.typeContext.field(innerTy, field, &.{ .l = self.loc(v), .r = self.loc(dottok).between(self.loc(name)), }); innerTy = ft; } else break; } try self.devour(.EQUALS); const pm = self.foldFromHere(); const e = try self.expression(); try self.typeContext.unify(innerTy, e.t, &.{ .l = self.loc(v), .r = e.l }); // TEMP // if (vtsc.sc != self.scope.currentScope()) { // try self.reportError(.{ .CannotDirectlyMutateVarFromEnv = .{} }); // } try self.finishFold(pm); break :b .{ .VarMut = .{ .varRef = vv.v, .locality = locality(self.env, vtsc.level), .accessors = accessors.items, .varValue = e, } }; } // mutation else { // try parsing expression yo as a variable n shiii const pm = self.foldFromHere(); const vv = try self.instantiateVar(&.{}, v); const e = try self.finishExpression(try self.allocExpr(.{ .t = vv.t, .e = .{ .Var = .{ .v = vv.v, .match = vv.m, .locality = vv.l, } }, .l = self.loc(v), })); try self.finishFold(pm); break :b .{ .Expr = e }; } } // var or mut else if (self.check(.LET)) { const refvar = self.deconRefVar(); const d = try self.deconstruction(refvar); try self.devour(.EQUALS); const expr = try self.expression(); try self.typeContext.unify(d.t, expr.t, &.{ .l = d.l, .r = expr.l }); break :b .{ .Decon = .{ .d = d, .refvar = refvar, .e = expr } }; } // deconstruction (TEMP let, i'll have to rethink deconstruction and maybe make it polymorphic enough for this?) else if (self.check(.UNDERSCORE)) { try self.devour(.EQUALS); const e = try self.expression(); try self.endStmt(); break :b .{ .Expr = e }; } // assignment to nothing else if (self.consume(.TYPE)) |typename| { if (self.peek().type == .DOT) { // DON'T CONSUME! const pm = self.foldFromHere(); const qe = try self.qualified(typename); const e = try self.finishExpression(qe); try self.finishFold(pm); break :b .{ .Expr = e }; } // start counting tvars. var tvars = std.ArrayList(Token).init(self.arena); // can be a tvar or a postfix call. // TODO BUG: seems like it's currently broken? const lexState = self.saveLexingState(); if (self.consume(.IDENTIFIER)) |mtv| { if (self.peek().type == .LEFT_PAREN) { // postfix call. self.loadLexingState(lexState); // AHH AHSDH FUCK I DID IT, NO!! // ITS OBVIOUS I SHOULD USE A `data` KEYWORD OR SOMETHING LIKE THIS BRUHHHH. // BUT MUH QT SYNTAX :OOOOOOOO const pm = self.foldFromHere(); const ce = try self.constructorExpression(&.{}, typename); try self.finishFold(pm); break :b .{ .Expr = try self.finishExpression(ce) }; } else { try tvars.append(mtv); } } // consume tvars yo! while (true) { if (self.consume(.NUMTYNAME)) |numtyTok| { try tvars.append(numtyTok); } else if (self.consume(.IDENTIFIER)) |tvname| { try tvars.append(tvname); } else { break; } } if (self.check(.EQUALS)) { try self.typeSynonym(typename, tvars.items, annotations); break :b null; } else if (self.peek().type == .COLON or self.peek().type == .INDENT or self.peek().type == .STMT_SEP or tvars.items.len > 0) { // basically in these conditions, we can be sure that we're trying to parse a datatype. try self.dataDef(typename, tvars.items, annotations); break :b null; } // In this case, it's probably something like (`None Just()` or `None(420)`) this is probably an expression, so parse it as one. const pm = self.foldFromHere(); const ce = try self.constructorExpression(&.{}, typename); const e = try self.finishExpression(ce); try self.finishFold(pm); break :b .{ .Expr = e }; } // type else if (self.check(.IF)) { const cond = try self.expression(); try self.typeContext.unify(cond.t, try self.definedType(.Bool), &.{ .l = cond.l }); const bTrueBod = try self.body(); const bTrue = bTrueBod.stmts.items; var returnStatus = bTrueBod.returnStatus; var elifs = std.ArrayList(AST.Stmt.Elif).init(self.arena); while (self.check(.ELIF)) { const elifCond = try self.expression(); try self.typeContext.unify(elifCond.t, try self.definedType(.Bool), &.{ .l = elifCond.l }); const elifBodyAndStatus = try self.body(); returnStatus = returnStatus.alternative(elifBodyAndStatus.returnStatus); try elifs.append(AST.Stmt.Elif{ .cond = elifCond, .body = elifBodyAndStatus.stmts.items }); } const elseBody = if (self.check(.ELSE)) els: { const bod = try self.body(); returnStatus = returnStatus.alternative(bod.returnStatus); break :els bod.stmts.items; } else els: { returnStatus = .Nah; break :els null; }; self.returned = returnStatus; // incomplete break :b .{ .If = .{ .cond = cond, .bTrue = bTrue, .bOthers = elifs.items, .bElse = elseBody, } }; } // if else if (self.check(.WHILE)) { const cond = try self.expression(); const boolTy = try self.definedType(.Bool); try self.typeContext.unify(cond.t, boolTy, &.{ .l = cond.l }); const bod = try self.body(); self.returned = bod.returnStatus; break :b .{ .While = .{ .cond = cond, .body = bod.stmts.items, } }; } // while else if (self.check(.FOR)) { self.beginScope(); defer self.endScope(); const refvar = self.deconRefVar(); const decon = .{ .d = try self.deconstruction(refvar), .refvar = refvar, }; try self.devour(.IN); const itexpr = try self.expression(); // do the unifications! // TODO: bruh, type and code generation is so annoying bruv. I should make it look nicer (and simpler repr will cause less bugs.) // Maybe I should make some convenience functions? const intoIterClass = try self.definedClass(.IntoIter); const intoIterFun = intoIterClass.classFuns[0]; const intoIterFunInst = try self.instantiateClassFunction(intoIterFun, itexpr.l); const iterType = try self.typeContext.fresh(); try self.typeContext.unify(intoIterFunInst.t, try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{itexpr.t}, .ret = iterType, } }), &.{ .l = itexpr.l }); const iterClass = try self.definedClass(.Iter); const nextFun = iterClass.classFuns[0]; const nextFunInst = try self.instantiateClassFunction(nextFun, itexpr.l); const elemType = decon.d.t; const maybeElem = (try self.defined(.Maybe)).dataInst; try self.typeContext.unify(maybeElem.tyArgs[0].Type, elemType, &.{ .l = itexpr.l, .r = decon.d.l }); const ptrType = (try self.defined(.Ptr)).dataInst; try self.typeContext.unify(ptrType.tyArgs[0].Type, iterType, &.{ .l = itexpr.l }); try self.typeContext.unify(nextFunInst.t, try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ptrType.t}, .ret = maybeElem.t, } }), &.{ .l = itexpr.l, .r = decon.d.l }); const bod = try self.body(); self.returned = bod.returnStatus; break :b .{ .For = .{ .decon = decon, .iter = itexpr, .iterTy = iterType, .condTy = maybeElem.t, .intoIterFun = intoIterFunInst.ref, .nextFun = nextFunInst.ref, .body = bod.stmts.items, } }; } // for else if (self.check(.CASE)) { const switchOn = try self.expression(); const refvar = self.deconRefVar(); var returnStatus = ReturnStatus.Returned; // mempty-like var cases = std.ArrayList(AST.Case).init(self.arena); try self.devour(.INDENT); self.beginScope(); while (!self.check(.DEDENT)) { const decon = try self.deconstruction(refvar); try self.typeContext.unify(switchOn.t, decon.t, &.{ .l = switchOn.l, .r = decon.l }); const bod = try self.body(); try cases.append(.{ .decon = decon, .body = bod.stmts.items }); returnStatus = returnStatus.alternative(bod.returnStatus); } self.endScope(); self.returned = returnStatus; // check here for exhaustiveness. If not exhaustive, add .Nah OR I disallow non-exhaustive cases. break :b .{ .Switch = .{ .switchOn = switchOn, .refvar = refvar, .cases = cases.items, }, }; } // case else if (self.check(.CLASS)) { const className = try self.expect(.TYPE); const uid = self.gen.types.newUnique(); const oldSelf = self.selfType; const selfVar = try self.newTVar("_", .{ .ClassFunction = uid }); const selfType = try self.typeContext.newType(.{ .TVar = selfVar, }); self.selfType = selfType; const class = try self.arena.create(AST.Class); class.* = AST.Class{ .uid = uid, .name = className.literal(self.lexer.source), .selfType = selfVar, .classFuns = undefined, .default = null, .level = self.level(), }; if (self.check(.COLON)) { const qtypeName = try self.parseQualifiedType(try self.expect(.TYPE)); const data = (try self.findQualifiedDataOrClass(qtypeName.modpath, qtypeName.name, qtypeName.loc) orelse unreachable).Data; class.default = data; } var classFuns = std.ArrayList(*AST.ClassFun).init(self.arena); try self.devour(.INDENT); while (!self.check(.DEDENT)) { const classFun = try self.classFunction(.{ .tvar = selfVar, .t = selfType }, class); self.consumeSeps(); try classFuns.append(classFun); } self.selfType = oldSelf; class.classFuns = classFuns.items; try self.newClass(class); return null; } // class else if (self.check(.INST)) { const qclassName = try self.parseQualifiedType(try self.expect(.TYPE)); const class = (try self.findQualifiedDataOrClass(qclassName.modpath, qclassName.name, qclassName.loc) orelse unreachable).Class; const qtypeName = try self.parseQualifiedType(try self.expect(.TYPE)); const data = (try self.findQualifiedDataOrClass(qtypeName.modpath, qtypeName.name, qtypeName.loc) orelse unreachable).Data; const oldSelf = self.selfType; const dataInst = try self.instantiateData(data, qtypeName.loc); const instantiatedSelfType = dataInst.t; self.selfType = instantiatedSelfType; defer self.selfType = oldSelf; var instFuns = std.ArrayList(AST.Instance.InstFun).init(self.arena); try self.devour(.INDENT); while (true) { // while1 const funName = try self.expect(.IDENTIFIER); try self.devour(.LEFT_PAREN); const funptr = try self.arena.create(AST.Function); funptr.* = .{ .name = AST.Var{ .name = funName.literal(self.lexer.source), .uid = self.gen.vars.newUnique(), }, .scheme = AST.Scheme.empty(), .env = undefined, .params = undefined, .ret = undefined, .body = undefined, .temp__isRecursive = true, .temp__calls = std.ArrayList(AST.Function.Instantiation).init(self.arena), .temp__finishedParsing = false, .temp__mono = AST.Function.Mono.empty(self.typeContext, self.arena), }; const fun = try self.function(funptr, self.loc(funName)); // now, "associate it" with a class function for (class.classFuns) |classFun| { if (!Common.streq(classFun.name.name, fun.name.name)) continue; // class function found. unify types. // todo try instFuns.append(.{ .fun = fun, .classFunId = classFun.uid }); break; } else { // error that instance function is not found. // should we do anything more? try self.reportError(.{ .InstanceFunctionDoesNotMatchClassFunction = .{ .data = data, .class = class, .fun = fun, .loc = self.loc(funName), }, }); } if (self.check(.DEDENT)) break; } const instance = try Common.allocOne(self.arena, AST.Instance{ .uid = self.gen.instances.newUnique(), .class = class, .data = data, .instFuns = instFuns.items, .level = self.level(), }); try self.addInstance(self.scope.currentScope(), instance); break :b .{ .Instance = instance, }; } // instance else if (self.check(.EXTERNAL)) { if (self.consume(.IDENTIFIER)) |nameTok| { try self.externalFun(nameTok, annotations); // single statement } // single stmt else if (self.check(.INDENT)) { while (!self.check(.DEDENT)) { const funAnns = try self.parseAnnotation(); // this could be later freed. const allAnns = try std.mem.concat(self.arena, AST.Annotation, &[_][]const AST.Annotation{ annotations, funAnns }); const nameTok = try self.expect(.IDENTIFIER); try self.externalFun(nameTok, allAnns); } } // multiple else { unreachable; // TODO: err } break :b null; } // external function else { // this is kinda funny now, but try to parse an expression in this case. const pm = self.foldFromHere(); const e = try self.expression(); try self.finishFold(pm); break :b .{ .Expr = e, }; // return self.err(*AST.Stmt, "Expect statement.", .{}); } // (if we forget to return an expression, this should catch it) unreachable; }; self.consumeSeps(); if (stmtVal) |stmtValForSure| { const stmt = try self.arena.create(AST.Stmt); stmt.* = stmtValForSure; return stmt; } return null; } fn importThing(self: *Self, mmodule: ?Module, modpath: Module.Path) !void { if (self.consume(.IDENTIFIER)) |v| { if (mmodule) |mod| { const varName = v.literal(self.lexer.source); if (mod.lookupVar(varName)) |vv| { try self.scope.currentScope().vars.put(varName, .{ .thing = vv, .fromWhere = .Imported }); } else { try self.reportError(.{ .ModuleDoesNotExportThing = .{ .moduleName = modpath, .thing = varName, .l = self.loc(v), } }); } } } // else if (self.consume(.TYPE)) |tt| { const typeName = tt.literal(self.lexer.source); const dataOrClass: ?Module.DataOrClass = bb: { if (mmodule) |mod| { const doc = mod.lookupData(typeName) orelse { try self.reportError(.{ .UndefinedType = .{ .typename = typeName, .loc = self.loc(tt) } }); break :bb null; }; try self.scope.currentScope().types.put(typeName, .{ .thing = doc, .fromWhere = .Imported }); break :bb doc; } else { break :bb null; } }; if (self.check(.LEFT_PAREN)) { while (!self.check(.RIGHT_PAREN)) { if (self.consume(.IDENTIFIER)) |vt| { const vname = vt.literal(self.lexer.source); if (dataOrClass) |doc| { switch (doc) { .Class => |c| { for (c.classFuns) |cfun| { if (Common.streq(cfun.name.name, vname)) { try self.scope.currentScope().vars.put(vname, .{ .thing = .{ .ClassFun = cfun }, .fromWhere = .Imported }); break; } } else { try self.reportError(.{ .ClassDoesNotExportThing = .{} }); } }, .Data => try self.reportError(.{ .ModuleDoesNotExportThing = .{ .moduleName = modpath, .thing = vname, .l = self.loc(vt), } }), .Synonym => unreachable, // TODO } } } // else if (self.consume(.TYPE)) |ct| { const cname = ct.literal(self.lexer.source); if (dataOrClass) |doc| { switch (doc) { .Data => |d| { for (d.stuff.cons) |*con| { if (Common.streq(con.name, cname)) { try self.scope.currentScope().cons.put(cname, .{ .thing = con, .fromWhere = .Imported }); break; } } else { try self.reportError(.{ .DataDoesNotExportThing = .{ .thing = cname, .loc = self.loc(ct), } }); } }, .Class => try self.reportError(.{ .ModuleDoesNotExportThing = .{ .moduleName = modpath, .thing = cname, .l = self.loc(ct), } }), .Synonym => unreachable, // TODO } } } // else if (self.check(.TIMES)) { if (dataOrClass) |doc| { switch (doc) { .Class => |c| { for (c.classFuns) |cfun| { try self.scope.currentScope().vars.put(cfun.name.name, .{ .thing = .{ .ClassFun = cfun }, .fromWhere = .Imported }); } }, .Data => |d| { for (d.stuff.cons) |*con| { try self.scope.currentScope().cons.put(con.name, .{ .thing = con, .fromWhere = .Imported }); } }, .Synonym => unreachable, // TODO } } } // else { return try self.errorExpect("imported stuff"); } if (self.check(.RIGHT_PAREN)) break; try self.devour(.COMMA); } } } // else if (self.check(.TIMES)) { if (mmodule) |*module| { try self.addExports(&module.exports); // technically, adds instances twice (first, when the module is loaded, second here.) // TODO: add errors explaining the futility of importing anything else from the module. } } // else { return try self.errorExpect("import"); } } fn externalFun(self: *Self, nameTok: Token, annotations: []AST.Annotation) !void { const uid = self.gen.vars.newUnique(); const name = nameTok.literal(self.lexer.source); self.beginScope(); try self.devour(.LEFT_PAREN); var params = std.ArrayList(AST.ExternalFunction.Param).init(self.arena); const tyconstr = Type.Constrain{ .ExternalFunction = .{ .uid = uid } }; if (!self.check(.RIGHT_PAREN)) { while (true) { const pname = try self.expect(.IDENTIFIER); const v = try self.newVar(pname, null); // pointless fresh. const t = try Type.init(self, tyconstr).sepTyo(); try params.append(.{ .pn = v.v.v, .pt = t.e }); if (self.check(.RIGHT_PAREN)) { break; } try self.devour(.COMMA); } } try self.devour(.RIGHT_ARROW); const ret = try Type.init(self, tyconstr).sepTyo(); try self.endStmt(); // TODO: Technically, we should be able to pass buffers. But we should not in general allow type integers. var definedTVars = std.ArrayList(AST.TVarOrNum).init(self.arena); var it = self.scope.currentScope().tvars.valueIterator(); while (it.next()) |tvar| { try definedTVars.append(tvar.*); } self.endScope(); const scheme = AST.Scheme{ .tvars = definedTVars.items, .associations = &.{}, .envVars = &.{}, }; const extfun = try Common.allocOne(self.arena, AST.ExternalFunction{ .name = .{ .name = name, .uid = uid, }, .params = params.items, .ret = ret.e, .scheme = scheme, .anns = annotations, }); try self.scope.currentScope().vars.put(name, .{ .thing = .{ .Extern = extfun } }); } // IMPORTS / EXPORTS // (moved 'em down, cuz I think I first should have access to more changed functions like body, expression, etc.) fn exportListToExports(self: *Self, definedExports: []Export) !Module.Exports { if (definedExports.len == 0) { return try self.scopeToExports(); } var exports = Module.Exports.init(self.arena); for (definedExports) |thing| { switch (thing.exportedThing) { .Wildcard => { if (thing.externalModule.len == 0) { try exports.mergeWith(&try self.scopeToExports()); } else { const motherModule = try self.loadModuleFromPath(thing.externalModule, thing.qualifierLoc.?); if (motherModule) |otherModule| { try exports.mergeWith(&otherModule.exports); } else { // loadModuleFromPath should throw an error, right? } } }, .VariableOrFunction => |vtok| { const vorf = try self.lookupVar(thing.externalModule, vtok); try exports.vars.put(vtok.literal(self.lexer.source), vorf.vorf); }, .Type => |dataType| { // looks like "Type" - whatever it is, just export it. // also, when we explicitly export no constructors, we don't care which type it is and stuff. if (!dataType.constructors.explicitlyHasConstructors()) { const tyname = dataType.typename; const tynameStr = tyname.literal(self.lexer.source); const mWhatever = try self.findQualifiedDataOrClass( thing.externalModule, tynameStr, self.loc(dataType.typename).between(thing.qualifierLoc), ); if (mWhatever) |whatever| { try exports.types.put(tynameStr, whatever); if (dataType.constructors == .AllCons) { switch (whatever) { .Data => |data| switch (data.stuff) { .cons => |cons| { for (cons) |*con| { try exports.cons.put(con.name, con); } }, .recs => {}, }, .Class => |class| { for (class.classFuns) |cfun| { try exports.vars.put(cfun.name.name, .{ .ClassFun = cfun }); } }, else => {}, } } } } // looks like a datatype with constructors. error out when it's not a constructor datatype. else { const tyname = dataType.typename; const tynameStr = tyname.literal(self.lexer.source); const tynameLoc = thing.qualifierLoc.?.between(self.loc(dataType.typename)); const mWhatever = try self.findQualifiedDataOrClass( thing.externalModule, tynameStr, tynameLoc, ); if (mWhatever) |whatever| { try exports.types.put(tynameStr, whatever); switch (whatever) { .Data => |data| switch (data.stuff) { .cons => |dcons| { const cons = dataType.constructors.Cons; // .AllCons should be caught in the first case. for (cons) |con| { // exported cons const cname = con.literal(self.lexer.source); for (dcons) |*dcon| { // data cons if (Common.streq(dcon.name, cname)) { try exports.cons.put(cname, dcon); break; } } else { try self.reportError(.{ .DataDoesNotExportThing = .{ .thing = cname, .loc = tynameLoc, } }); } } }, .recs => { try self.reportError(.{ .TypeIsNotAnEnum = .{ .d = data, .loc = tynameLoc, } }); }, }, .Class => |class| { const cfuns = dataType.constructors.Cons; const dcfuns = class.classFuns; for (cfuns) |cfun| { // exported cons const cname = cfun.literal(self.lexer.source); for (dcfuns) |dcfun| { // data cons if (Common.streq(dcfun.name.name, cname)) { try exports.vars.put(cname, .{ .ClassFun = dcfun }); break; } } else { try self.reportError(.{ .DataDoesNotExportThing = .{ .thing = cname, .loc = tynameLoc, } }); } } }, else => {}, } } } }, } } return exports; } // not related to one hot encoding. // true when it finished at indent. // TODO: maybe the code will be cleaner if I utilize parsing modes and just make it a single folded line? fn oneHotExport(self: *Self, definedExports: *std.ArrayList(Export), outerPath: Module.Path) !bool { // try qualified type thing. // due to the way we parse it, we first must check which type was parsed yo. var moduleQualifier = std.ArrayList(Str).init(self.arena); try moduleQualifier.appendSlice(outerPath); var qualifierLoc: ?Loc = null; var exportedType: ?Token = null; if (self.consume(.TYPE)) |first| { exportedType = first; while (self.check(.DOT)) { const curLoc = self.loc(exportedType.?); try moduleQualifier.append(first.literal(self.lexer.source)); qualifierLoc = if (qualifierLoc) |l| l.between(curLoc) else curLoc; if (self.consume(.TYPE)) |tytok| { exportedType = tytok; } // else { exportedType = null; break; } } } if (exportedType) |ty| { try definedExports.append(.{ .externalModule = moduleQualifier.items, .qualifierLoc = qualifierLoc, .exportedThing = try self.exportedThing(ty), }); } else { if (self.check(.LEFT_PAREN)) { while (!self.check(.RIGHT_PAREN)) { try definedExports.append(.{ .externalModule = moduleQualifier.items, .qualifierLoc = qualifierLoc, .exportedThing = try self.exportedThing(null), }); if (self.check(.RIGHT_PAREN)) break; try self.devour(.COMMA); } } // else if (self.check(.INDENT)) { while (true) { const endedInIndent = try self.oneHotExport(definedExports, moduleQualifier.items); if (endedInIndent) { if (self.check(.DEDENT)) break; continue; } if (!self.check(.COMMA)) { try self.endStmt(); if (self.check(.DEDENT)) break; } } return true; } // else { try definedExports.append(.{ .externalModule = moduleQualifier.items, .qualifierLoc = qualifierLoc, .exportedThing = try self.exportedThing(null), }); } } return false; } fn exportedThing(self: *Self, firstType: ?Token) !Export.Thing { const parseInParens = struct { fn parseInParens(this: *Self) !Export.ConsDef { if (this.check(.LEFT_PAREN)) { if (this.check(.TIMES)) { try this.devour(.RIGHT_PAREN); return .AllCons; } else { // can be class or data yo! var constructors = std.ArrayList(Token).init(this.arena); while (!this.check(.RIGHT_PAREN)) { try constructors.append(try this.expectOneOf(&.{ .TYPE, .IDENTIFIER })); if (this.check(.RIGHT_PAREN)) break; try this.devour(.COMMA); } return .{ .Cons = constructors.items }; } } // by default, no cons exported??? i gotta think about this. return .{ .Cons = &.{} }; } }.parseInParens; // the firstType thing is here (even doe it's ugly), so the parsing behavior is localized // since i might change it slighlty if (firstType) |tytok| { const constructors = try parseInParens(self); return .{ .Type = .{ .typename = tytok, .constructors = constructors } }; } else { if (self.check(.TIMES)) { return .Wildcard; } // wildcard else if (self.consume(.IDENTIFIER)) |vt| { return .{ .VariableOrFunction = vt }; } // var or fun else if (self.consume(.TYPE)) |tytok| { const constructors = try parseInParens(self); return .{ .Type = .{ .typename = tytok, .constructors = constructors, } }; } // else { unreachable; // parse error. todo, because we need recover logic. } } } pub fn addExports(self: *Self, exports: *const Module.Exports) !void { try addImportToScope(&self.scope.currentScope().vars, &exports.vars); try addImportToScope(&self.scope.currentScope().cons, &exports.cons); try addImportToScope(&self.scope.currentScope().types, &exports.types); try self.addAllInstances(exports); } fn addImportToScope(dest: anytype, src: anytype) !void { var it = src.iterator(); while (it.next()) |e| { try dest.put(e.key_ptr.*, .{ .thing = e.value_ptr.*, .fromWhere = .Imported }); } } // NOTE: assumes Module.Exports now owns the thing. fn scopeToExports(self: *Self) !Module.Exports { std.debug.assert(self.errors.items.len > 0 or self.scope.scopes.current == 1); const scope = self.scope.currentScope(); return .{ .vars = try cloneImported(Module.VarOrFun, self.arena, scope.vars), .types = try cloneImported(Module.DataOrClass, self.arena, scope.types), .cons = try cloneImported(*AST.Con, self.arena, scope.cons), .instances = scope.instances, }; } fn cloneImported(comptime T: type, al: std.mem.Allocator, src: std.StringHashMap(CurrentScope.Import(T))) !std.StringHashMap(T) { var dest = std.StringHashMap(T).init(al); var it = src.iterator(); while (it.next()) |e| { const val = e.value_ptr.*; if (val.fromWhere == .Imported) continue; try dest.put(e.key_ptr.*, val.thing); } return dest; } // try parse annotations. // It's possible to divide annotations in multiple lines: // #[linkname 'printf'] // #[dylib 'libc.so'] // external printf(fmt Ptr Char, x a) -> Void // TODO: check if statements make sense in context. // TODO: merge / somehow handle duplicate annotations fn parseAnnotation(self: *Self) ![]AST.Annotation { var annotations = std.ArrayList(AST.Annotation).init(self.arena); // nothing is allocated when there are no annotations. while (self.check(.BEGIN_ANNOTATION)) { if (!self.check(.RIGHT_SQBR)) while (true) { const annName = try self.expect(.IDENTIFIER); var annParams = std.ArrayList(Str).init(self.arena); while (self.consume(.STRING)) |param| { const litWithQuotes = param.literal(self.lexer.source); const lit = litWithQuotes[1 .. litWithQuotes.len - 1]; try annParams.append(lit); } try annotations.append(.{ .name = annName.literal(self.lexer.source), .params = annParams.items, }); if (self.check(.RIGHT_SQBR)) break; try self.devour(.COMMA); }; try self.endStmt(); self.consumeSeps(); } return annotations.items; } fn unitReturn(self: *Self, culprit: Loc) !AST.Stmt { const t = try self.definedType(.Unit); const con = &self.typeContext.getType(t).Con.type.stuff.cons[0]; // WARNING: funny casts try self.typeContext.unify(t, self.returnType.?, &.{ .l = culprit }); return .{ .Return = try self.allocExpr(.{ .t = t, .e = .{ .Con = con }, .l = culprit, }) }; } // for single line statements that do not contain a body. // (why? in IF and such, a dedent is equivalent to STMTSEP, so we must accept both. it depends on the type of statement.) fn endStmt(self: *Self) !void { if (self.peek().type != .DEDENT and self.peek().type != .EOF) { try self.devour(.STMT_SEP); } } fn isEndStmt(self: *Self) bool { const tt = self.peek().type; return tt == .DEDENT or tt == .STMT_SEP or tt == .EOF; // NOTE: check for EOF just in case! } fn consumeSeps(self: *Self) void { while (self.check(.STMT_SEP)) {} } fn classFunction(self: *Self, classSelf: struct { tvar: AST.TVar, t: AST.Type }, class: *AST.Class) !*AST.ClassFun { const funName = try self.expect(.IDENTIFIER); const uid = self.gen.classFuns.newUnique(); self.beginScope(); var params = std.ArrayList(AST.ClassFun.Param).init(self.arena); var assocs = std.ArrayList(AST.Association).init(self.arena); const tyconstr = Type.Constrain{ .ClassFunction = .{ .uid = uid, .assocs = &assocs } }; try self.devour(.LEFT_PAREN); if (!self.check(.RIGHT_PAREN)) while (true) { // consume identifier if possible. if (self.check(.IDENTIFIER)) {} try params.append(.{ .t = (try Type.init(self, tyconstr).sepTyo()).e }); if (self.check(.RIGHT_PAREN)) { break; } try self.devour(.COMMA); }; // another new eye candy - default Unit const ret = if (self.check(.RIGHT_ARROW)) (try Type.init(self, tyconstr).sepTyo()).e else try self.definedType(.Unit); // constraints const constraints_ = try self.constraints(); try self.endStmt(); const tvarsMap = self.scope.currentScope().tvars; self.endScope(); // make a scheme from deze vars yo. var tvars = std.ArrayList(AST.TVarOrNum).init(self.arena); try tvars.append(.{ .TVar = classSelf.tvar }); var tvit = tvarsMap.valueIterator(); while (tvit.next()) |tvar| { try tvars.append(tvar.*); } // also append implicit tvars from inner class definitions. for (assocs.items) |ass| { try tvars.append(.{ .TVar = ass.depends }); } // make sure to add the implicit tvars before this! try self.addConstraintsToAssocs(&assocs, &constraints_); const scheme = AST.Scheme{ .tvars = tvars.items, .envVars = &.{}, // TEMP .associations = assocs.items, // NOTE: this will change when class constraints are allowed on functions. EDIT: Or not??? We have no constraints to specify, because these constraints are not based on class function calls. Right now, I'll leave it alone because of our "broken" type system. }; return try Common.allocOne(self.arena, AST.ClassFun{ .uid = uid, .name = .{ .name = funName.literal(self.lexer.source), .uid = self.gen.vars.newUnique(), }, .params = params.items, .ret = ret, .scheme = scheme, .self = classSelf.t, .class = class, }); } // parse constraints <= constr (, constr)* const Constraints = std.HashMap(AST.TVar, std.ArrayList(*AST.Class), AST.TVar.comparator(), std.hash_map.default_max_load_percentage); fn constraints(self: *Self) !Constraints { var constraints_ = Constraints.init(self.arena); // maybe I should just use pointers to vars? if (self.check(.LTEQ)) { while (true) { const classTok = try self.expect(.TYPE); const tvt = try self.expect(.IDENTIFIER); if (self.maybeLookupType(classTok.literal(self.lexer.source))) |dataOrClass| b: { switch (dataOrClass) { .Class => |class| { const tvarName = tvt.literal(self.lexer.source); const tvarOrNum = self.scope.currentScope().tvars.get(tvarName) orelse { // NOTE: we're looking only at current scope, so we won't find any named tvars from other functions. try self.reportError(.{ .ConstrainedNonExistentTVar = .{ .tvname = tvarName } }); break :b; }; const tvar = switch (tvarOrNum) { .TVar => |tv| tv, .TNum => unreachable, // TODO: error }; const e = try constraints_.getOrPutValue(tvar, std.ArrayList(*AST.Class).init(self.arena)); // NOTE: source says it's not allocating anything until an element is inserted. try e.value_ptr.append(class); }, .Data => unreachable, // TODO: error. .Synonym => unreachable, // TODO: error } } else { try self.reportError(.{ .UndefinedClass = .{ .className = classTok.literal(self.lexer.source), } }); } if (!self.check(.COMMA)) break; } } return constraints_; } fn addConstraintsToAssocs(self: *Self, assocs: *std.ArrayList(AST.Association), constrs: *const Constraints) !void { // also add defined constraints! (but it's all bad thoooo) var constrIt = constrs.iterator(); while (constrIt.next()) |kv| { for (kv.value_ptr.items) |class| { try assocs.append(.{ .depends = kv.key_ptr.*, .uid = self.gen.assocs.newUnique(), .class = class, .default = class.default, .concrete = null, }); } } } // TODO: what was RefVar for? I forgot fn deconRefVar(self: *Self) AST.Var { return .{ .name = "dref", .uid = self.gen.vars.newUnique() }; } fn deconstruction(self: *Self, refvar: AST.Var) !*AST.Decon { return try self.deconstructionIdx(refvar, null); } fn deconstructionIdx(self: *Self, refvar: AST.Var, idx: ?u32) !*AST.Decon { const ty = try self.typeContext.fresh(); const dpBase = try AST.Decon.Path.init(self.arena, .{ .Tip = .{ .v = refvar, .t = ty, .idx = idx, } }); const decon = try deconstruction_(self, dpBase); try self.typeContext.unify(ty, decon.t, null); return decon; } fn deconstruction_(self: *Self, dp: *const AST.Decon.Path) ParserError!*AST.Decon { const decon: AST.Decon = if (self.consume(.IDENTIFIER)) |vn| b: { const v = try self.newVar(vn, switch (dp.*) { .Tip => |tip| if (tip.idx == null) null else .{ .dp = dp }, .Concat => .{ .dp = dp }, }); break :b .{ .t = v.t, .l = self.loc(vn), .d = .{ .Var = v.v.v }, }; } // var else if (self.consume(.UNDERSCORE)) |ut| b: { break :b .{ .t = try self.typeContext.fresh(), .l = self.loc(ut), .d = .{ .None = .{} }, }; } // ignore var else if (self.consumeInteger()) |numTok| b: { const l = self.loc(numTok); const numd = try self.instantiateNumDecon(self.parseInt(numTok), false, l); break :b .{ .t = numd.t, .l = l, .d = .{ .Num = numd.d }, }; } // number else if (self.consume(.MINUS)) |minus| b: { const numTok = try self.expectInteger(); const l = self.loc(minus).between(self.loc(numTok)); const numd = try self.instantiateNumDecon(self.parseInt(numTok), true, l); break :b .{ .t = numd.t, .l = l, .d = .{ .Num = numd.d }, }; // } // -number else if (self.consume(.STRING)) |strtok| b: { const l = self.loc(strtok); const litWithQuotes = strtok.literal(self.lexer.source); const lit = litWithQuotes[1 .. litWithQuotes.len - 1]; const cst = try self.constStrType(lit, l); const eqClass = try self.definedClass(.Eq); const eqfun = eqClass.classFuns[0]; const eqInst = try self.instantiateClassFunction(eqfun, l); const eqfunTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ cst.selfTy, cst.selfTy }, .ret = try self.typeContext.fresh(), } }); try self.typeContext.unify(eqInst.t, eqfunTy, &.{ .l = l }); break :b .{ .t = cst.selfTy, .l = l, .d = .{ .Str = .{ .str = lit, .instEq = eqInst.ref, .instFromString = cst.ref, }, }, }; } // string else if (self.consume(.LEFT_PAREN)) |lp| b: { const path = try AST.Decon.Path.concat(self.arena, dp, .None); const first = try self.deconstruction_(path); if (self.check(.COMMA)) { var tups = std.ArrayList(*AST.Decon).init(self.arena); var paths = std.ArrayList(*AST.Decon.Path).init(self.arena); try tups.append(first); try paths.append(path); const rp = bb: while (true) { const pp = try AST.Decon.Path.concat(self.arena, dp, .None); try tups.append(try self.deconstruction_(pp)); try paths.append(pp); if (self.consume(.RIGHT_PAREN)) |rp| break :bb rp; try self.devour(.COMMA); }; std.debug.assert(tups.items.len > 1); const tupty = try switch (tups.items.len) { 2 => self.defined(.Tuple2), 3 => self.defined(.Tuple3), 4 => self.defined(.Tuple4), else => unreachable, }; // update paths after determining the type of the tuple. const con = &tupty.data.stuff.cons[0]; for (tups.items, paths.items, 0..) |d, p, i| { p.Concat.path = .{ .Con = .{ .con = con, .field = i, .t = d.t, } }; try self.typeContext.unify(tupty.dataInst.tyArgs[i].Type, d.t, null); } break :b .{ .t = tupty.dataInst.t, .l = self.loc(lp).between(self.loc(rp)), .d = .{ .Con = .{ .con = con, .decons = tups.items, }, }, }; } else { try self.devour(.RIGHT_PAREN); return first; } // } // grouping OR tuple else if (self.consume(.TYPE)) |cn| b: { const con = bb: { if (self.check(.DOT)) { var modpath = std.ArrayList(Str).init(self.arena); try modpath.append(cn.literal(self.lexer.source)); // TODO: oof. what is this? I need to check it if it's duplicate code. loop: while (true) { if (self.consume(.TYPE)) |tn| { if (self.check(.DOT)) { try modpath.append(tn.literal(self.lexer.source)); continue :loop; } else { break :bb try self.instantiateCon(modpath.items, tn); } } else { unreachable; // TODO } } } else { break :bb try self.instantiateCon(&.{}, cn); } }; const conLocation = self.loc(cn); // TODO: incorrect in case of qualified types var decons: []*AST.Decon = &.{}; var args: []AST.Type = &.{}; var tysLoc: ?Loc = null; if (self.check(.LEFT_PAREN)) { var ds = std.ArrayList(*AST.Decon).init(self.arena); var tys = std.ArrayList(AST.Type).init(self.arena); while (true) { // while1 const d = try self.deconstruction_(try AST.Decon.Path.concat(self.arena, dp, if (con.con.data.isPointer()) .Ptr else .{ .Con = .{ .con = con.con, .field = ds.items.len, .t = con.t, } })); try ds.append(d); try tys.append(d.t); tysLoc = if (tysLoc) |l| l.between(d.l) else d.l; if (self.check(.RIGHT_PAREN)) break; try self.devour(.COMMA); } decons = ds.items; args = tys.items; } try self.typeContext.unifyParams(con.tys, args, &.{ .l = conLocation, .r = tysLoc, }, &.{ .lfull = con.t, .rfull = null, }); break :b .{ .t = con.t, .l = conLocation.between(tysLoc), .d = .{ .Con = .{ .con = con.con, .decons = decons, }, }, }; } // con decon else if (self.consume(.LEFT_BRACE)) |leftBraceTok| b: { const t = try self.typeContext.fresh(); var fields = std.ArrayList(AST.Decon.Field).init(self.arena); while (true) { const fieldTok = try self.expect(.IDENTIFIER); const fieldName = fieldTok.literal(self.lexer.source); const fieldTy = try self.typeContext.field(t, fieldName, null); if (self.check(.COLON)) { const decon = try self.deconstruction_(try AST.Decon.Path.concat(self.arena, dp, .{ .Field = .{ .rec = fieldName, .t = t }, })); try self.typeContext.unify(fieldTy, decon.t, null); try fields.append(.{ .field = fieldName, .decon = decon, }); } else { const vnt = try self.newVar(fieldTok, .{ .dp = try AST.Decon.Path.concat(self.arena, dp, .{ .Field = .{ .rec = fieldName, .t = t }, }) }); try self.typeContext.unify(fieldTy, vnt.t, null); try fields.append(.{ .field = fieldName, .decon = try Common.allocOne(self.arena, AST.Decon{ .t = fieldTy, .d = .{ .Var = vnt.v.v }, .l = self.loc(fieldTok), }), }); } if (!self.check(.COMMA)) break; } const rightBraceTok = try self.expect(.RIGHT_BRACE); const dloc = self.loc(leftBraceTok).between(self.loc(rightBraceTok)); // TODO: right now we only care that the deconstructed struct has all the fields defined. basically { , ... } // later expect the user to write `...` to ignore extra fields. break :b .{ .t = t, .d = .{ .Record = fields.items }, .l = dloc, }; } // record deccon else if (self.consume(.LEFT_SQBR)) |ltok| b: { var left = std.ArrayList(*AST.Decon).init(self.arena); var right = std.ArrayList(*AST.Decon).init(self.arena); const listTy = try self.typeContext.fresh(); const elemTy = try self.typeContext.fresh(); const spreadTy = try self.typeContext.fresh(); const spreadInnerTy = try self.typeContext.fresh(); // for now, parse an easy version of this. var decons = &left; var spreadVar: ?AST.Var = null; var hadSpread = false; var idx: u32 = 0; const lrefvar = self.deconRefVar(); var rrefvar: ?AST.Var = null; var refvar = lrefvar; const dloc = if (self.consume(.RIGHT_SQBR)) |rtok| bb: { break :bb self.loc(ltok).between(self.loc(rtok)); } else bb: { while (true) { if (!hadSpread and self.check(.DOT)) { // scuffed spread xddddd try self.devour(.DOT); try self.devour(.DOT); if (self.consume(.IDENTIFIER)) |svtok| { const sv = try self.newVar(svtok, null); try self.typeContext.unify(sv.t, spreadInnerTy, &.{ .l = self.loc(svtok) }); spreadVar = sv.v.v; } hadSpread = true; decons = &right; rrefvar = self.deconRefVar(); refvar = rrefvar.?; idx = 0; } else { const decon = try self.deconstructionIdx(refvar, idx); try self.typeContext.unify(decon.t, elemTy, &.{ .l = decon.l }); try decons.append(decon); idx += 1; } if (self.consume(.RIGHT_SQBR)) |rtok| { break :bb self.loc(ltok).between(self.loc(rtok)); } try self.devour(.COMMA); } }; const class: *AST.Class = try self.definedClass(.ListDecon); // NOTE: assumes, that we won't be doing any deconstructing of lists in prelude (a fair assumption) const cfun = class.classFuns[0]; // assume only one function! no need create another enum or search by string! const ifn = try self.instantiateClassFunction(cfun, dloc); // ====== Construct fun ty from here. ====== const params = try self.arena.alloc(AST.Type, 6); for (params) |*param| { param.* = try self.typeContext.fresh(); } try self.typeContext.unify(params[0], listTy, null); const elemPtr = (try self.defined(.Ptr)).dataInst; // pointer to actual elements const elemPtrPtr = (try self.defined(.Ptr)).dataInst; // ptr to ptr which switches on real data or the premade list. // this is to allow modification, while allowing types which don't have a stable pointer to any element. try self.typeContext.unify(elemPtr.tyArgs[0].Type, elemTy, null); // TODO: nulls here, we'll see if this place can error out. try self.typeContext.unify(elemPtrPtr.tyArgs[0].Type, elemPtr.t, null); try self.typeContext.unify(params[1], elemPtrPtr.t, null); try self.typeContext.unify(params[4], elemPtrPtr.t, null); const spread = (try self.defined(.ListSpread)).dataInst; try self.typeContext.unify(spread.tyArgs[0].Type, spreadInnerTy, null); try self.typeContext.unify(params[3], spread.t, null); try self.typeContext.unify(spread.t, spreadTy, null); const funTy = try self.typeContext.newType(.{ .Fun = .{ .args = params, .ret = try self.definedType(.Bool), .env = try self.typeContext.newEnv(null), } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = dloc }); break :b .{ .t = listTy, .l = dloc, .d = .{ .List = .{ .l = left.items, .lrefvar = lrefvar, .r = if (hadSpread) .{ .spreadVar = if (spreadVar) |v| .{ .v = v, .t = spreadInnerTy } else null, .r = right.items, .rrefvar = rrefvar.?, } else null, .assocRef = ifn.ref, .elemTy = elemTy, .spreadTy = spreadTy, } }, }; // } // arr decon [...] else { return try self.errorExpect("decon"); }; return try Common.allocOne(self.arena, decon); } fn instantiateNumDecon(self: *Self, num: IntType, negative: bool, l: Loc) !struct { t: AST.Type, d: AST.Decon.NumDecon } { // copied from .Int in expr // class FromIntegral const intTy = try self.definedType(.Size); const fiTy = try self.typeContext.fresh(); const funTyFromIntegral = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{intTy}, .ret = fiTy, } }); const classFromIntegral = try self.definedClass(.FromIntegral); const cfunFromIntegral = classFromIntegral.classFuns[0]; const instFromIntegral = try self.instantiateClassFunction(cfunFromIntegral, l); try self.typeContext.unify(funTyFromIntegral, instFromIntegral.t, &.{ .l = l }); var instNegate: ?AST.InstFunInst = null; if (negative) { // class Negation const funTyNegation = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{fiTy}, .ret = fiTy, } }); const classNegation = try self.definedClass(.Negation); const cfunNegation = classNegation.classFuns[0]; const instNegation = try self.instantiateClassFunction(cfunNegation, l); try self.typeContext.unify(funTyNegation, instNegation.t, &.{ .l = l }); instNegate = instNegation.ref; } // class Eq const funTyEq = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ fiTy, fiTy }, .ret = try self.definedType(.Bool), } }); const classEq = try self.definedClass(.Eq); const cfunEq = classEq.classFuns[0]; const instEq = try self.instantiateClassFunction(cfunEq, l); try self.typeContext.unify(funTyEq, instEq.t, &.{ .l = l }); return .{ .t = fiTy, .d = .{ .num = num, .instFromIntegral = instFromIntegral.ref, .instNegate = instNegate, .instEq = instEq.ref, }, }; } // jon blow my c0c :3 fn expression(self: *Self) !*AST.Expr { return self.precedenceExpression(0); } fn precedenceExpression(self: *Self, minPrec: u32) ParserError!*AST.Expr { var left = try self.term(minPrec); while (true) { const node = try self.increasingPrecedenceExpression(left, minPrec); if (node == left) break; left = node; } return left; } fn finishExpression(self: *Self, leftmost: *AST.Expr) ParserError!*AST.Expr { var left = leftmost; while (true) { const node = try self.increasingPrecedenceExpression(left, 0); if (node == left) break; left = node; } return left; } fn increasingPrecedenceExpression(self: *Self, left: *AST.Expr, minPrec: u32) !*AST.Expr { var optok = self.peek(); // FUNNY! Handle multiline lambdas. if (optok.type == .INDENT and self.mode == .Multiline) { switch (self.mode.Multiline.this) { .Lambda => { try self.multilineLambda(self.loc(optok)); }, .Case => |case| { try self.caseExpr(self.mode.Multiline.prev, case.caseExpr, self.loc(optok)); }, } switch (self.mode) { .Simple => |ml| { switch (ml) { .CountIndent => |ci| { if (ci.indent == 0) { return left; } }, else => {}, } }, else => {}, } optok = self.peek(); } var binop = getBinOp(self.peek()) orelse return left; const nextPrec = binOpPrecedence(binop); if (nextPrec <= minPrec) { return left; } else { self.skip(); // if accepted, consume if (binop == .Call) { var params = std.ArrayList(*AST.Expr).init(self.arena); const leftLoc = self.loc(optok); const rightLoc = if (self.consume(.RIGHT_PAREN)) |rightTok| b: { break :b self.loc(rightTok); } else b: { while (true) { try params.append(try self.expression()); if (!self.check(.COMMA)) break; } break :b self.loc(try self.expect(.RIGHT_PAREN)); }; // how a function is represented. const paramTs = try self.arena.alloc(AST.Type, params.items.len); for (params.items, 0..) |et, i| { paramTs[i] = et.t; } const retType = try self.typeContext.fresh(); const callType = try self.typeContext.newType(.{ .Fun = .{ .args = paramTs, .ret = retType, .env = try self.typeContext.newEnv(null), }, }); try self.typeContext.unify(left.t, callType, &.{ .l = left.l, .r = leftLoc.between(rightLoc), }); return self.allocExpr(.{ .t = retType, .e = .{ .Call = .{ .callee = left, .args = params.items, } }, .l = left.l.between(rightLoc), }); } if (binop == .PostfixCall) { const funt: *AST.Expr = try self.qualified(optok); var params = std.ArrayList(*AST.Expr).init(self.arena); try params.append(left); _ = try self.devour(.LEFT_PAREN); const rightLoc = if (self.consume(.RIGHT_PAREN)) |rightTok| b: { break :b self.loc(rightTok); } else b: { while (true) { try params.append(try self.expression()); if (!self.check(.COMMA)) break; } break :b self.loc(try self.expect(.RIGHT_PAREN)); }; // how a function is represented. const paramTs = try self.arena.alloc(AST.Type, params.items.len); for (params.items, 0..) |et, i| { paramTs[i] = et.t; } const retType = try self.typeContext.fresh(); const callType = try self.typeContext.newType(.{ .Fun = .{ .args = paramTs, .ret = retType, .env = try self.typeContext.newEnv(null), }, }); try self.typeContext.unify(callType, funt.t, &.{ .l = funt.l, .r = left.l.between(rightLoc), }); return self.allocExpr(.{ .t = retType, .e = .{ .Call = .{ .callee = funt, .args = params.items, } }, .l = left.l.between(rightLoc), }); } if (binop == .Deref) { const ptr = (try self.defined(.Ptr)).dataInst; const dl = self.loc(optok); try self.typeContext.unify(ptr.t, left.t, &.{ .l = left.l, .r = dl, }); return self.allocExpr(.{ .t = ptr.tyArgs[0].Type, .e = .{ .UnOp = .{ .op = .Deref, .e = left }, }, .l = left.l.between(dl), }); } if (binop == .RecordAccess) { const mem = try self.expect(.IDENTIFIER); const fieldLoc = self.loc(mem); const t = try self.typeContext.field(left.t, mem.literal(self.lexer.source), &.{ .l = left.l, .r = fieldLoc }); return self.allocExpr(.{ .t = t, .e = .{ .UnOp = .{ .e = left, .op = .{ .Access = mem.literal(self.lexer.source), }, } }, .l = left.l.between(fieldLoc), }); } if (binop == .RecordUpdate) { const record = try self.someRecordDefinition(); const l = left.l.between(record.rightLoc); const t = left.t; for (record.fields) |field| { const fieldTy = try self.typeContext.field(t, field.field, &.{ .l = left.l, .r = field.value.l, }); try self.typeContext.unify(fieldTy, field.value.t, &.{ .l = left.l, .r = field.value.l, }); } return try self.allocExpr(.{ .t = t, .e = .{ .UnOp = .{ .e = left, .op = .{ .Update = record.fields, }, } }, .l = l, }); } if (binop == .As) { const t = try Type.init(self, null).sepTyo(); try self.typeContext.unify(t.e, left.t, &.{ .l = left.l, .r = t.l, }); return self.allocExpr(.{ .t = t.e, // NOTE: we generate a new node only for error reporting. we don't really need it otherwise. .e = .{ .UnOp = .{ .e = left, .op = .{ .As = t.e }, } }, .l = left.l.between(t.l), }); } if (binop == .ElementAccess) { const expr = try self.expression(); const rp = try self.expect(.RIGHT_SQBR); const selfType = left.t; const keyTy = expr.t; const class = try self.definedClass(.Indexable); const classfun = class.classFuns[0]; const l = self.loc(optok).between(self.loc(rp)); const inst = try self.instantiateClassFunction(classfun, l); const ret = try self.typeContext.fresh(); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ selfType, keyTy }, .ret = ret, }, }); try self.typeContext.unify(funTy, inst.t, &.{ .l = l }); return try self.allocExpr(.{ .t = ret, .e = .{ .UnOp = .{ .e = left, .op = .{ .ElementAccess = .{ .access = inst.ref, .index = expr, } }, }, }, .l = l, }); } const right = try self.precedenceExpression(nextPrec); const exprType = switch (binop) { .Plus, .Minus, .Times, .Divide, => |*ref| b: { const class: *AST.Class = try switch (binop) { .Plus => self.definedClass(.Addition), .Minus => self.definedClass(.Subtraction), .Times => self.definedClass(.Multiplication), .Divide => self.definedClass(.Division), else => unreachable, }; const cfun = class.classFuns[0]; const l = left.l.between(right.l); const ifn = try self.instantiateClassFunction(cfun, l); const retTy = try self.typeContext.fresh(); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ left.t, right.t }, .ret = retTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); ref.* = ifn.ref; break :b retTy; }, .GreaterThan, .LessThan, .GreaterEqualThan, .LessEqualThan, => |*ref| b: { const class = try self.definedClass(.Ord); const cfun = class.classFuns[0]; const l = left.l.between(right.l); const ifn = try self.instantiateClassFunction(cfun, l); const retTy = try self.typeContext.fresh(); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ left.t, right.t }, .ret = retTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); ref.* = ifn.ref; // NOTE: not generating any AST. the to constructors will happend in the backend. const boolTy = try self.definedType(.Bool); break :b boolTy; }, .Or, .And, => b: { const boolTy = try self.definedType(.Bool); try self.typeContext.unify(left.t, boolTy, &.{ .l = left.l, }); try self.typeContext.unify(right.t, boolTy, &.{ .l = right.l, }); break :b boolTy; }, .Equals, .NotEquals => |*ref| b: { const eqClass = try self.definedClass(.Eq); const eqFun = eqClass.classFuns[0]; const l = left.l.between(right.l); const ifn = try self.instantiateClassFunction(eqFun, l); try self.typeContext.unify(left.t, right.t, &.{ .l = left.l, .r = right.l, }); const boolTy = try self.definedType(.Bool); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ left.t, right.t }, .ret = boolTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); ref.* = ifn.ref; break :b boolTy; }, else => unreachable, }; return self.allocExpr(.{ .t = exprType, .e = .{ .BinOp = .{ .op = binop, .l = left, .r = right } }, .l = left.l.between(right.l), }); } } fn term(self: *Self, minPrec: u32) !*AST.Expr { // parse unary prefix. // I'm not sure if this is good, but getting a reference of something is not really done outside of function calls / constructors. // If *nothing* has been parsed, you may get a reference. if (minPrec == 0) if (self.consume(.REF)) |tokref| { const n = try self.expression(); const ptr = (try self.defined(.Ptr)).dataInst; const l = self.loc(tokref).between(n.l); try self.typeContext.unify(ptr.tyArgs[0].Type, n.t, &.{ .l = l }); return self.allocExpr(.{ .e = .{ .UnOp = .{ .op = .Ref, .e = n } }, .t = ptr.t, .l = l, }); }; // not if (minPrec <= comptime binOpPrecedence(.And)) if (self.consume(.NOT)) |nottok| { const n = try self.precedenceExpression(binOpPrecedence(.And) + 1); // higher than and const boolTy = try self.definedType(.Bool); try self.typeContext.unify(n.t, boolTy, &.{ .l = n.l }); const l = self.loc(nottok).between(n.l); return self.allocExpr(.{ .e = .{ .UnOp = .{ .op = .Not, .e = n } }, .t = boolTy, .l = l, }); }; // negation (-) const beforeNegPrec = comptime binOpPrecedence(.{ .Divide = undefined }); if (minPrec <= beforeNegPrec) if (self.consume(.MINUS)) |mintok| { // undefined here, because we just want to check binop precedence. note, that we still want to rewrite it, because we confuse both. const n = try self.precedenceExpression(beforeNegPrec + 1); // higher than div const l = self.loc(mintok).between(n.l); const class = try self.definedClass(.Negation); const cfun = class.classFuns[0]; const ifn = try self.instantiateClassFunction(cfun, l); const retTy = try self.typeContext.fresh(); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{n.t}, .ret = retTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); return self.allocExpr(.{ .e = .{ .UnOp = .{ .op = .{ .Negate = ifn.ref }, .e = n } }, .t = retTy, .l = l, }); }; // TODO: maybe make some function to automatically allocate memory when expr succeeds? if (self.consume(.FN)) |tokfun| { // smol hack to allow quick empty lambdas. var params = std.ArrayList(AST.DeconBase).init(self.arena); const env = try self.beginEnv(null); var needsBody = false; var l = self.loc(tokfun); // WARNING: (): whatever gets parsed as a lambda with no args. this might be incorrect behavior when we add tuples. if (self.check(.LEFT_PAREN)) { if (self.consume(.RIGHT_PAREN)) |rparen| { l = l.between(self.loc(rparen)); } else { while (true) { const refvar = self.deconRefVar(); const decon = try self.deconstruction(refvar); try params.append(.{ .d = decon, .refvar = refvar }); if (self.consume(.RIGHT_PAREN)) |rparen| { l = l.between(self.loc(rparen)); break; } try self.devour(.COMMA); } } if (!self.check(.COLON)) { // multiline lambda bruh. needsBody = true; // basically, I want the body thing to happen later. } } else if (self.consume(.COLON)) |colonTok| { // no params // NOTE: current syntax allows a lambda to start with a colon only. _ = colonTok; } else { // single param const refvar = self.deconRefVar(); const decon = try self.deconstruction(refvar); try params.append(.{ .d = decon, .refvar = refvar }); try self.devour(.COLON); } // do the types for function type. const argTys = try self.arena.alloc(AST.Type, params.items.len); for (params.items, 0..) |p, i| { argTys[i] = p.d.t; } if (needsBody) { const lamscopeSave = self.scope.currentScope().*; self.endScope(); const ret = try self.typeContext.fresh(); const lamExpr = try self.allocExpr(.{ .t = try self.typeContext.newType(.{ .Fun = .{ .args = argTys, .env = try self.typeContext.newEnv(null), .ret = ret, }, }), .e = .{ .Lam = .{ .params = params.items, .body = .{ .Body = .{ .stmts = &.{}, .ret = ret, } }, // temporary empty list! .env = undefined, }, }, .l = l, }); const oldMode = switch (self.mode) { .Simple => |simp| simp, .Multiline => |ml| b: { // This means we are trying to define two multiline lambdas on the same line. This is BRUH! try self.reportError(.{ .TriedDefiningSecondMultilineOnSameLine = .{ .loc = l }, }); break :b ml.prev; }, }; self.mode = .{ .Multiline = .{ .prev = oldMode, .this = .{ .Lambda = .{ .lamExpr = lamExpr, .scope = lamscopeSave, .env = env, } }, } }; return lamExpr; } else { const expr = try self.expression(); self.endScope(); l = l.between(expr.l); return self.allocExpr(.{ .t = try self.typeContext.newType(.{ .Fun = .{ .args = argTys, .env = try self.typeContext.newEnv(.{ .env = env, .match = try Common.allocOne(self.arena, AST.Match.empty(AST.Scheme.Empty)), .fun = null, .level = env.level, }), .ret = expr.t, }, }), .e = .{ .Lam = .{ .params = params.items, .body = .{ .Expr = expr }, .env = env, }, }, .l = l, }); } } // lambda else if (self.consume(.IF)) |iftok| { const condition = try self.expression(); try self.typeContext.unify(condition.t, try self.definedType(.Bool), &.{ .l = condition.l }); try self.devour(.COLON); const ifTrue = try self.expression(); var elifs = std.ArrayList(AST.Expr.Elif).init(self.arena); while (self.check(.ELIF)) { const elifCond = try self.expression(); try self.typeContext.unify(elifCond.t, try self.definedType(.Bool), &.{ .l = elifCond.l }); try self.devour(.COLON); const elifThen = try self.expression(); try self.typeContext.unify(elifThen.t, ifTrue.t, &.{ .l = elifThen.l, .r = ifTrue.l }); try elifs.append(.{ .cond = elifCond, .then = elifThen }); } try self.devour(.ELSE); // try self.devour(.COLON); // DESIGN: should this be here??? const ifFalse = try self.expression(); try self.typeContext.unify(ifTrue.t, ifFalse.t, &.{ .l = ifTrue.l, .r = ifFalse.l }); return self.allocExpr(.{ .t = ifTrue.t, .l = self.loc(iftok).between(ifFalse.l), .e = .{ .IfElse = .{ .cond = condition, .ifTrue = ifTrue, .ifOthers = elifs.items, .ifFalse = ifFalse, }, }, }); } // if expr else if (self.consume(.CASE)) |casetok| { const switchOn = try self.expression(); const l = self.loc(casetok).between(switchOn.l); const caseexpr = try self.allocExpr(.{ .t = try self.typeContext.fresh(), .e = .{ .CaseExpr = .{ .switchOn = switchOn, .refvar = self.deconRefVar(), .cases = &.{}, } }, .l = l, }); const oldMode = switch (self.mode) { .Simple => |simp| simp, .Multiline => |ml| b: { // This means we are trying to define two multiline lambdas on the same line. This is BRUH! try self.reportError(.{ .TriedDefiningSecondMultilineOnSameLine = .{ .loc = l }, }); break :b ml.prev; }, }; self.mode = .{ .Multiline = .{ .prev = oldMode, .this = .{ .Case = .{ .caseExpr = caseexpr } }, } }; return caseexpr; } // case expr else if (self.consume(.IDENTIFIER)) |v| { const dv = try self.instantiateVar(&.{}, v); return self.allocExpr(.{ .t = dv.t, .e = .{ .Var = .{ .v = dv.v, .match = dv.m, .locality = dv.l, } }, .l = self.loc(v), }); } // var else if (self.consume(.INTRINSIC)) |intrTok| { var l = self.loc(intrTok); // for intrinsics, we must IMMEDIATELY parse the call - we don't want to deal with them being passed around. const fullIntr = intrTok.literal(self.lexer.source); const intrName = fullIntr[1..]; if (Intrinsic.findByName(intrName)) |intr| { // parse any required arguments brah. var args = std.ArrayList(*AST.Expr).init(self.arena); if (intr.args > 0) { try self.devour(.LEFT_PAREN); for (0..intr.args) |i| { try args.append(try self.expression()); if (i != intr.args - 1) { try self.devour(.COMMA); } else { const lastTok = try self.expect(.RIGHT_PAREN); l = l.between(self.loc(lastTok)); } } } const t = switch (intr.ty) { .cast => try self.typeContext.fresh(), .panic => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.ConstStr), &.{ .l = args.items[0].l }); // todo: remove or use ConstStr? break :b try self.definedType(.Unit); }, .undefined => try self.typeContext.fresh(), .@"size-of" => try self.definedType(.Size), .@"offset-ptr" => b: { // arg 1 const ptr = (try self.defined(.Ptr)).dataInst; std.debug.assert(args.items.len == 2); // right now, we have a parse error that aborts execution if number of args is not correct. When it changes,must change this. try self.typeContext.unify(ptr.t, args.items[0].t, &.{ .l = args.items[0].l }); // arg 2 try self.typeContext.unify(try self.definedType(.I64), args.items[1].t, &.{ .l = args.items[1].l }); break :b ptr.t; }, .argc => try self.definedType(.Size), .argv => b: { const ptr = (try self.defined(.Ptr)).dataInst; try self.typeContext.unify(ptr.tyArgs[0].Type, try self.definedType(.ConstStr), null); break :b ptr.t; }, .memeq => b: { try self.typeContext.unify(args.items[0].t, args.items[1].t, &.{ .l = l, }); break :b try self.definedType(.Bool); }, .errno => try self.definedType(.I32), .@"register-signal" => b: { const sigty = try self.definedType(.I32); const unit = try self.definedType(.Unit); try self.typeContext.unify(args.items[0].t, sigty, &.{ .l = l }); const funty = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{sigty}, .ret = unit, } }); try self.typeContext.unify(funty, self.modules.signalFunTy, &.{ .l = l }); try self.typeContext.unify(args.items[1].t, funty, &.{ .l = l }); break :b unit; }, .@"size-f32" => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.Size), &.{ .l = l }); break :b try self.definedType(.F32); }, .@"size-f64" => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.Size), &.{ .l = l }); break :b try self.definedType(.F64); }, .@"f32-f64" => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.F32), &.{ .l = l }); break :b try self.definedType(.F64); }, .@"f64-i64-floor" => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.F64), &.{ .l = l }); break :b try self.definedType(.I64); }, .@"i32-i64" => b: { try self.typeContext.unify(args.items[0].t, try self.definedType(.I32), &.{ .l = l }); break :b try self.definedType(.I64); }, .@"u32-bit-and", .@"u32-bit-or" => b: { const u32ty = try self.definedType(.U32); try self.typeContext.unify(args.items[0].t, u32ty, &.{ .l = l }); try self.typeContext.unify(args.items[1].t, u32ty, &.{ .l = l }); break :b u32ty; }, .@"u32-bit-neg" => b: { const u32ty = try self.definedType(.U32); try self.typeContext.unify(args.items[0].t, u32ty, &.{ .l = l }); break :b u32ty; }, .@"i64-add", .@"i64-sub", .@"i64-mul", .@"i64-div", .@"u64-add", .@"u64-sub", .@"u64-mul", .@"u64-div", .@"i32-add", .@"i32-sub", .@"i32-mul", .@"i32-div", .@"u32-add", .@"u32-sub", .@"u32-mul", .@"u32-div", .@"u8-add", .@"u8-sub", .@"u8-mul", .@"u8-div", .@"size-add", .@"size-sub", .@"size-mul", .@"size-div", .@"f32-add", .@"f32-sub", .@"f32-mul", .@"f32-div", .@"f64-add", .@"f64-sub", .@"f64-mul", .@"f64-div", => b: { const intTy = switch (intr.ty) { .@"i64-add", .@"i64-sub", .@"i64-mul", .@"i64-div" => try self.definedType(.I64), .@"u64-add", .@"u64-sub", .@"u64-mul", .@"u64-div" => try self.definedType(.U64), .@"i32-add", .@"i32-sub", .@"i32-mul", .@"i32-div" => try self.definedType(.I32), .@"u32-add", .@"u32-sub", .@"u32-mul", .@"u32-div" => try self.definedType(.U32), .@"u8-add", .@"u8-sub", .@"u8-mul", .@"u8-div" => try self.definedType(.U8), .@"f32-add", .@"f32-sub", .@"f32-mul", .@"f32-div" => try self.definedType(.F32), .@"f64-add", .@"f64-sub", .@"f64-mul", .@"f64-div" => try self.definedType(.F64), .@"size-add", .@"size-sub", .@"size-mul", .@"size-div" => try self.definedType(.Size), else => unreachable, }; try self.typeContext.unify(args.items[0].t, intTy, &.{ .l = args.items[0].l }); try self.typeContext.unify(args.items[1].t, intTy, &.{ .l = args.items[1].l }); break :b intTy; }, .@"i64-cmp", .@"u64-cmp", .@"i32-cmp", .@"u32-cmp", .@"u8-cmp", .@"f32-cmp", .@"f64-cmp", .@"size-cmp", => b: { const intTy = try switch (intr.ty) { .@"i64-cmp" => self.definedType(.I64), .@"i32-cmp" => self.definedType(.I32), .@"u32-cmp" => self.definedType(.U32), .@"u64-cmp" => self.definedType(.U64), .@"f32-cmp" => self.definedType(.F32), .@"f64-cmp" => self.definedType(.F64), .@"size-cmp" => self.definedType(.Size), .@"u8-cmp" => self.definedType(.U8), else => unreachable, }; const ordTy = try self.definedType(.Ordering); try self.typeContext.unify(args.items[0].t, intTy, &.{ .l = args.items[0].l }); try self.typeContext.unify(args.items[1].t, intTy, &.{ .l = args.items[1].l }); break :b ordTy; }, }; return self.allocExpr(.{ .t = t, .e = .{ .Intrinsic = .{ .intr = intr, .args = args.items, }, }, .l = l, }); } else { // NOTE: after this error there are bound to be shitty errors about trying to call some intrinsic type. // Most likely, the intrinsic has args, BUT it's possible we are calling `@undefined` for example, but we end up consuming the call. // TODO: think about it, I should probably implement that skipping. try self.reportError(.{ .UndefinedIntrinsic = .{ .name = fullIntr, .loc = l, } }); return self.allocExpr(.{ .t = try self.typeContext.fresh(), .e = .{ .Intrinsic = .{ // placeholder .intr = .{ .ty = .undefined, .args = 0, }, .args = &.{}, }, }, .l = l, }); } } // intrinsic else if (self.consume(.TYPE)) |con| { return try self.qualified(con); } // con else if (self.consumeInteger()) |i| { const intTy = try self.definedType(.Size); const retTy = try self.typeContext.fresh(); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{intTy}, .ret = retTy, } }); const class = try self.definedClass(.FromIntegral); const cfun = class.classFuns[0]; const l = self.loc(i); const ifun = try self.instantiateClassFunction(cfun, l); try self.typeContext.unify(funTy, ifun.t, &.{ .l = l }); return self.allocExpr(.{ .t = retTy, .e = .{ .Int = .{ .int = self.parseInt(i), .ref = ifun.ref } }, .l = self.loc(i), }); } // integer else if (self.consume(.FRACTIONAL)) |f| { return self.allocExpr(.{ .t = try self.definedType(.F64), .e = .{ .Float = self.parseFloat(f) }, .l = self.loc(f), }); } // fractional else if (self.consume(.STRING)) |s| { return try self.stringLiteral(s); } // string else if (self.consume(.LEFT_PAREN)) |lp| { // it might be a UNIT if (self.consume(.RIGHT_PAREN)) |rp| { const unit = try self.defined(.Unit); return self.allocExpr(.{ .t = unit.dataInst.t, .e = .{ .Con = &unit.data.stuff.cons[0] }, .l = self.loc(lp).between(self.loc(rp)), }); } const expr = try self.expression(); // check if we're defining a tuple. if (self.check(.COMMA)) { var tups = std.ArrayList(*AST.Expr).init(self.arena); try tups.append(expr); const rp = b: while (true) { try tups.append(try self.expression()); if (self.consume(.RIGHT_PAREN)) |rp| break :b rp; try self.devour(.COMMA); }; std.debug.assert(tups.items.len > 1); const tupty = try switch (tups.items.len) { 2 => self.defined(.Tuple2), 3 => self.defined(.Tuple3), 4 => self.defined(.Tuple4), else => unreachable, }; var confuntys = try self.arena.alloc(AST.Type, tups.items.len); for (tups.items, 0..) |et, i| { try self.typeContext.unify(tupty.dataInst.tyArgs[i].Type, et.t, null); confuntys[i] = et.t; } // setup call const l = self.loc(lp).between(self.loc(rp)); return try self.allocExpr(.{ .e = .{ .Call = .{ .callee = try self.allocExpr(.{ .e = .{ .Con = &tupty.data.stuff.cons[0] }, .t = try self.typeContext.newType(.{ .Fun = .{ .args = confuntys, .ret = tupty.dataInst.t, .env = try self.typeContext.newEnv(try TypeContext.Env.empty(self.gen.envs.newUnique(), self.arena)), } }), .l = l, }), .args = tups.items, } }, .t = tupty.dataInst.t, .l = l, }); } else { try self.devour(.RIGHT_PAREN); return expr; } } // grouping else if (self.consume(.LEFT_BRACE)) |leftTok| { const definitionsAndLoc = try self.someRecordDefinition(); const definitions = definitionsAndLoc.fields; const rightLoc = definitionsAndLoc.rightLoc; // TODO: deduplicate (and, in this case, error out) const typeFields = try self.arena.alloc(AST.TypeF(AST.Type).Field, definitions.len); for (definitions, 0..) |def, i| { typeFields[i] = .{ .t = def.value.t, .field = def.field }; } const t = try self.typeContext.newAnon(typeFields); return self.allocExpr(.{ .e = .{ .AnonymousRecord = definitions }, .t = t, .l = self.loc(leftTok).between(rightLoc), }); } // anonymous struct. else if (self.consume(.LEFT_SQBR)) |ltok| { var listLikeThing = std.ArrayList(*AST.Expr).init(self.arena); const elemTy = try self.typeContext.fresh(); const l: Loc = if (self.consume(.RIGHT_SQBR)) |rtok| b: { break :b self.loc(ltok).between(self.loc(rtok)); } else b: { while (true) { const expr = try self.expression(); try self.typeContext.unify(expr.t, elemTy, &.{ .l = expr.l }); // TODO: unify all of them AFTER. Then, you can use the location of the whole list to use as .{ .r } to stand for elemTy. try listLikeThing.append(expr); if (self.consume(.RIGHT_SQBR)) |rtok| { break :b self.loc(ltok).between(self.loc(rtok)); } try self.devour(.COMMA); } }; const class = try self.definedClass(.ListLike); const cfun = class.classFuns[0]; const ifn = try self.instantiateClassFunction(cfun, l); const selfType = try self.typeContext.fresh(); const arrInst = try self.defined(.Array); try self.typeContext.unifyNum(arrInst.dataInst.tyArgs[0].Num, try self.typeContext.newNum(.{ .Literal = @intCast(listLikeThing.items.len), }), &.{ .l = l }, &.{ .lfull = arrInst.dataInst.t, .rfull = null }); try self.typeContext.unify(arrInst.dataInst.tyArgs[1].Type, elemTy, &.{ .l = l }); // static array expression const arr = try self.allocExpr(.{ .e = .{ .StaticArray = listLikeThing.items, }, .t = arrInst.dataInst.t, .l = l, }); // &[...] expr const arrPtr = (try self.defined(.Ptr)).dataInst; try self.typeContext.unify(arrPtr.tyArgs[0].Type, arrInst.dataInst.t, &.{ .l = l }); // we create a ref expression to not have to update the backends :) const arg = try self.allocExpr(.{ .e = .{ .UnOp = .{ .e = arr, .op = .Ref } }, .t = arrPtr.t, .l = l, }); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{arrPtr.t}, .ret = selfType, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); const args = try self.arena.alloc(*AST.Expr, 1); args[0] = arg; const callee = try self.allocExpr(.{ .e = .{ .Var = .{ .v = .{ .ClassFun = .{ .cfun = cfun, .ref = ifn.ref, } }, .match = ifn.m, .locality = locality(self.env, cfun.class.level), }, }, .t = funTy, .l = l, }); return try self.allocExpr(.{ .e = .{ .Call = .{ .callee = callee, .args = args, } }, .t = selfType, .l = l, }); } // list-like thing else { return try self.errorExpect("term"); } } // isolate this in a function, because its long AND it shares stuff with the normal function() fn multilineLambda(self: *Self, tempLoc: Loc) !void { const lamMode = self.mode.Multiline; self.scope.restoreScope(lamMode.this.Lambda.scope); self.env = .{ .env = lamMode.this.Lambda.env, .fun = null }; // CRAP CODE!!! const ret = try self.typeContext.fresh(); const lamty = lamMode.this.Lambda.lamExpr.t; const funty = self.typeContext.getType(lamty).Fun; try self.typeContext.unify(funty.ret, ret, null); const oldReturnType = self.returnType; defer self.returnType = oldReturnType; self.returnType = ret; // COPYPASTA, but needs defer, so its okay? I might group these statements together in a function? const oldTriedReturningAtAll = self.triedReturningAtAll; const oldReturned = self.returned; defer { self.triedReturningAtAll = oldTriedReturningAtAll; self.returned = oldReturned; } self.triedReturningAtAll = false; self.returned = .Nah; const bod = try self.body(); var stmts = bod.stmts; self.endScope(); // this also assigns self.env. try self.finishBodyAndInferReturnType(&stmts, bod.returnStatus, tempLoc); // TEMP. I should return the location of the last statement (but I don''t have locations in statements yet.') const lamenv = lamMode.this.Lambda.env; const envty = try self.typeContext.newEnv(.{ .env = lamenv, .fun = null, .match = &AST.Match.Empty, .level = lamenv.level, }); try self.typeContext.unifyUnion(funty.env, envty, &.{ .l = lamMode.this.Lambda.lamExpr.l }, &.{ .lfull = lamty, .rfull = null }); lamMode.this.Lambda.lamExpr.e.Lam.body.Body = .{ .stmts = stmts.items, .ret = ret, }; lamMode.this.Lambda.lamExpr.e.Lam.env = lamenv; self.mode = .{ .Simple = switch (lamMode.prev) { .Normal => .Normal, .CountIndent => |i| .{ .CountIndent = .{ .indent = i.indent, .hadMultiline = true, }, }, }, }; } // NOTE: this is seriously unfinished! fn caseExpr(self: *Self, prev: ParsingMode.Simple, caseexpr: *AST.Expr, tempLoc: Loc) !void { _ = tempLoc; // COPYPASTA self.mode = .{ .Simple = .Normal }; const switchOn = caseexpr.e.CaseExpr.switchOn; const refvar = caseexpr.e.CaseExpr.refvar; const exprRetTy = caseexpr.t; // var returnStatus = ReturnStatus.Returned; // mempty-like var cases = std.ArrayList(AST.Expr.ExprCase).init(self.arena); try self.devour(.INDENT); self.beginScope(); while (!self.check(.DEDENT)) { const decon = try self.deconstruction(refvar); try self.typeContext.unify(switchOn.t, decon.t, &.{ .l = switchOn.l, .r = decon.l }); if (self.check(.COLON)) { const oldMode = self.foldFromHere(); const exp = try self.expression(); try self.typeContext.unify(exprRetTy, exp.t, &.{ .l = exp.l }); try cases.append(.{ .Expr = .{ .decon = decon, .expr = exp } }); try self.finishFold(oldMode); } else { unreachable; // TODO // const bod = try self.body(); // try cases.append(.{ .Case = .{ .decon = decon, .body = bod.stmts.items } }); // returnStatus = returnStatus.alternative(bod.returnStatus); } } self.endScope(); // self.returned = returnStatus; // check here for exhaustiveness. If not exhaustive, add .Nah OR I disallow non-exhaustive cases. caseexpr.e.CaseExpr.cases = cases.items; self.mode = .{ .Simple = switch (prev) { .Normal => .Normal, .CountIndent => |i| .{ .CountIndent = .{ .indent = i.indent, .hadMultiline = true, }, }, }, }; } fn qualified(self: *Self, first: Token) !*AST.Expr { if (first.type == .IDENTIFIER) { const dv = try self.instantiateVar(&.{}, first); return self.allocExpr(.{ .t = dv.t, .e = .{ .Var = .{ .v = dv.v, .match = dv.m, .locality = dv.l, } }, .l = self.loc(first), }); } // single identifier else if (first.type == .TYPE) { if (self.check(.DOT)) { // fallthrough } else if (self.check(.LEFT_BRACE)) { return try self.namedRecordDefinition(&.{}, first); } else { return try self.constructorExpression(&.{}, first); } } // single constructor else unreachable; var modpath = std.ArrayList(Str).init(self.arena); try modpath.append(first.literal(self.lexer.source)); var l = self.loc(first); loop: while (true) { if (self.consume(.TYPE)) |possibleCon| { if (self.check(.DOT)) { try modpath.append(possibleCon.literal(self.lexer.source)); l = l.between(self.loc(possibleCon)); continue :loop; } else if (self.check(.LEFT_BRACE)) { return try self.namedRecordDefinition(modpath.items, possibleCon); } else { return try self.constructorExpression(modpath.items, possibleCon); } } else if (self.consume(.IDENTIFIER)) |v| { const dv = try self.instantiateVar(modpath.items, v); return self.allocExpr(.{ .t = dv.t, .e = .{ .Var = .{ .v = dv.v, .match = dv.m, .locality = dv.l, } }, .l = l, }); } else { return try self.errorExpect("rest of qualification"); } } } fn namedRecordDefinition(self: *Self, modpath: Module.Path, name: Token) !*AST.Expr { const definitionsAndLoc = try self.someRecordDefinition(); const fieldsLoc = definitionsAndLoc.rightLoc; const definitions = definitionsAndLoc.fields; // instantiate it. const mDataOrClass = try self.findQualifiedDataOrClass(modpath, name.literal(self.lexer.source), self.loc(name)); if (mDataOrClass) |dataOrClass| { switch (dataOrClass) { .Data => |data| { switch (data.stuff) { .recs => |dataFields| { const dataInst = try self.instantiateData(data, self.loc(name)); const match = dataInst.match; // check if all fields were defined for (dataFields) |annDF| { const dataField = annDF.rec; for (definitions) |def| { if (Common.streq(dataField.field, def.field)) { try self.typeContext.unify(def.value.t, try self.typeContext.mapType(match, dataField.t), &.{ .l = self.loc(name) }); break; } } else { // when a field is not defined. try self.reportError(.{ .DidNotDefineField = .{ .field = dataField.field, .loc = fieldsLoc, } }); } } return self.allocExpr(.{ .t = dataInst.t, .e = .{ .NamedRecord = .{ .data = data, .fields = definitions, }, }, .l = self.loc(name), }); }, .cons => { try self.reportError(.{ .DataIsNotARecord = .{ .data = data } }); // fallthrough to return placeholder. }, } }, .Synonym => unreachable, // TODO (I allow it, but todo implement) .Class => { // ~fallthrough and return placeholder.~ // I THOUGHT THIS WOULD BE AN ERROR. // BUT SINCE WE WANT ANONYMOUS STRUCTS TO COERCE TO TYPES, MAYBE THIS SHOULD SPAWN A // - fresh type // - with class constraint // - with field constraints. unreachable; }, } } else { // error already reported. // fall to add placeholder. } // PLACEHOLDER EXPR. std.debug.assert(self.errors.items.len > 0); return try self.allocExpr(.{ .e = .{ .AnonymousRecord = &.{} }, .t = try self.typeContext.fresh(), .l = self.loc(name), }); } // either anonymous or normal :) // checks for duplicates. fn someRecordDefinition(self: *Self) !struct { fields: []AST.Expr.Field, rightLoc: Loc } { var definitions = std.ArrayList(AST.Expr.Field).init(self.arena); while (true) { const fieldTok = try self.expect(.IDENTIFIER); const fieldName = fieldTok.literal(self.lexer.source); const expr = if (self.check(.COLON)) b: { break :b try self.expression(); } else b: { const varInst = try self.instantiateVar(&.{}, fieldTok); // { x } => { x: x } TODO: maybe disallow anything except simple var definitions. Even more, maybe allow only current scope / env? break :b try self.allocExpr(.{ .t = varInst.t, .e = .{ .Var = .{ .v = varInst.v, .match = varInst.m, .locality = varInst.l, } }, .l = self.loc(fieldTok), }); }; // check for duplication. for (definitions.items) |field| { if (Common.streq(field.field, fieldName)) { try self.reportError(.{ .DuplicateField = .{ .field = fieldName }, }); break; } } else { // not a duplicate. try definitions.append(.{ .field = fieldName, .value = expr }); } if (!self.check(.COMMA)) break; } const rightLoc = try self.expect(.RIGHT_BRACE); return .{ .fields = definitions.items, .rightLoc = self.loc(rightLoc) }; } fn parseQualifiedType(self: *Self, first: Token) !struct { modpath: Module.Path, name: Str, loc: Common.Location } { if (self.peek().type != .DOT) { return .{ .modpath = &.{}, .name = first.literal(self.lexer.source), .loc = self.loc(first) }; } var fullPath = try std.ArrayList(Str).initCapacity(self.arena, 1); try fullPath.append(first.literal(self.lexer.source)); var qloc = self.loc(first); while (self.check(.DOT)) { const tt = try self.expect(.TYPE); try fullPath.append(tt.literal(self.lexer.source)); qloc = qloc.between(self.loc(tt)); } const modpath = fullPath.items[0 .. fullPath.items.len - 1]; const tyname = fullPath.getLast(); return .{ .modpath = modpath, .name = tyname, .loc = qloc }; } // right now only used for records. In the future, will be used for qualifying types themselves. // ALSO, TODO we might abstract away the stuff about getting the module, because it's annoying and it's not immediately obvious how I should handle that error. So, a fn (modpath) -> ?Module (but also throw error when module == null) fn findQualifiedDataOrClass(self: *Self, modpath: Module.Path, name: Str, dloc: Common.Location) !?Module.DataOrClass { if (modpath.len == 0) { if (self.maybeLookupType(name)) |dataOrClass| { return dataOrClass; } else { try self.reportError(.{ .UndefinedType = .{ .typename = name, .loc = dloc, } }); return null; } } else { if (try self.loadModuleFromPath(modpath, dloc)) |mod| { if (mod.lookupData(name)) |data| { return data; } else { try self.reportError(.{ .UndefinedType = .{ .typename = name, .loc = dloc, }, }); return null; } } else { // circular dep?? // return null; unreachable; } } unreachable; } // properly instantiates a constructor expression. (remembers function types n shit) fn constructorExpression(self: *Self, modpath: Module.Path, name: Token) !*AST.Expr { const ct = try self.instantiateCon(modpath, name); const t = if (ct.tys.len == 0) ct.t else try self.typeContext.newType(.{ .Fun = .{ .args = ct.tys, .ret = ct.t, .env = try self.typeContext.newEnv(try TypeContext.Env.empty(self.gen.envs.newUnique(), self.arena)), // nocheckin: we have to figure out if the env is the same. }, }); return self.allocExpr(.{ .e = .{ .Con = ct.con }, .t = t, .l = self.loc(name), }); } // TODO: handle errors in literals // Also, make it legible. fn stringLiteral(self: *Self, st: Token) !*AST.Expr { const og = st.literal(self.lexer.source); // this includes single quotes // handling chars // currently, a string of 1 character will always be a char. // this is obviously bad, since we sometimes want one-char strings. // The thing is: kc code should be polymorphic enough to support it, // but we sometimes (incorrectly, but out of laziness) rely on ConstStr == const char*, which will be bad and will force us to allocate. // if (og.len == 3) { // 1 + two `'` // return try self.allocExpr(.{ // .e = .{ .Char = og[1] }, // .l = self.loc(st), // .t = try self.definedType(.Char), // }); // } var e: ?*AST.Expr = null; var s = std.ArrayList(u8).init(self.arena); var i: usize = 1; var last: usize = i; while (i < og.len - 1) { const ci = i; const c = og[ci]; if (c == '\\') { i += 2; switch (og[i - 1]) { '(' => { const start = i; if (last != ci) { const se = try self.constStr(try s.toOwnedSlice(), .{ .from = st.from + last, // this is probably incorrect. .to = st.from + ci, .line = self.lexer.line, // should be correct... right? .module = .{ .source = self.lexer.source, .name = self.name, }, }); if (e) |ee| { e = try self.strConcat( ee, se, ); } else { e = se; } } // BAD BAD BAD BAD while (og[i] != ')' and og[i] != '.' and og[i] != '&') i += 1; const v = try self.instantiateVar(&.{}, .{ .from = st.from + start, .to = st.from + i, .type = .IDENTIFIER, .line = st.line, }); var varExpr = try self.allocExpr(.{ .e = .{ .Var = .{ .v = v.v, .match = v.m, .locality = v.l, } }, .t = v.t, .l = .{ .from = st.from + start, .to = st.from + i, .line = st.line, .module = .{ .source = self.lexer.source, .name = self.name, }, }, }); while (true) { switch (og[i]) { '.' => { i += 1; const lastLast = i; while (og[i] != ')' and og[i] != '.' and og[i] != '&') i += 1; const field = og[lastLast..i]; const fieldLoc = Loc{ .from = st.from + lastLast, .to = st.from + i, .line = st.line, .module = .{ .name = self.name, .source = self.lexer.source, }, }; const t = try self.typeContext.field( varExpr.t, field, &.{ .l = varExpr.l, .r = fieldLoc, }, ); varExpr = try self.allocExpr(.{ .t = t, .e = .{ .UnOp = .{ .e = varExpr, .op = .{ .Access = field, }, } }, .l = varExpr.l.between(fieldLoc), }); }, '&' => { i += 1; const ptr = (try self.defined(.Ptr)).dataInst; const l = Loc{ .from = st.from + i - 1, .to = st.from + i, .line = st.line, .module = .{ .name = self.name, .source = self.lexer.source, }, }; try self.typeContext.unify( ptr.t, varExpr.t, &.{ .l = varExpr.l, .r = l, }, ); varExpr = try self.allocExpr(.{ .t = ptr.tyArgs[0].Type, .e = .{ .UnOp = .{ .op = .Deref, .e = varExpr }, }, .l = l, }); }, ')' => { i += 1; break; }, else => unreachable, // error } } if (e) |ee| { e = try self.strConcat(ee, varExpr); } else { e = varExpr; } last = i; }, 't' => try s.append('\t'), 'n' => try s.append('\n'), 'r' => try s.append('\r'), '\\' => try s.append('\\'), '\'' => try s.append('\''), 'x' => { // TODO: do error checking. const hex = og[i .. i + 2]; const num = std.fmt.parseInt(u8, hex, 16) catch unreachable; i += 2; try s.append(num); }, '0' => try s.append(0), else => unreachable, // TODO handle errors } } else { try s.append(c); i += 1; } } if (last != i) { const se = try self.constStr(try s.toOwnedSlice(), .{ // NOTE: same problem as the loc definition for string in the beginning. .from = st.from + last, .to = st.from + i, .line = self.lexer.line, .module = .{ .source = self.lexer.source, .name = self.name, }, }); if (e) |ee| { e = try self.strConcat( ee, se, ); } else { e = se; } } return e orelse try self.constStr(&.{}, self.loc(st)); } fn constStr(self: *Self, s: Str, l: Loc) !*AST.Expr { // NOTE(12.07.26): the return type currently does not distinguish whether a Char or a String was detected. keep in mind, that the representation might change, and then this whole thing must be split. const cst = try self.constStrType(s, l); const ptrArg = try self.allocExpr(.{ .e = .{ .Str = s, }, .t = cst.ogStrTy, .l = l, }); const sizeArg = try self.allocExpr(.{ .e = .{ .ConstSize = s.len, }, .t = try self.definedType(.Size), .l = l, }); const args = try self.arena.alloc(*AST.Expr, 2); args[0] = ptrArg; args[1] = sizeArg; const callee = try self.allocExpr(.{ .e = .{ .Var = .{ .v = .{ .ClassFun = .{ .cfun = cst.cfun, .ref = cst.ref, } }, .match = cst.m, .locality = locality(self.env, cst.cfun.class.level), }, }, .t = cst.funTy, .l = l, }); return try self.allocExpr(.{ .e = .{ .Call = .{ .callee = callee, .args = args, } }, .t = cst.selfTy, .l = l, }); } fn constStrType(self: *Self, s: Str, l: Loc) !struct { ref: AST.InstFunInst, cfun: *const AST.ClassFun, m: *const AST.Match, ogStrTy: AST.Type, funTy: AST.Type, selfTy: AST.Type, } { // NOTE(05.06.26): should FromChar operate on a 1-byte character or a utf8 grapheme cluster? // currently we use the shitty unicode definition of a single character. // is this correct doe? // like, often we want AsciiChars... // and because of this, we might need to throw a runtime error when we use a utf8 single character for an ascii char. // also, there is little need to distinguish Strings and Characters when it's a pointer to allocated memory anyway... // on the other hand, we may want to compare characters directly, like c == '병' for example. if (s.len > 0 and Common.isSingleCharacter(s)) { const retTy = try self.typeContext.fresh(); const class = try self.definedClass(.FromChar); const cfun = class.classFuns[0]; const ifn = try self.instantiateClassFunction(cfun, l); const ptrTy = try self.ptrTo(try self.definedType(.U8)); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ ptrTy, try self.definedType(.Size), }, .ret = retTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); return .{ .ref = ifn.ref, .cfun = cfun, .m = ifn.m, .ogStrTy = ptrTy, .funTy = funTy, .selfTy = retTy, }; } else { const retTy = try self.typeContext.fresh(); const class = try self.definedClass(.FromString); const cfun = class.classFuns[0]; const ifn = try self.instantiateClassFunction(cfun, l); const ptrTy = try self.ptrTo(try self.definedType(.U8)); const funTy = try self.makeType(.{ .Fun = .{ .args = [_]AST.Type{ ptrTy, try self.definedType(.Size), }, .ret = retTy, } }); try self.typeContext.unify(ifn.t, funTy, &.{ .l = l }); return .{ .ref = ifn.ref, .cfun = cfun, .m = ifn.m, .ogStrTy = ptrTy, .funTy = funTy, .selfTy = retTy, }; } } fn locality(menv: ?AST.EnvFun, compLevel: usize) AST.Locality { if (menv) |env| { return if (compLevel >= env.env.level) .Local else .External; } else { return .Local; } } fn strConcat(self: *Self, l: *AST.Expr, r: *AST.Expr) !*AST.Expr { const sc = try self.defined(.StrConcat); const sci = sc.dataInst; try self.typeContext.unify(sci.tyArgs[0].Type, l.t, &.{ .l = l.l }); try self.typeContext.unify(sci.tyArgs[1].Type, r.t, &.{ .l = r.l }); const args = try self.arena.alloc(*AST.Expr, 2); args[0] = l; args[1] = r; const tyArgs = try self.arena.alloc(AST.Type, 2); tyArgs[0] = args[0].t; tyArgs[1] = args[1].t; return self.allocExpr(.{ .t = sci.t, .e = .{ .Call = .{ .callee = try self.allocExpr(.{ .t = try self.typeContext.newType(.{ .Fun = .{ .ret = sci.t, .args = tyArgs, .env = try self.typeContext.newEnv(.{ .env = try Common.allocOne(self.arena, AST.Env.empty(self.gen.envs.newUnique())), .match = sci.match, .fun = null, .level = 0, }), } }), .e = .{ .Con = &sc.data.stuff.cons[0] }, .l = l.l.between(r.l), }), .args = args, } }, .l = l.l.between(r.l), }); } fn allocExpr(self: *const Self, ev: AST.Expr) error{OutOfMemory}!*AST.Expr { const e = try self.arena.create(AST.Expr); e.* = ev; return e; } fn getBinOp(tok: Token) ?AST.BinOp { return switch (tok.type) { // TODO: I think in the future, match on TokenTypes and only after map them. This is very iffy bruh. .PLUS => .{ .Plus = undefined }, .MINUS => .{ .Minus = undefined }, .TIMES => .{ .Times = undefined }, .SLASH => .{ .Divide = undefined }, .EQEQ => .{ .Equals = undefined }, .NOTEQ => .{ .NotEquals = undefined }, .LT => .{ .LessThan = undefined }, .LTEQ => .{ .LessEqualThan = undefined }, .GT => .{ .GreaterThan = undefined }, .GTEQ => .{ .GreaterEqualThan = undefined }, .OR => .Or, .AND => .And, .LEFT_PAREN => .Call, .REF => .Deref, .IDENTIFIER => .PostfixCall, .TYPE => .PostfixCall, .DOT => .RecordAccess, .LEFT_BRACE => .RecordUpdate, .AS => .As, .LEFT_SQBR => .ElementAccess, else => null, }; } // here and not in AST.BinOp, because precedence only matters for parsing. fn binOpPrecedence(op: AST.BinOp) u32 { return switch (op) { // 0 means it won't be consumed, like a sentinel value. .As => 1, .Or => 2, .And => 3, // .Not => 4, .Equals => 8, .NotEquals => 8, .LessThan => 8, .LessEqualThan => 8, .GreaterThan => 8, .GreaterEqualThan => 8, .Plus => 12, .Minus => 12, .Times => 14, .Divide => 14, // .Negation => 16, .Call => 18, .RecordAccess => 18, .RecordUpdate => 18, .Deref => 18, .PostfixCall => 18, .ElementAccess => 18, // else => unreachable, }; } const Type = struct { const Constrain = union(enum) { Data: WithAssocs, ClassFunction: WithAssocs, Function: struct { uid: Unique }, ExternalFunction: struct { uid: Unique }, const WithAssocs = struct { uid: Unique, assocs: *std.ArrayList(AST.Association) }; }; constrain: ?Constrain, parser: *Self, fn init(self: *Self, constrain: ?Constrain) @This() { return .{ .constrain = constrain, .parser = self }; } fn binding(this: *const @This()) ?AST.Binding { return if (this.constrain) |c| switch (c) { .Data => |e| .{ .Data = e.uid }, .ClassFunction => |e| .{ .ClassFunction = e.uid }, .Function => |e| .{ .Function = e.uid }, .ExternalFunction => |e| .{ .Function = e.uid }, } else null; } // type-o fn typ(this: *const @This()) ParserError!LocdIn(AST.Type) { const self = this.parser; // temp if (self.consume(.TYPE)) |ty| { const ity = try this.qualifiedType(ty); if (ity.tyArgs.len != 0) { try self.reportError(.{ .MismatchingKind = .{ .data = ity.type.Data.data, .expect = ity.tyArgs.len, .actual = 0 } }); // TODO: this crashes when we use a type synonym. } return .{ .e = ity.t, .l = self.loc(ty) }; } else if (self.consume(.IDENTIFIER)) |tv| { // TVAR return .{ .e = try self.typeContext.newType(.{ .TVar = try self.lookupTVar(tv, this.binding()), }), .l = self.loc(tv), }; } else if (self.consume(.LEFT_PAREN)) |lp| { if (self.consume(.RIGHT_PAREN)) |rp| { return .{ .e = try self.definedType(.Unit), .l = self.loc(lp).between(self.loc(rp)), }; } const ty = try this.sepTyo(); if (self.check(.COMMA)) { var args = std.ArrayList(AST.Type).init(self.arena); try args.append(ty.e); const rp = b: while (true) { try args.append((try this.sepTyo()).e); if (self.consume(.RIGHT_PAREN)) |rp| break :b rp; try self.devour(.COMMA); }; const tupty = try switch (args.items.len) { 2 => self.defined(.Tuple2), 3 => self.defined(.Tuple3), 4 => self.defined(.Tuple4), else => unreachable, }; for (args.items, 0..) |arg, i| { try self.typeContext.unify(tupty.dataInst.tyArgs[i].Type, arg, null); } return .{ .e = tupty.dataInst.t, .l = self.loc(lp).between(self.loc(rp)), }; } else { try self.devour(.RIGHT_PAREN); return ty; } } else if (self.consume(.UNDERSCORE)) |tok| { if (self.selfType) |t| { return .{ .e = t, .l = self.loc(tok) }; } else { // underscore without declaration means "whatever" type. if (this.constrain == null) { return .{ .e = try self.typeContext.fresh(), .l = self.loc(tok) }; } else { // TODO: signal error unreachable; } } } else if (self.consume(.LEFT_BRACE)) |leftTok| { var fields = std.ArrayList(AST.TypeF(AST.Type).Field).init(self.arena); while (true) { const field = try self.expect(.IDENTIFIER); const t = try this.sepTyo(); try fields.append(.{ .t = t.e, .field = field.literal(self.lexer.source), }); if (!self.check(.COMMA)) break; } const rightTok = try self.expect(.RIGHT_BRACE); const l = self.loc(leftTok).between(self.loc(rightTok)); return .{ .e = try self.typeContext.newAnon(fields.items), .l = l, }; } else { try self.errorExpect("type"); } unreachable; } fn sepTyo(this: *const @This()) !LocdIn(AST.Type) { const self = this.parser; if (self.consume(.TYPE)) |tyName| { const ty = try this.qualifiedType(tyName); var tyArgs = std.ArrayList(AST.TypeOrNum).init(self.arena); var l = self.loc(tyName); var i: usize = 0; // bruh while (true) { defer i += 1; // BRUH const tokType = self.peek().type; if (!(tokType == .LEFT_PAREN or tokType == .TYPE or tokType == .IDENTIFIER or tokType == .UNDERSCORE or tokType == .NUMTYNAME or tokType == .INTEGER)) { // bad but works break; } if (i < ty.tyArgs.len and ty.tyArgs[i].isNum()) { // BRUHHHH const numTy: AST.TypeOrNum = b: { if (self.consume(.NUMTYNAME)) |numtyTok| { // we also accept carets to make sure we are using a number type. const tnum = try self.lookupTNum(numtyTok, this.binding()); break :b .{ .Num = try self.typeContext.newNum(.{ .TNum = tnum }) }; } else if (self.consume(.IDENTIFIER)) |numtyTok| { const tnum = try self.lookupTNum(numtyTok, this.binding()); break :b .{ .Num = try self.typeContext.newNum(.{ .TNum = tnum }) }; } else if (self.consume(.INTEGER)) |intTok| { const num = self.parseInt(intTok); break :b .{ .Num = try self.typeContext.newNum(.{ .Literal = num }) }; } else if (self.consume(.TYPE)) |_| { unreachable; // TODO: assert that it's a numeric type and get stored value. } else { unreachable; // TODO: error (or just quit and let unification throw an error.) } }; try tyArgs.append(numTy); continue; } const lt = try this.typ(); l = l.between(lt.l); try tyArgs.append(.{ .Type = lt.e }); } // simply check arity. if (ty.type != .Class) { try self.typeContext.unifyParamsWithTNums(ty.tyArgs, tyArgs.items, &.{ .l = l }, &.{ .lfull = ty.t, .rfull = null, }); } else if (tyArgs.items.len > 0) { unreachable; // TODO: error that says you cannot apply parameters to class. } // there's a possibility it's a function! if (self.check(.RIGHT_ARROW)) { const args = try self.arena.alloc(AST.Type, 1); args[0] = ty.t; const ret = try this.sepTyo(); return .{ .e = try self.typeContext.newType(.{ .Fun = .{ .args = args, .ret = ret.e, .env = if (this.constrain) |constr| (switch (constr) { .ExternalFunction => try self.typeContext.newEnv(try TypeContext.Env.empty(self.gen.envs.newUnique(), self.arena)), else => try self.typeContext.newEnv(null), }) else try self.typeContext.newEnv(null), }, }), .l = l.between(ret.l), }; } else { return .{ .e = ty.t, .l = l }; } } else if (self.consume(.LEFT_PAREN)) |ltok| { // try parse function (but it can also be an extra paren!) var l = self.loc(ltok); var args = std.ArrayList(AST.Type).init(self.arena); if (self.consume(.RIGHT_PAREN)) |rtok| { l = l.between(self.loc(rtok)); } else { while (true) { const t = try this.sepTyo(); l = l.between(t.l); try args.append(t.e); if (!self.check(.COMMA)) { break; } } const rtok = try self.expect(.RIGHT_PAREN); l = l.between(self.loc(rtok)); } if (self.check(.RIGHT_ARROW)) { const ret = try this.sepTyo(); return .{ .e = try self.typeContext.newType(.{ .Fun = .{ .ret = ret.e, .args = args.items, .env = try self.typeContext.newEnv(null), } }), .l = l.between(ret.l), }; } else if (args.items.len == 1) { // just parens! return .{ .e = args.items[0], .l = l }; } else if (args.items.len == 0) { // only Unit tuple is supported. return .{ .e = try self.definedType(.Unit), .l = l }; } else { const tupty = try switch (args.items.len) { 2 => self.defined(.Tuple2), 3 => self.defined(.Tuple3), 4 => self.defined(.Tuple4), else => unreachable, }; for (args.items, 0..) |arg, i| { try self.typeContext.unify(tupty.dataInst.tyArgs[i].Type, arg, null); } return .{ .e = tupty.dataInst.t, .l = l }; } } else if (self.consume(.IDENTIFIER)) |tv| { const tvt = try self.typeContext.newType(.{ .TVar = try self.lookupTVar(tv, this.binding()), }); if (self.consume(.RIGHT_ARROW)) |rtok| { const args = try self.arena.alloc(AST.Type, 1); args[0] = tvt; const ret = try this.sepTyo(); return .{ .e = try self.typeContext.newType(.{ .Fun = .{ .args = args, .ret = ret.e, .env = try self.typeContext.newEnv(null), } }), .l = self.loc(tv).between(self.loc(rtok)), }; } else { return .{ .e = tvt, .l = self.loc(tv) }; } } else { return try this.typ(); } unreachable; } fn qualifiedType(this: *const @This(), first: Token) !DataOrClassShit { const self = this.parser; const stuff = try self.parseQualifiedType(first); const modpath = stuff.modpath; const tyname = stuff.name; const qloc = stuff.loc; if (try self.findQualifiedDataOrClass(modpath, tyname, qloc)) |dataOrClass| { switch (dataOrClass) { .Data => |data| { const dt = try self.instantiateData(data, qloc); return .{ .t = dt.t, .tyArgs = dt.tyArgs, .type = .{ .Data = .{ .data = data, .match = dt.match, } }, }; }, .Synonym => |syn| { const match = try self.instantiateScheme(syn.scheme, null, qloc); const t = try self.typeContext.mapType(match, syn.t); return .{ .t = t, .tyArgs = match.tvars, .type = .Synonym, }; }, .Class => |class| { if (this.constrain) |constr| { switch (constr) { .Data, .ClassFunction => |data| { const tv: AST.TVar = .{ .uid = self.gen.tvars.newUnique(), .name = "miau:3", .binding = this.binding(), .inferred = false, .fields = &.{}, .fieldsTotal = false, }; try data.assocs.append(.{ .depends = tv, .uid = self.gen.assocs.newUnique(), .class = class, .default = class.default, .concrete = null, }); return .{ .t = try self.typeContext.newType(.{ .TVar = tv }), .tyArgs = &.{}, .type = .Class, }; }, .Function => { const t = try self.typeContext.fresh(); try self.addAssociation(.{ .from = t, .loc = qloc, // TODO! .class = class, .instances = try self.getInstances(), .default = class.default, .concrete = null, }); return .{ .t = t, .tyArgs = &.{}, .type = .Class, }; }, .ExternalFunction => unreachable, // TODO: error } unreachable; } else { const t = try self.typeContext.fresh(); try self.addAssociation(.{ .from = t, .loc = qloc, // TODO! .class = class, .instances = try self.getInstances(), .default = class.default, .concrete = null, }); return .{ .t = t, .tyArgs = &.{}, .type = .Class, }; } unreachable; }, } } else { return try self.newPlaceholderType( tyname, self.loc(first), ); } } }; // resolver zone // TODO: distinguish between non-existent module and it being current module. fn loadModuleFromPath(self: *Self, path: Module.Path, l: Loc) ParserError!?Module { // TODO: should return a const pointer, cuz Module is chunky. std.debug.assert(path.len > 0); // should not be called with empty path. if (self.importedModules.get(path)) |mmod| { return mmod; } const mmod = try self.modules.loadModule(.{ .ByModulePath = .{ .base = self.base, .path = path } }, l, .{}); try self.importedModules.put(path, mmod); // automatically add instances (like muh haskells) if (mmod) |mod| { try self.addAllInstances(&mod.exports); } return mmod; } fn addAllInstances(self: *Self, exports: *const Module.Exports) !void { var it = exports.instances.iterator(); while (it.next()) |insts| { var iit = insts.value_ptr.iterator(); while (iit.next()) |inst| { try self.addInstance(self.scope.scopes.botp(), inst.value_ptr.*); } } } // VARS fn newVar(self: *@This(), varTok: Token, deconUse: ?AST.DeconUse) !Module.VarAndType { const varName = varTok.literal(self.lexer.source); const t = try self.typeContext.fresh(); const thisVar = AST.Var{ .name = varName, .uid = self.gen.vars.newUnique(), }; try self.scope.currentScope().vars.put(varName, .{ .thing = .{ .Var = .{ .v = .{ .v = thisVar, .dc = deconUse }, .t = t, } } }); return .{ .v = .{ .v = thisVar, .dc = deconUse }, .t = t, }; } fn newFunction(self: *@This(), funNameTok: Token) !*AST.Function { const varName = funNameTok.literal(self.lexer.source); const thisVar = AST.Var{ .name = varName, .uid = self.gen.vars.newUnique(), }; const funPtr = try self.arena.create(AST.Function); funPtr.* = .{ // TODO: is this all really needed? The scheme is assigned empty in the function() anyways. I should inline all this code in the future. .name = thisVar, .scheme = AST.Scheme.empty(), .env = undefined, .params = undefined, .ret = undefined, .body = undefined, .temp__isRecursive = true, .temp__calls = std.ArrayList(AST.Function.Instantiation).init(self.arena), .temp__finishedParsing = false, .temp__mono = AST.Function.Mono.empty(self.typeContext, self.arena), }; try self.scope.currentScope().vars.put(varName, .{ .thing = .{ .Fun = funPtr } }); return funPtr; } fn lookupVar(self: *Self, modpath: Module.Path, varTok: Token) !struct { vorf: Module.VarOrFun, level: usize, } { const varName = varTok.literal(self.lexer.source); if (modpath.len == 0) { var lastVars = self.scope.scopes.iterateFromTop(); var lvl = self.level(); while (lastVars.nextPtr()) |cursc| { if (cursc.vars.get(varName)) |vorf| { return .{ .vorf = vorf.thing, .level = lvl }; } lvl -= 1; } } else { // TODO: better error when the variable is not defined in the module (currently has error "undefined variable") if (try self.loadModuleFromPath(modpath, self.loc(varTok))) |mod| { // TODO(errors): incorrect location. we have to pipe the module location through to here. if (mod.lookupVar(varName)) |vorf| { return .{ .vorf = vorf, .level = 0, }; } else { // FALLTHROUGH. // TODO: set source module to be of that found module. } } else { // i dunno, probably some other error. Ignore ig? // unreachable; // TEMP. I JUST NEED TO SEE WHEN THAT HAPPENS. // RE: it happens when I try to import the module I'm currently in. (this can be handled before tho, to give the appropriate error) } } const placeholderVar = AST.Var{ .name = varName, .uid = self.gen.vars.newUnique(), }; try self.reportError(.{ .UndefinedVariable = .{ .varname = placeholderVar, .loc = self.loc(varTok) }, }); const t = try self.typeContext.fresh(); // return placeholder var after an error. return .{ .vorf = .{ .Var = .{ .v = .{ .v = placeholderVar, .dc = null }, .t = t }, }, .level = 0, }; } const VarInst = struct { v: AST.Expr.VarType, t: AST.Type, m: *AST.Match, l: AST.Locality, }; fn instantiateVar(self: *@This(), modpath: Module.Path, varTok: Token) !VarInst { const vorfAndScope = try self.lookupVar(modpath, varTok); const vorf = vorfAndScope.vorf; const l: AST.Locality = locality(self.env, vorfAndScope.level); const varInst: VarInst = switch (vorf) { .TNum => |tnum| .{ .v = .{ .TNum = tnum }, .t = try self.definedType(.I32), .m = try Common.allocOne(self.arena, AST.Match.empty(AST.Scheme.empty())), .l = l, }, .Var => |vt| .{ .v = .{ .Var = vt.v }, .t = vt.t, .m = try Common.allocOne(self.arena, AST.Match.empty(AST.Scheme.empty())), .l = l, }, .Fun => |fun| b: { const funTyAndMatch = try self.instantiateFunction(fun, null, self.loc(varTok)); // not needed here, done in addToEnvIfPossible // try self.expandFunctionEnvIntoCurrentEnv(self.env, fun.env, funTyAndMatch.m); // add uses const use = AST.Function.Use{ .Fun = .{ .fun = fun, .m = funTyAndMatch.m, .t = funTyAndMatch.t } }; if (AST.EnvFun.getFun(self.env)) |envfun| { try envfun.temp__mono.uses.append(use); } else { try self.topLevels.append(use); } break :b .{ .v = .{ .Fun = fun }, .t = funTyAndMatch.t, .m = funTyAndMatch.m, .l = l, }; }, .ClassFun => |cfun| b: { const ifn = try self.instantiateClassFunction(cfun, self.loc(varTok)); const varInst = VarInst{ .v = .{ .ClassFun = .{ .cfun = cfun, .ref = ifn.ref, }, }, .t = ifn.t, .m = ifn.m, .l = l, }; // NOTE: "use" is added in the instantiateClassFunction break :b varInst; }, .Extern => |extfun| { const match = try self.instantiateScheme(extfun.scheme, null, self.loc(varTok)); var params = std.ArrayList(AST.Type).init(self.arena); for (extfun.params) |p| { try params.append(try self.typeContext.mapType(match, p.pt)); } const ret = try self.typeContext.mapType(match, extfun.ret); const funTy = try self.typeContext.newType(.{ .Fun = .{ .args = params.items, .ret = ret, .env = try self.typeContext.newEnv(try TypeContext.Env.empty(self.gen.envs.newUnique(), self.arena)), }, }); // NOTE: we just return. External functions are not added to the environment. return .{ .v = .{ .ExternalFun = extfun, }, .t = funTy, .m = match, .l = l, }; }, }; const isRecursive = switch (varInst.v) { .Fun => |fun| fun.temp__isRecursive, else => false, }; if (!isRecursive) b: { const envVar: AST.EnvVar = .{ .v = switch (varInst.v) { .ClassFun => break :b, // don't add a class function - it'll be added when solving constraints. .ExternalFun => unreachable, // extern functions are not added to the environment. .Fun => |fun| .{ .Fun = fun, }, .TNum => |tnum| .{ .TNum = tnum, }, .Var => |v| .{ .Var = v, }, }, .m = varInst.m, .t = varInst.t, .l = vorfAndScope.level, }; // try addToEnvUpUntilALevel(self.env, envVar, vorfAndScope.level); try self.addToEnvIfPossible(self.env, envVar, false); } return varInst; } // fn addToEnvUpUntilALevel(self: *Self, firstEnv: ?*AST.Env, inst: AST.EnvVar, lvl: usize) !void { // _ = self; // var menv: ?*AST.Env = firstEnv; // while (menv) |env| { // if (env.level <= lvl) { // // // } else { // try env.insts.append(inst); // } // menv = AST.EnvFun.getEnv(env.outer); // } // } // TODO: probably merge with addToEnvIfPossible or something. fn expandFunctionEnvIntoCurrentEnv(self: *Self, mcurrentEnv: ?*AST.Env, env: *AST.Env, m: *AST.Match) !void { const currentEnvLevel = if (mcurrentEnv) |currentEnv| currentEnv.level else 1; // 1 is the minimum level! // check if we need to add it to env at all. // if the env is from outer or equal scope, don't add the inner part. if (env.level <= currentEnvLevel) return; if (mcurrentEnv) |currentEnv| { for (env.insts.items) |inst| { // when it's from outside, add it whole. if (inst.l < currentEnvLevel) { const envVar: AST.EnvVar = .{ .v = inst.v, .t = try self.typeContext.mapType(m, inst.t), .m = (try self.typeContext.mapMatch(m, inst.m)) orelse inst.m, .l = inst.l, }; try currentEnv.insts.append(envVar); } // when it's from inside, check its environment recursively(its slow :((((((, and only should be done in monomorphisation). else { switch (inst.v) { .Fun => |fun| { try self.expandFunctionEnvIntoCurrentEnv(mcurrentEnv, fun.env, try self.typeContext.mapMatch(m, inst.m) orelse inst.m); }, else => {}, } } } for (m.assocs) |massoc| { if (massoc) |assoc| { switch (assoc) { .Id => {}, .InstFun => |pair| { const fun = pair.fun; _ = fun; // try self.expandFunctionEnvIntoCurrentEnv(mcurrentEnv, fun.env, try self.typeContext.mapMatch(m, pair.m) orelse pair.m); }, } } } } } // add to envs up until a function. fn addToEnvIfPossible(self: *Self, menv: ?AST.EnvFun, inst: AST.EnvVar, temp__solvingConstraints: bool) !void { // if (!temp__solvingConstraints) { _ = temp__solvingConstraints; if (!false) { var envfun = menv; while (envfun) |ef| { const env = ef.env; if (env.level <= inst.l) { return; } try env.insts.append(inst); if (ef.fun) |_| { return; } envfun = ef.env.outer; } else { // _ = self; } } else { // very hacky FUCK { var menvfun: ?AST.EnvFun = menv; var insts = std.ArrayList(*AST.Match).init(self.arena); try insts.append(inst.m); while (menvfun) |envfun| { const env = envfun.env; for (insts.items) |m| { if (env.level > inst.l) { const envVar: AST.EnvVar = .{ .v = inst.v, .t = try self.typeContext.mapType(m, inst.t), .m = (try self.typeContext.mapMatch(m, inst.m)) orelse inst.m, .l = inst.l, }; try env.insts.append(envVar); } // else { // switch (inst.v) { // .Fun => |fun| { // // _ = self; // // _ = fun; // try self.expandFunctionEnvIntoCurrentEnv(env, fun.env, m); // }, // else => {}, // } // } } if (envfun.fun) |fun| { if (!fun.temp__finishedParsing) break; var newInsts = std.ArrayList(*AST.Match).init(self.arena); for (insts.items) |m| { for (fun.temp__calls.items) |call| { try newInsts.append((try self.typeContext.mapMatch(m, call.m)) orelse call.m); } } // insts.clearAndFree(); insts = newInsts; } menvfun = envfun.env.outer; } } // now add expansions (BRUHHHH) { var menvfun: ?AST.EnvFun = .{ .env = inst.v.Fun.env, .fun = inst.v.Fun }; var insts = std.ArrayList(*AST.Match).init(self.arena); try insts.append(inst.m); while (menvfun) |envfun| { // if (Common.streq(inst.getVar().name, "do-sth")) { // std.debug.print("cock\n", .{}); // } const env = envfun.env; for (insts.items) |m| { switch (inst.v) { .Fun => |fun| { // _ = self; // _ = fun; // if (Common.streq(inst.getVar().name, "do-sth")) { // var hadNewline = false; // const c = AST.Ctx.init(&hadNewline, self.typeContext); // c.print(.{ inst.getVar(), ":: ", if (envfun.fun) |efun| efun.name else AST.Var{ .name = "<>", .uid = 0 }, env, " <- ", fun.name, fun.env, "\n" }); // } try self.expandFunctionEnvIntoCurrentEnv(env, fun.env, m); }, else => {}, } } if (envfun.fun) |fun| { if (!fun.temp__finishedParsing) break; var newInsts = std.ArrayList(*AST.Match).init(self.arena); for (insts.items) |m| { if (fun != inst.v.Fun) { for (fun.temp__calls.items) |call| { try newInsts.append((try self.typeContext.mapMatch(m, call.m)) orelse call.m); } } else { try newInsts.append((try self.typeContext.mapMatch(m, inst.m)) orelse inst.m); } } // insts.clearAndFree(); insts = newInsts; } menvfun = envfun.env.outer; } } } } fn instantiateClassFunction(self: *Self, cfun: *const AST.ClassFun, l: Loc) !struct { ref: AST.InstFunInst, t: AST.Type, m: *AST.Match, } { const match = try self.instantiateScheme(cfun.scheme, null, l); // mk new, instantiated type var params = std.ArrayList(AST.Type).init(self.arena); for (cfun.params) |p| { try params.append(try self.typeContext.mapType(match, p.t)); } const ret = try self.typeContext.mapType(match, cfun.ret); const funTy = try self.typeContext.newType(.{ .Fun = .{ .args = params.items, .ret = ret, .env = try self.typeContext.newEnv(null), } }); const classSelf = try self.typeContext.mapType(match, cfun.self); const instances = try self.getInstances(); const ref: *?AST.Match.AssocRef = try Common.allocOne(self.arena, @as(?AST.Match.AssocRef, null)); try self.addAssociation(.{ .from = classSelf, .class = cfun.class, .instances = instances, .loc = l, .default = cfun.class.default, .concrete = .{ .classFun = cfun, .ref = ref, .to = funTy, .env = self.env, .envType = .ClassFunInstantiation, .match = match, }, }); // NOTE: everywhere this function was called we want to add the "USE" const use = AST.Function.Use{ .ClassFun = .{ .cfun = cfun, .ref = ref, .t = funTy, }, }; // add uses if (AST.EnvFun.getFun(self.env)) |fun| { try fun.temp__mono.uses.append(use); } else { try self.topLevels.append(use); } return .{ .ref = ref, .t = funTy, .m = match, }; } fn instantiateFunction(self: *Self, fun: *AST.Function, instances: ?Module.ClassInstance, l: ?Loc) !AST.Function.Instantiation { const match = try self.instantiateScheme(fun.scheme, instances, l); // mk normal, uninstantiated type. var params = std.ArrayList(AST.Type).init(self.arena); for (fun.params) |p| { try params.append(p.d.t); } const funTy = try self.typeContext.newType(.{ .Fun = .{ .args = params.items, .ret = fun.ret, .env = try self.typeContext.newEnv(.{ .env = fun.env, .match = match, .fun = fun, .level = fun.env.level, }), // this is sussy. maybe we should also keep the "newEnv" still. NOTE(02.10.25): ???? }, }); // OLD, left for reference // add stuff to env (note: when generalizing, we can split stuff that needs to be "readded", because it contains tvars.) // for (fun.env.insts.items) |unmappedEnvVar| { // const envVar: AST.EnvVar = .{ // .v = unmappedEnvVar.v, // .t = try self.typeContext.mapType(match, unmappedEnvVar.t), // .m = if (try self.typeContext.mapMatch(match, unmappedEnvVar.m)) |m| m else unmappedEnvVar.m, // .l = unmappedEnvVar.l, // }; // try self.addToEnvIfPossible(fun.env.outer, envVar); // } const funInst = AST.Function.Instantiation{ .t = try self.typeContext.mapType(match, funTy), .m = match }; if (!fun.temp__isRecursive) { try fun.temp__calls.append(funInst); } return funInst; } // CURRENTLY VERY SLOW! fn getInstances(self: *Self) !Module.ClassInstance { // OPTIMIZATION POSSIBILITY: Instance declarations happen often in sequence, then are used. Right now, the list is copied each time. Instead, we can make instances copied on demand. // 1. lots of instance declarations, then class: // copy hashmap and insert into associations and assign to function class // 2. lots of function class calls, then instance // copy hashmap and then insert into associations and add instance // basically, when unchanged, pass the current one and create a copy when it needs to be changed. // ALSO ITS BAD YO BECAUSE WE HAVE TO COPY EVERYTHING!!!! AAAAAAAAAA // BECAUSE AN INSTANCE MIGHT HAVE OTHER "REQUIREMENTS" var foundInsts = Module.ClassInstance.init(self.arena); var scopeIt = self.scope.scopes.iterateFromTop(); while (scopeIt.nextPtr()) |sc| { var classIt = sc.instances.iterator(); while (classIt.next()) |classDataInsts| { const nuInstsEntry = try foundInsts.getOrPut(classDataInsts.key_ptr.*); if (!nuInstsEntry.found_existing) { nuInstsEntry.value_ptr.* = Module.DataInstance.init(self.arena); } const nuInsts = nuInstsEntry.value_ptr; const insts = classDataInsts.value_ptr; var instIt = insts.iterator(); while (instIt.next()) |inst| { if (nuInsts.getKey(inst.key_ptr.*) == null) { try nuInsts.put(inst.key_ptr.*, inst.value_ptr.*); } } } } return foundInsts; } // the function name bruh fn solveAvailableConstraintsAndApplyDefaultsIfPossible(self: *Self) !void { while (true) { try self.solveAvailableConstraints(); var appliedAnyDefaults = false; if (self.associations.items.len > 0) { // try solving defaults. for (self.associations.items) |assoc| { appliedAnyDefaults = appliedAnyDefaults or try self.maybeApplyDefault(&assoc); } } if (!appliedAnyDefaults) { break; } } } // TODO: this should not really be a function thooo fn maybeApplyDefault(self: *Self, assoc: *const Association) !bool { if (assoc.default) |def| { const data = try self.instantiateData(def, assoc.loc); try self.typeContext.unify(assoc.from, data.t, &.{ .l = assoc.loc.? }); return true; } return false; } fn solveAvailableConstraints(self: *Self) !void { var hadChanges = true; // true, because we need to enter the loop at least once. while (hadChanges) { hadChanges = false; // copy array, so that modifications won't affect it. const currentAssocs = try self.arena.alloc(Association, self.associations.items.len); defer self.arena.free(currentAssocs); // noop with arena. but reminds me of currentAssocs's lifetime. @memcpy(currentAssocs, self.associations.items); var i: usize = 0; // for future me: we are modifying i inside the loop, so we can't make a for(,) zip thing. for (currentAssocs) |assoc| { defer i +%= 1; switch (self.typeContext.getType(assoc.from)) { .Con => |con| { if (if (assoc.instances.getPtr(assoc.class)) |dataInstances| dataInstances.get(con.type) else null) |inst| errexit: { if (assoc.concrete) |conc| { const fun: *AST.Function = b: { for (inst.instFuns) |instFun| { if (instFun.classFunId == conc.classFun.uid) { const fun = instFun.fun; break :b fun; } } // TODO: compiler error: could not find instance function. // This case should be checked when parsing the selected instance. // Then here, we would return some placeholder (or the placeholder will be provided then) try self.reportError(.{ // so this is TEMP .MissingInstanceFunction = .{ .data = con.type, .classFun = conc.classFun, .loc = assoc.loc.?, }, }); conc.ref.* = null; break :errexit; }; const funTyAndMatch = try self.instantiateFunction(fun, assoc.instances, assoc.loc); const funTy = funTyAndMatch.t; try self.typeContext.unify(conc.to, funTy, if (assoc.loc) |l| &.{ .l = l } else null); conc.ref.* = .{ .InstFun = .{ .fun = fun, .m = funTyAndMatch.m, .locality = locality(conc.env, fun.env.level), .t = funTyAndMatch.t, } }; const envInst = AST.EnvVar{ .v = .{ .Fun = fun }, .t = funTyAndMatch.t, .m = funTyAndMatch.m, .l = fun.env.level - 1, // NOTE: this is funny! I think it's because normally, the level is not equal when instantiating variables, but envs have indented level so ????? just trust the science }; try self.addToEnvIfPossible( conc.env, envInst, true, ); // if top level, add inst // switch (conc.envType) { // .ClassFunInstantiation => { // if (AST.EnvFun.getFun(conc.env)) |afun| { // try afun.temp__uses.append(envInst); // } else { // try self.topLevels.append(envInst); // } // }, // .AssociatedInstantiation => { // // TODO? // // if (AST.EnvFun.getFun(conc.env)) |afun| { // // _ = afun; // // } // }, // } // if (fun.env.outer == null and conc.envType == .ClassFunInstantiation and conc.env == null) { // try self.topLevels.append(envInst); // } } else { // nothing. it's good. } } else { // error try self.reportError(.{ .CouldNotFindInstanceForType = .{ .data = con.type, .class = assoc.class, .possibilities = assoc.instances.getPtr(assoc.class), .loc = assoc.loc.?, // TODO: is this safe? }, }); if (assoc.concrete) |*conc| { conc.ref.* = null; } } _ = self.associations.orderedRemove(i); // TODO: not very efficient with normal ArrayList. i -%= 1; // make sure to adjust index. hadChanges = true; }, .Anon => unreachable, // ??? i dunno .Fun => unreachable, // error! // BUG?(tvar-in-solving-constraints) .TVar => |tv| { // what should i do here? // const targetClass = assoc.classFun.class; // // for (tv.classes.items) |class| { // // if (targetClass == class) break; // // } else { // // } // if (currentFunction) |funId| { // if (std.meta.eql(tv.binding, AST.TVar.Binding{ .Function = funId })) { // continue; // } // } _ = tv; // unreachable; // should this happen? // assoc.ref.* = null; // _ = self.associations.orderedRemove(i); // try self.reportError(.{ .TVarDoesNotImplementClass = .{ .tv = tv, .class = targetClass } }); // NOTE: don't assign to hadChanges, because this doesn't impact anything. }, .TyVar => {}, } } } } // TYPES // (requires US to generate a new unique.) fn newData(self: *@This(), data: *AST.Data) !void { // add type try self.scope.currentScope().types.put(data.name, .{ .thing = .{ .Data = data } }); // add constructors switch (data.stuff) { .cons => |cons| { for (cons) |*con| { try self.scope.currentScope().cons.put(con.name, .{ .thing = con }); } }, .recs => { // do nothing :) }, } } fn newClass(self: *Self, class: *AST.Class) !void { try self.scope.currentScope().types.put( class.name, .{ .thing = .{ .Class = class } }, ); for (class.classFuns) |classFun| { try self.scope.currentScope().vars.put(classFun.name.name, .{ .thing = .{ .ClassFun = classFun } }); } } fn newTypeSynonym(self: *Self, name: Str, synonym: *AST.TypeSynonym) !void { try self.scope.currentScope().types.put(name, .{ .thing = .{ .Synonym = synonym } }); } const DataInst = struct { t: AST.Type, tyArgs: []AST.TypeOrNum, match: *AST.Match, }; fn instantiateData(self: *Self, data: *const AST.Data, l: ?Loc) !DataInst { const match = try self.instantiateScheme(data.scheme, null, l); const outerTVars = try self.arena.alloc(AST.TypeOrNum, data.outerTVars.len); for (data.outerTVars, 0..) |otv, i| { switch (otv) { .TVar => |tv| outerTVars[i] = .{ .Type = try self.typeContext.newType(.{ .TVar = tv }) }, .TNum => |tnum| outerTVars[i] = .{ .Num = try self.typeContext.newNum(.{ .TNum = tnum }) }, } } return .{ .t = try self.typeContext.newType(.{ .Con = .{ .type = data, .application = match, .outerApplication = outerTVars, } }), .tyArgs = match.tvars, .match = match, }; } const DataOrClassShit = struct { t: AST.Type, tyArgs: []AST.TypeOrNum, // it's a class here bruv: type: union(enum) { Data: struct { data: *AST.Data, match: *AST.Match, }, Class, Synonym, }, }; fn newPlaceholderType(self: *Self, typename: Str, location: Common.Location) !DataOrClassShit { const placeholderType = try Common.allocOne(self.arena, AST.Data{ .name = typename, .uid = self.gen.vars.newUnique(), .stuff = .{ .cons = &.{} }, .scheme = AST.Scheme.empty(), .outerTVars = &.{}, .annotations = &.{}, }); try self.reportError(.{ .UndefinedType = .{ .typename = typename, .loc = location }, }); const match = try Common.allocOne(self.arena, AST.Match.empty(placeholderType.scheme)); return .{ .t = try self.typeContext.newType(.{ .Con = .{ .type = placeholderType, .application = match, .outerApplication = &.{}, } }), .tyArgs = &.{}, .type = .{ .Data = .{ .data = placeholderType, .match = match, } }, }; } fn maybeLookupType(self: *Self, typename: Str) ?Module.DataOrClass { var lastScopes = self.scope.scopes.iterateFromTop(); while (lastScopes.next()) |cursc| { if (cursc.types.get(typename)) |t| { return t.thing; } } else { return null; } } // TVar fn newTVar(self: *@This(), tvname: Str, binding: ?AST.Binding) !AST.TVar { const tv: AST.TVar = .{ .uid = self.gen.tvars.newUnique(), .name = tvname, .binding = binding, .inferred = false, .fields = &.{}, .fieldsTotal = false, }; try self.scope.currentScope().tvars.put(tvname, .{ .TVar = tv }); return tv; } // basically, sometimes when we look up tvars in declarations, we want to define them. slightly hacky, but makes stuff easier. fn lookupTVar(self: *Self, tvTok: Token, binding: ?AST.Binding) !AST.TVar { const tvname = tvTok.literal(self.lexer.source); var lastScopes = self.scope.scopes.iterateFromTop(); while (lastScopes.next()) |cursc| { if (cursc.tvars.get(tvname)) |tvOrNum| { switch (tvOrNum) { .TVar => |tv| { return tv; }, .TNum => { unreachable; // TODO: error }, } } } else { // create a new var then. if (binding == null) { try self.reportError(.{ .UndefinedTVar = .{ .tvname = tvname, .loc = self.loc(tvTok), } }); } return try self.newTVar(tvTok.literal(self.lexer.source), binding); } } // Num TVar fn newTNum(self: *Self, name: Str, binding: ?AST.Binding) !AST.TNum { const tnum = AST.TNum{ .uid = self.gen.vars.newUnique(), .name = name, .binding = binding, }; try self.scope.currentScope().tvars.put(name, .{ .TNum = tnum }); try self.scope.currentScope().vars.put(name, .{ .thing = .{ .TNum = tnum } }); return tnum; } fn lookupTNum(self: *Self, tnumTok: Token, binding: ?AST.Binding) !AST.TNum { var tvname = tnumTok.literal(self.lexer.source); if (tvname[0] == '^') tvname = tvname[1..]; var lastScopes = self.scope.scopes.iterateFromTop(); while (lastScopes.next()) |cursc| { if (cursc.tvars.get(tvname)) |tvOrNum| { switch (tvOrNum) { .TNum => |tv| { return tv; }, .TVar => { unreachable; // TODO: error }, } } } else { // create a new var then. if (binding == null) { try self.reportError(.{ .UndefinedTNum = .{ .tvname = tvname, .loc = self.loc(tnumTok), } }); } return try self.newTNum(tvname, binding); } } // CNS fn newCon(self: *@This(), con: *AST.Con) !void { try self.scope.currentScope().cons.put(con.name, con); } fn instantiateCon(self: *@This(), modpath: Module.Path, conTok: Token) !struct { con: *AST.Con, t: AST.Type, tys: []AST.Type, } { const conName = conTok.literal(self.lexer.source); const con = if (modpath.len == 0) b: { var lastVars = self.scope.scopes.iterateFromTop(); while (lastVars.next()) |cursc| { if (cursc.cons.get(conName)) |con| break :b con.thing; } else { const data = try self.arena.create(AST.Data); data.uid = self.gen.types.newUnique(); data.name = conName; data.stuff = .{ .cons = try self.arena.alloc(AST.Con, 1) }; data.stuff.cons[0] = .{ .uid = self.gen.cons.newUnique(), .name = conName, .tys = &.{}, .data = data, .tagValue = 0, .anns = &.{}, }; data.scheme = AST.Scheme.empty(); try self.reportError(.{ .UndefinedCon = .{ .conname = conName, .loc = self.loc(conTok), }, }); // return placeholder var after an error. return .{ .con = &data.stuff.cons[0], .t = try self.typeContext.fresh(), .tys = &.{} }; } } else b: { if (try self.loadModuleFromPath(modpath, self.loc(conTok))) |mod| { // TODO(errors): incorrect location, we need to pipe the location of modPath here. if (mod.lookupCon(conName)) |con| { break :b con; } else { unreachable; // TODO ERROR: could not find constructor. } } else { unreachable; // this might be a circular dependency? } }; const dt = try self.instantiateData(con.data, self.loc(conTok)); // found con. now we instantiate it. if (con.tys.len == 0) { return .{ .con = con, .t = dt.t, .tys = &.{} }; } else { // NOTE: function type making moved to .Con case in expression() var args = std.ArrayList(AST.Type).init(self.arena); for (con.tys) |ty| { try args.append(try self.typeContext.mapType(dt.match, ty)); } return .{ .con = con, .t = dt.t, .tys = args.items, }; } } // SCHEMES fn instantiateScheme(self: *Self, scheme: AST.Scheme, minstances: ?Module.ClassInstance, l: ?Loc) !*AST.Match { const tvarMatch = try self.arena.create(AST.Match); const tvars = try self.arena.alloc(AST.TypeOrNum, scheme.tvars.len); for (scheme.tvars, 0..) |tvOrNum, i| { tvars[i] = switch (tvOrNum) { .TVar => .{ .Type = try self.typeContext.fresh() }, .TNum => .{ .Num = try self.typeContext.newNum(.Unknown) }, }; } const envVars = try self.arena.alloc(AST.UnionRef, scheme.envVars.len); for (scheme.envVars, 0..) |_, i| { // NOTE: we don't care for now - at the end we're gonna unify these environments. // const envb = self.typeContext.getEnv(er); // if (envb.env) |e| { // envVars[i] = try self.typeContext.newEnv(.{ // .env = e.env, // .fun = e.fun, // .match = if (self.level() < e.env.level) try Common.allocOne(self.arena, TypeContext.MatchLink{ // .match = tvarMatch, // .next = e.match, // }) else e.match, // }); envVars[i] = try self.typeContext.newEnv(null); } const assocsStuff = try self.arena.alloc(?AST.Match.AssocRef, scheme.associations.len); const assocs = try self.arena.alloc(*?AST.Match.AssocRef, scheme.associations.len); for (0..assocs.len) |i| { assocsStuff[i] = null; assocs[i] = &assocsStuff[i]; } tvarMatch.* = AST.Match{ .tvars = tvars, .envVars = envVars, .assocs = assocs, .scheme = scheme, }; if (scheme.associations.len > 0) { // NOTE: it's nullable, because always instantiating instances when it's an argument was SUPER SLOW! // later, it's gonna be cached AND THEN it's also going to be a pointer, so passing it as an argument won't be a problem const instances = minstances orelse try self.getInstances(); // should prolly add assocs to "Match", but we don't need em yet. for (scheme.associations, assocs) |assoc, ref| { try self.addAssociation(.{ .from = try self.typeContext.mapType(tvarMatch, try self.typeContext.newType( .{ .TVar = assoc.depends }, )), .instances = instances, .loc = l, .class = assoc.class, .default = assoc.default, .concrete = if (assoc.concrete) |conc| .{ .to = try self.typeContext.mapType(tvarMatch, conc.to), .classFun = conc.classFun, .ref = ref, .env = conc.env, .envType = .AssociatedInstantiation, .match = try self.typeContext.mapMatch(tvarMatch, conc.match), } else null, }); } } // now members fields for (scheme.tvars, tvars) |tvOrNum, tyv| { switch (tvOrNum) { .TVar => |tv| { if (!tv.fieldsTotal) { for (tv.fields) |field| { const fieldTy = try self.typeContext.field(tyv.Type, field.field, null); try self.typeContext.unify(fieldTy, try self.typeContext.mapType(tvarMatch, field.t), null); } } else { const mappedFields = try self.arena.alloc(AST.Record, tv.fields.len); for (mappedFields, tv.fields) |*mfield, field| { mfield.* = .{ .field = field.field, .t = try self.typeContext.mapType(tvarMatch, field.t) }; } const anonTy = try self.typeContext.newAnon(mappedFields); try self.typeContext.unify(tyv.Type, anonTy, null); } }, .TNum => { // no fields to instantiate }, } } // now environment shit for (scheme.envVars, envVars) |sse, me| { try self.typeContext.unifyUnion(me, try self.typeContext.cloneMapUnion(tvarMatch, sse), null, undefined); } return tvarMatch; } fn mkSchemeForFunction(self: *Self, alreadyDefinedTVars: *const std.StringHashMap(AST.TVarOrNum), params: []AST.DeconBase, ret: AST.Type, env: *AST.Env, functionId: Unique, constraints_: *const Constraints) !AST.Scheme { const expectedBinding = AST.Binding{ .Function = functionId, }; // Function local stuff. var funftvs = TypeContext.FTVs.init(self.arena, self.typeContext); try self.typeContext.ftvs(&funftvs, ret); for (params) |p| { try self.typeContext.ftvs(&funftvs, p.d.t); } // environment stuff. var envftvs = TypeContext.FTVs.init(self.arena, self.typeContext); try self.typeContext.ftvsFromEnv(&envftvs, env); // TEMP: detect free type variables and apply defaults in case they are not from outside. // NOTE(invalidate-entries): we can invalidate entries. while (true) { var defaultsApplied = false; for (self.associations.items) |assoc| { const from = self.typeContext.getType(assoc.from); switch (from) { .TyVar => {}, else => continue, // NOTE: for some reason, it's possible for a non-tyvar to appear here (I guess after applying a default?) If so, continue and let this constraint be solved later. } if (assoc.default != null and !funftvs.tyvars.contains(.{ .t = assoc.from, .tyv = from.TyVar }) and !envftvs.contains(assoc.from, from.TyVar)) { defaultsApplied = defaultsApplied or try self.maybeApplyDefault(&assoc); } } if (!defaultsApplied) { break; } // TODO: THIS SHOULD NOT BE HERE. BAD DESIGN (need better free variables detection) try self.solveAvailableConstraints(); } // now, remove the tyvars from env here. funftvs.difference(&envftvs); // make tvars out of them // TODO: assign pretty names ('a, 'b, etc.). var tvars = std.ArrayList(AST.TVarOrNum).init(self.arena); // add defined tvars in this function. var tvit = alreadyDefinedTVars.valueIterator(); while (tvit.next()) |tvar| { try tvars.append(tvar.*); } var it = funftvs.tyvars.iterator(); while (it.next()) |e| { // NOTE(invalidate-entries): filter in case it was invalidated. switch (self.typeContext.getType(e.t)) { .TyVar => {}, else => continue, } const name = try std.fmt.allocPrint(self.arena, "'{}", .{e.tyv.uid}); const fields = self.typeContext.getFieldsForTVar(e.tyv); const tv = AST.TVar{ .name = name, .uid = self.gen.tvars.newUnique(), .binding = expectedBinding, .inferred = true, .fields = if (fields) |tyvs| tyvs.fields else &.{}, .fieldsTotal = if (fields) |tyvs| tyvs.total else false, }; try tvars.append(.{ .TVar = tv }); const tvt = try self.typeContext.newType(.{ .TVar = tv }); try self.typeContext.unify(e.t, tvt, null); } var numIt = funftvs.nums.iterator(); while (numIt.next()) |freenum| { const name = try std.fmt.allocPrint(self.arena, "'{}", .{freenum.id}); const tnum = AST.TNum{ .name = name, .binding = expectedBinding, .uid = self.gen.vars.newUnique(), }; const tnumref = try self.typeContext.newNum(.{ .TNum = tnum }); try self.typeContext.unifyNum(freenum.*, tnumref, null, undefined); try tvars.append(.{ .TNum = tnum }); } var envs = std.ArrayList(AST.UnionRef).init(self.arena); var envIt = funftvs.envs.iterator(); while (envIt.next()) |e| { try envs.append(e.*); } // also, make sure to gather assocs var assocs = std.ArrayList(AST.Association).init(self.arena); var assocsChanged = true; while (assocsChanged) { assocsChanged = false; const currentAssocs = try self.arena.alloc(Association, self.associations.items.len); defer self.arena.free(currentAssocs); // noop with arena. but reminds me of currentAssocs's lifetime. @memcpy(currentAssocs, self.associations.items); var i: usize = 0; for (currentAssocs) |assoc| { defer i +%= 1; switch (self.typeContext.getType(assoc.from)) { .TVar => |assocTV| { // TODO: associate tvars with places they are declared. this can be a tvar of an outside function. if (!std.meta.eql(assocTV.binding, expectedBinding)) continue; if (!assocTV.inferred) b: { const assocClass = assoc.class; if (constraints_.get(assocTV)) |constrs| { for (constrs.items) |class| { if (class.uid == assocClass.uid) { // OKAY! break :b; } } } // here, it's "bruh" try self.reportError(.{ .TVarDoesNotImplementClass = .{ .class = assocClass, .tv = assocTV } }); } const assocID = self.gen.assocs.newUnique(); if (assoc.concrete) |conc| { // make sure to check it's actually bound to a function. var assocFTVs = TypeContext.FTVs.init(self.arena, self.typeContext); // TODO: this is kinda fugly. I should reuse the general ftvs. defer assocFTVs.deinit(); try self.typeContext.ftvs(&assocFTVs, conc.to); var assocFTVIt = assocFTVs.tyvars.iterator(); while (assocFTVIt.next()) |tyv| { const name = try std.fmt.allocPrint(self.arena, "'{}", .{tyv.tyv.uid}); const fields = self.typeContext.getFieldsForTVar(tyv.tyv); const tv = AST.TVar{ .name = name, .uid = self.gen.tvars.newUnique(), .binding = .{ .Function = functionId }, .inferred = true, .fields = if (fields) |tyvs| tyvs.fields else &.{}, .fieldsTotal = if (fields) |tyvs| tyvs.total else false, }; try tvars.append(.{ .TVar = tv }); const tvt = try self.typeContext.newType(.{ .TVar = tv }); try self.typeContext.unify(tyv.t, tvt, null); } var assocEnvIt = assocFTVs.envs.iterator(); while (assocEnvIt.next()) |e| { try envs.append(e.*); } // here we are adding an existing association to a scheme. // remember to create a uid and pointer-write it to the previous match's association. conc.ref.* = .{ .Id = assocID }; try assocs.append(.{ .depends = assocTV, .class = assoc.class, .uid = assocID, .default = assoc.default, .concrete = .{ .classFun = conc.classFun, .to = conc.to, .ref = conc.ref, .env = switch (conc.envType) { .ClassFunInstantiation => env.outer, .AssociatedInstantiation => env.outer, // TODO: which? }, .match = conc.match, }, }); try self.addToEnvIfPossible(conc.env, .{ .v = .{ .ClassFun = .{ .cfun = conc.classFun, .ref = conc.ref, }, }, .t = conc.to, .m = conc.match, .l = env.level, }, false); // NOTE: I think this adds pointless constraints (breaks 1_t25 test) // I'm thinking if this is not actually needed, because those things are THE SAME as the match's assocs. // try conc.env.?.insts.append(.{ // .v = .{ // .ClassFun = .{ // .cfun = conc.classFun, // .ref = conc.ref, // }, // }, // .t = conc.to, // .m = conc.match, // }); } else { try assocs.append(.{ .depends = assocTV, .class = assoc.class, .uid = assocID, .default = assoc.default, .concrete = null, }); } // also, make sure to later add tvars to them _ = self.associations.orderedRemove(i); i -%= 1; assocsChanged = true; }, .Anon => unreachable, // ??? .TyVar => {}, .Con => unreachable, // should be handled earlier .Fun => unreachable, // -//- } } } // also add defined constraints! (but it's all bad thoooo) try self.addConstraintsToAssocs(&assocs, constraints_); // NOTE(env-escaping) // at the end, fill the environments, yah. // when you return an inner environment, this maps any tvars n shii. // TODO: when you call an external function with an inner environment, it should also be mapped. // where will these additional environments appear? in the environment's Matches - both in the Env and the Type. for (envs.items) |se| { const ref = self.typeContext.getUnion(se).env; for (ref.envs.items) |*ftvenv| { if (env.level < ftvenv.level) { const newMatch = b: { // NOTE about this whole thing: currently, we're most likely not handling variables correctly. If we assign the env to an outer variable, and then try to return that variable, it's gonna be real funny // (function schemes won't be properly assigned and when we return it, a scheme that will get assigned will miss all the schemes in between the env and the returning function). // should we also check if the env vars contain inner envs for that? if (ftvenv.env.monoLastScheme) |lastScheme| { const outerLevel = lastScheme.lastEnv.level; std.debug.assert(outerLevel >= env.level); if (outerLevel == env.level) { // NOTE/TODO: technically, this is pretty slow. We make a "blank" match, then map what we know. // It's supposed to yield the same result as match.joinScheme() after we create this env's scheme. // Except, I'm sure the first part of the Scheme (0..match.X.len) must be mapped, and the other one must be "blank". const blankMatch = try AST.Match.blankMatch(&lastScheme.scheme, self.typeContext, self.arena); const normalMatch = try self.typeContext.mapMatch(ftvenv.match, blankMatch); break :b normalMatch; } else { std.debug.assert(outerLevel > env.level); std.debug.assert(ftvenv.level == lastScheme.lastEnv.level); // fallthrough to default case. } } { // get those tvars. var tvarStore = TypeContext.AllStore.init(self.arena, self.typeContext); try self.typeContext.getTVarsFromEnv(expectedBinding, &tvarStore, ftvenv.env, env.level); const scheme = try tvarStore.toScheme(&funftvs.envs, assocs.items); const numatch = try ftvenv.match.joinScheme(&scheme, self.typeContext, self.arena); // joins a scheme to match with uninstantiated stuff from that scheme in the match. ftvenv.env.monoLastScheme = .{ .lastEnv = env, .scheme = scheme }; break :b numatch; } }; ftvenv.match = newMatch; ftvenv.level = env.level; } } } return .{ .tvars = tvars.items, .envVars = envs.items, .associations = assocs.items, }; } // ASSOCIATION // NOTE: kind of crappy constraint thing. It should work tho? pub const Association = struct { from: AST.Type, loc: ?Loc, class: *AST.Class, instances: Module.ClassInstance, default: ?*AST.Data, // for now, when generalizing, we DON'T keep defaults. I'm thinking of applying defaults before generalizing, so that returning strings works correctly. Or make a different default type: `late` and `eager` // when it's null, it's just a `constraint` and not based on a class function call. // when it's a value, it's an actual association with an associated function call. concrete: ?struct { classFun: *const AST.ClassFun, ref: *?AST.Match.AssocRef, to: AST.Type, // NOTE: These two are only used for class functions. // BUT, with the `ref` we technically don't have to add them to the environment, since it's in the SCHEME! env: ?AST.EnvFun, // fyi: this is self.env in class function instantiation and fun.env.outer when it's AssociatedInstantiation (when generalized) // when a class function instantiation's type is known imm. (never generalized), we want to add the class function to the enclosing environment (like a normal function call) // when it gets generalized, however, we don't want to add it to the environment that got it generalized - only the one "outer" to it. envType: enum { ClassFunInstantiation, AssociatedInstantiation, }, match: *const AST.Match, // this is funny - not sure if it should be here, as it is only used for the class match (because we are adding it later.) }, }; fn addAssociation(self: *Self, assoc: Association) !void { try self.associations.append(assoc); } fn addInstance(self: *Self, scope: *CurrentScope, instance: *AST.Instance) !void { const getOrPutResult = try scope.instances.getOrPut(instance.class); if (!getOrPutResult.found_existing) { getOrPutResult.value_ptr.* = Module.DataInstance.init(self.arena); } const dataInsts = getOrPutResult.value_ptr; try dataInsts.put(instance.data, instance); } fn beginEnv(self: *Self, fun: ?*AST.Function) !*AST.Env { self.beginScope(); // x -> x + 1 const nuEnv = try Common.allocOne(self.arena, AST.Env{ .id = self.gen.envs.newUnique(), .insts = std.ArrayList(AST.EnvVar).init(self.arena), .level = self.level(), // x + 1 ;; number of scopes -> level .outer = self.env, .monoInsts = AST.Env.Mono.initContext(self.arena, .{ .typeContext = self.typeContext, }), }); self.env = .{ .env = nuEnv, .fun = fun }; return nuEnv; } fn level(self: *const @This()) usize { return self.scope.scopes.current; } fn beginScope(self: *@This()) void { self.scope.scopes.push(CurrentScope.init(self.arena)); } fn endScope(self: *Self) void { _ = self.scope.scopes.pop(); if (AST.EnvFun.getEnv(self.env)) |env| { if (env.level > self.level()) { self.env = env.outer; } // make sure that the next env is AT LEAST on the level of the current scope (otherwise it wouldn't make sense.) if (AST.EnvFun.getEnv(self.env)) |eenv| { std.debug.assert(eenv.level <= self.level()); } } } const Scope = struct { al: std.mem.Allocator, // TODO: instead of this stack, we should just use the program stack! // But data locality is then scuffed...? scopes: stack.Fixed(CurrentScope, Common.MaxIndent), pub fn init(al: std.mem.Allocator) @This() { const Scopes = stack.Fixed(CurrentScope, Common.MaxIndent); var scopes = Scopes.init(); const defaultScope = CurrentScope.init(al); scopes.push(defaultScope); return .{ .al = al, .scopes = scopes, }; } pub fn currentScope(self: *@This()) *CurrentScope { return self.scopes.topp(); } fn restoreScope(self: *@This(), scope: CurrentScope) void { self.scopes.push(scope); } // ENVS // pub fn beginEnv(self: *@This()) []VarInst { // self.scopes.push(CurrentScope.init(self.al, CurrentScope.Env.init(self.al))); // } // pub fn endEnv(self: *@This()) void { // actually, return env. // const sc = self.scopes.pop(); // if (sc.env) |env| { // return env.items; // } else { // // this should not happen. begin/end scope must be perfectly matched. // unreachable; // } // } }; const CurrentScope = struct { vars: std.StringHashMap(Import(Module.VarOrFun)), types: std.StringHashMap(Import(Module.DataOrClass)), cons: std.StringHashMap(Import(*AST.Con)), tvars: std.StringHashMap(AST.TVarOrNum), instances: std.AutoHashMap(*AST.Class, Module.DataInstance), fn Import(t: type) type { return struct { thing: t, fromWhere: enum { DefinedLocally, Imported } = .DefinedLocally, }; } fn init(al: std.mem.Allocator) @This() { return .{ .vars = std.StringHashMap(Import(Module.VarOrFun)).init(al), .types = std.StringHashMap(Import(Module.DataOrClass)).init(al), .cons = std.StringHashMap(Import(*AST.Con)).init(al), .tvars = std.StringHashMap(AST.TVarOrNum).init(al), .instances = std.AutoHashMap(*AST.Class, Module.DataInstance).init(al), }; } // in the future - scopes are actually safe to deallocate. fn deinit() void {} }; // typechecking zone // TEST ZIG'S BIG BEAN BURRITO. fn makeType(self: *Self, t: anytype) !AST.Type { if (@hasField(@TypeOf(t), "Fun")) { const fun = @field(t, "Fun"); if (@hasField(@TypeOf(fun), "env")) @compileError("todo"); const ret = @field(fun, "ret"); const args = @field(fun, "args"); const params = try self.arena.alloc(AST.Type, args.len); for (args, 0..) |a, i| { params[i] = a; } return try self.typeContext.newType(.{ .Fun = .{ .args = params, .ret = ret, .env = try self.typeContext.newEnv(null), } }); } else { @compileError("trying to make unknown type brub"); } } fn getReturnType(self: *Self) !AST.Type { return self.returnType orelse try self.definedType(.I8); } fn ptrTo(self: *Self, ty: AST.Type) !AST.Type { const ptr = try self.defined(.Ptr); try self.typeContext.unify(ptr.dataInst.tyArgs[0].Type, ty, null); return ptr.dataInst.t; } fn definedType(self: *Self, predefinedType: Prelude.PremadeType) !AST.Type { return (try self.defined(predefinedType)).dataInst.t; } fn defined(self: *Self, predefinedType: Prelude.PremadeType) !struct { dataInst: DataInst, data: *const AST.Data, } { return if (self.prelude) |prelude| { const data = prelude.defined(predefinedType); return .{ .dataInst = try self.instantiateData(data, null), // no location, because it should not happen? .data = data, }; } else b: { const data = switch (self.maybeLookupType(Prelude.PremadeTypeName.get(predefinedType)) orelse break :b error.PreludeError) { .Data => |data| data, .Class => |_| break :b error.PreludeError, .Synonym => break :b error.PreludeError, }; return .{ .dataInst = try self.instantiateData(data, null), // -||- .data = data, }; }; } fn definedClass(self: *Self, predefinedType: Prelude.PremadeClass) !*AST.Class { return if (self.prelude) |prelude| { return prelude.definedClass(predefinedType); } else b: { return switch (self.maybeLookupType(Prelude.PremadeClassName.get(predefinedType)) orelse break :b error.PreludeError) { .Data => |_| error.PreludeError, .Class => |c| c, .Synonym => error.PreludeError, }; }; } // parser zone fn foldFromHere(self: *Self) ParsingMode { const old = self.mode; self.mode = .{ .Simple = .{ .CountIndent = .{ .indent = 0, } } }; return old; } fn finishFold(self: *Self, mode: ParsingMode) !void { switch (self.mode) { .Simple => |*nmode| switch (nmode.*) { .Normal => unreachable, .CountIndent => |i| { if (i.indent == 1) { try self.devour(.DEDENT); // maybe make not consuming it `unreachable`? since this might not even be possible. } else if (i.indent == 0 and !i.hadMultiline) { try self.endStmt(); } }, }, else => unreachable, } self.mode = mode; } fn expect(self: *Self, tt: TokenType) !Token { return self.consume(tt) orelse { try self.parseError(.{ .UnexpectedToken = .{ .got = self.currentToken, .expected = tt, } }); }; } fn expectOneOf(self: *Self, comptime tts: []const TokenType) !Token { for (tts) |tt| { if (self.consume(tt)) |tok| { return tok; } } try self.parseError(.{ .UnexpectedTokens = .{ .got = self.currentToken, .expected = tts, } }); } fn devour(self: *Self, tt: TokenType) !void { _ = try self.expect(tt); } fn check(self: *Self, tt: TokenType) bool { return self.consume(tt) != null; } // PARSING PRIMITIVES fn peek(self: *Self) Token { switch (self.mode) { .Simple => |*nmode| switch (nmode.*) { .Normal => {}, .CountIndent => |*ind| while (true) { if (!self.currentToken.isWhitespace()) break; if (self.currentToken.type == .STMT_SEP and ind.indent == 0) break; if (self.currentToken.type == .INDENT) ind.indent += 1; if (self.currentToken.type == .DEDENT) { if (ind.indent <= 1) break; ind.dedent(); } self.skip(); }, }, .Multiline => {}, } return self.currentToken; } fn consume(self: *Self, tt: TokenType) ?Token { var tok = self.currentToken; switch (self.mode) { .Simple => |*nmode| switch (nmode.*) { .Normal => {}, .CountIndent => |*ind| while (tok.isWhitespace()) { // TODO: COPYPASTA! if (tok.type == .STMT_SEP and ind.indent == 0) break; if (tok.type == .INDENT) ind.indent += 1; if (tok.type == .DEDENT) { if (ind.indent <= 1) break; ind.dedent(); } self.skip(); tok = self.currentToken; }, }, .Multiline => {}, } if (tok.type == tt) { self.skip(); return tok; } else { return null; } } fn consumeInteger(self: *Self) ?Token { const tok = self.peek(); return switch (tok.type) { .INTEGER, .HEX_INTEGER, .OCTAL_INTEGER => b: { self.skip(); break :b tok; }, else => null, }; } fn expectInteger(self: *Self) !Token { return self.consumeInteger() orelse { // dont use the generic error thing. try self.parseError(.{ .UnexpectedTokens = .{ .got = self.currentToken, .expected = &comptime [_]TokenType{ .INTEGER, .HEX_INTEGER, .OCTAL_INTEGER }, } }); }; } // IMPORTANT: DON'T MODIFY SKIP, SINCE AFTER PEEK/CONSUME THE MODE SHOULD BE CHANGED. LET PEEK / CONSUME CONSUME ALL THE WHITESPACE BEFOREHAND. // ALSO, THEY ALL DEPEND ON SKIP. fn skip(self: *Self) void { self.currentToken = self.lexer.nextToken(); } const ParsingMode = union(enum) { const Simple = union(enum) { Normal, CountIndent: struct { indent: u32, hadMultiline: bool = false, fn dedent(self: *@This()) void { self.indent -= 1; // self.hadMultiline = true; } }, }; Simple: Simple, Multiline: struct { prev: Simple, this: union(enum) { Lambda: struct { lamExpr: *AST.Expr, // TODO: fix iffy typing. scope: CurrentScope, env: *AST.Env, }, Case: struct { caseExpr: *AST.Expr, // TODO: fix iffy typing. }, }, }, // <- old parsing mode. we can't have two multi line lambdas on the same line. }; const LexingState = struct { lexer: Lexer, currentToken: Token, parsingMode: ParsingMode, }; // backtracking!! fn saveLexingState(self: *const Self) LexingState { return .{ .lexer = self.lexer, .currentToken = self.currentToken, .parsingMode = self.mode, }; } fn loadLexingState(self: *Self, state: LexingState) void { self.lexer = state.lexer; self.currentToken = state.currentToken; self.mode = state.parsingMode; } // NOTE: later, we don't have to specify a return value. Just always follow it with "return unreachable". fn errorExpect(self: *Self, exp: Str) !noreturn { try self.parseError(.{ .UnexpectedThing = .{ .at = self.loc(self.currentToken), .got = self.currentToken.type, .expected = exp, } }); } fn parseError(self: *Self, err: Error) !noreturn { try self.reportError(err); return error.ParseError; } // fn err(self: *Self, comptime t: type, comptime fmt: []const u8, args: anytype) !t { // std.debug.print(fmt ++ " at {} ({s})\n", args ++ .{ self.currentToken, self.name }); // std.debug.print("{s}\n", .{self.lexer.source[self.currentToken.from -% 5 .. @min(self.lexer.source.len, self.currentToken.to +% 5)]}); // return error.ParseError; // } // this might be in the tokenizer. fn sync_to_next_toplevel() void {} const ParseError = error{ParseError}; pub const ParserError = error{ ParseError, PreludeError, OutOfMemory, TempError, }; // full error set when it cannot be inferred. // TEMP const Fold = *u32; // assume integer is well formed because lexer guarantees it. const IntType = usize; fn parseInt(self: *const Self, tok: Token) IntType { return switch (tok.type) { .INTEGER => std.fmt.parseInt(IntType, tok.literal(self.lexer.source), 10) catch unreachable, .HEX_INTEGER => std.fmt.parseInt(IntType, tok.literal(self.lexer.source)[2..], 16) catch unreachable, .OCTAL_INTEGER => std.fmt.parseInt(IntType, tok.literal(self.lexer.source)[2..], 8) catch unreachable, else => unreachable, }; } fn parseFloat(self: *const Self, tok: Token) f64 { std.debug.assert(tok.type == .FRACTIONAL); return std.fmt.parseFloat(f64, tok.literal(self.lexer.source)) catch unreachable; } fn reportError(self: *const Self, ierr: Error) !void { try self.errors.append(.{ .err = ierr, .module = .{ .name = self.name, .source = self.lexer.source, } }); } fn LocdIn(t: type) type { return struct { l: Loc, e: t, }; } fn loc(self: *const Self, t: Token) Loc { return t.toLocation(self.lexer.source, self.name); }