From 89abeeba3d1d903a6d874f9bcef517f106b3b255 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Tue, 24 Sep 2024 00:46:21 -0700 Subject: [PATCH] [V-118] String support (#52) * Renames * Rename resolve types to resolve entities * Rename resolve types file * One more file rename * Semantics for FixedArray * Direct fixed array init * I think it works * Expose string module * Maybe working string literal * It works! * Bugfix * Change to namespace * Remove log * Add unit test --- src/__tests__/compiler.test.ts | 27 +++++++ src/__tests__/fixtures/e2e-file.ts | 11 ++- src/assembler.ts | 76 ++++++++++++++---- src/lib/binaryen-gc/index.ts | 34 +++++++- src/lib/host-runtime/bool-to-int.ts | 1 - src/lib/host-runtime/index.ts | 1 - src/lib/host-runtime/strings.ts | 77 ------------------- .../__snapshots__/parser.test.ts.snap | 2 +- src/parser/__tests__/fixtures/voyd-file.ts | 2 +- src/run.ts | 27 +------ src/semantics/check-types.ts | 33 +++++++- src/semantics/index.ts | 4 +- src/semantics/init-entities.ts | 41 +++++++--- src/semantics/resolution/combine-types.ts | 50 ++++++++++++ src/semantics/resolution/get-call-fn.ts | 8 +- src/semantics/resolution/get-expr-type.ts | 4 +- src/semantics/resolution/index.ts | 2 +- ...{resolve-call-types.ts => resolve-call.ts} | 56 ++++++++++---- .../{resolve-types.ts => resolve-entities.ts} | 64 +++++++-------- .../{resolve-fn-type.ts => resolve-fn.ts} | 12 +-- src/semantics/resolution/resolve-impl.ts | 8 +- .../resolution/resolve-intersection.ts | 10 ++- src/semantics/resolution/resolve-match.ts | 55 ++----------- .../resolution/resolve-object-type.ts | 11 +-- src/semantics/resolution/resolve-union.ts | 6 +- src/semantics/resolution/resolve-use.ts | 6 +- .../resolution/types-are-compatible.ts | 2 +- src/syntax-objects/syntax.ts | 6 +- src/syntax-objects/types.ts | 13 ++-- std/array.voyd | 26 ++++++- std/index.voyd | 3 +- std/operators.voyd | 18 +++++ std/string.voyd | 60 +++++++++++++++ std/strings.voyd | 36 --------- std/utils.voyd | 2 +- 35 files changed, 476 insertions(+), 318 deletions(-) delete mode 100644 src/lib/host-runtime/bool-to-int.ts delete mode 100644 src/lib/host-runtime/index.ts delete mode 100644 src/lib/host-runtime/strings.ts create mode 100644 src/semantics/resolution/combine-types.ts rename src/semantics/resolution/{resolve-call-types.ts => resolve-call.ts} (70%) rename src/semantics/resolution/{resolve-types.ts => resolve-entities.ts} (57%) rename src/semantics/resolution/{resolve-fn-type.ts => resolve-fn.ts} (91%) create mode 100644 std/string.voyd delete mode 100644 std/strings.voyd diff --git a/src/__tests__/compiler.test.ts b/src/__tests__/compiler.test.ts index 9e0fa76c..c966a98f 100644 --- a/src/__tests__/compiler.test.ts +++ b/src/__tests__/compiler.test.ts @@ -35,6 +35,15 @@ describe("E2E Compiler Pipeline", () => { expectedValues.forEach((v, i) => { const test = getWasmFn(`test${i + 1}`, instance); assert(test, `Test${i + 1} exists`); + + if (typeof v === "string") { + t.expect( + readString(test(), instance), + `test ${i + 1} returns correct value` + ).toEqual(v); + return; + } + t.expect(test(), `test ${i + 1} returns correct value`).toEqual(v); }); @@ -58,6 +67,7 @@ describe("E2E Compiler Pipeline", () => { 42, 2, // IntersectionType tests 20, // While loop + "Hello, world! This is a test.", ]); }); @@ -68,3 +78,20 @@ describe("E2E Compiler Pipeline", () => { t.expect(did); }); }); + +const readString = (ref: Object, instance: WebAssembly.Instance) => { + const newStringReader = getWasmFn("new_string_reader", instance)!; + const readNextChar = getWasmFn("read_next_char", instance)!; + const reader = newStringReader(ref); + + let str = ""; + while (true) { + const char = readNextChar(reader); + if (char < 0) { + break; + } + str += String.fromCharCode(char); + } + + return str; +}; diff --git a/src/__tests__/fixtures/e2e-file.ts b/src/__tests__/fixtures/e2e-file.ts index c1e3bffa..ca8c4050 100644 --- a/src/__tests__/fixtures/e2e-file.ts +++ b/src/__tests__/fixtures/e2e-file.ts @@ -13,7 +13,7 @@ pub fn main() `; export const kitchenSink = ` -use std::all +pub use std::string::all obj Vec { x: i32, @@ -87,15 +87,15 @@ pub fn test7() let vec = Bitly { x: 52, y: 2, z: 21 } get_num_from_vec_sub_obj(vec) -type DsArrayI32 = DsArray +type FixedArrayI32 = FixedArray // Test generic functions, should return 143 pub fn test8() - let arr2 = ds_array_init(10) + let arr2 = new_fixed_array(10) arr2.set(0, 1.5) arr2.get(0) - let arr: DsArrayI32 = ds_array_init(10) + let arr: FixedArrayI32 = new_fixed_array(10) arr.set(9, 143) arr.get(9) @@ -231,6 +231,9 @@ pub fn test19() -> i32 i = i + 1 if i == 5 then: break x + +pub fn test20() -> String + "Hello, world!" + " " + "This is a test." `; export const tcoText = ` diff --git a/src/assembler.ts b/src/assembler.ts index 501d4eb1..cea97907 100644 --- a/src/assembler.ts +++ b/src/assembler.ts @@ -8,7 +8,7 @@ import { Type, Primitive, ObjectType, - DsArrayType, + FixedArrayType, voydBaseObject, UnionType, IntersectionType, @@ -33,6 +33,7 @@ import { initExtensionHelpers } from "./assembler/extension-helpers.js"; import { returnCall } from "./assembler/return-call.js"; import { Float } from "./syntax-objects/float.js"; import { initFieldLookupHelpers } from "./assembler/field-lookup-helpers.js"; +import { List } from "./syntax-objects/list.js"; export const assemble = (ast: Expr) => { const mod = new binaryen.Module(); @@ -126,10 +127,20 @@ const compileType = (opts: CompileExprOpts) => { }; const compileModule = (opts: CompileExprOpts) => { - return opts.mod.block( + const result = opts.mod.block( opts.expr.id, opts.expr.value.map((expr) => compileExpression({ ...opts, expr })) ); + + if (opts.expr.isIndex) { + opts.expr.getAllExports().forEach((entity) => { + if (entity.isFn()) { + opts.mod.addFunctionExport(entity.id, entity.name.value); + } + }); + } + + return result; }; const compileBlock = (opts: CompileExprOpts) => { @@ -225,6 +236,7 @@ const compileCall = (opts: CompileExprOpts): number => { if (expr.calls("member-access")) return compileObjMemberAccess(opts); if (expr.calls("while")) return compileWhile(opts); if (expr.calls("break")) return mod.br(opts.loopBreakId!); + if (expr.calls("FixedArray")) return compileFixedArray(opts); if (expr.calls("binaryen")) { return compileBnrCall(opts); } @@ -251,6 +263,15 @@ const compileCall = (opts: CompileExprOpts): number => { return mod.call(id, args, returnType); }; +const compileFixedArray = (opts: CompileExprOpts) => { + const type = opts.expr.type as FixedArrayType; + return gc.arrayNewFixed( + opts.mod, + gc.binaryenTypeToHeapType(mapBinaryenType(opts, type)), + opts.expr.argArrayMap((expr) => compileExpression({ ...opts, expr })) + ); +}; + const compileWhile = (opts: CompileExprOpts) => { const { expr, mod } = opts; const loopId = expr.syntaxId.toString(); @@ -309,21 +330,21 @@ const compileObjectInit = (opts: CompileExprOpts) => { const compileExport = (opts: CompileExprOpts) => { const expr = opts.expr.exprArgAt(0); const result = compileExpression({ ...opts, expr }); - - if (expr.parentModule?.isIndex && expr.isBlock()) { - expr.getAllEntities().forEach((entity) => { - if (entity.isFn()) { - opts.mod.addFunctionExport(entity.id, entity.name.value); - } - }); - } - return result; }; const compileAssign = (opts: CompileExprOpts): number => { const { expr, mod } = opts; - const identifier = expr.argAt(0) as Identifier; + const identifier = expr.argAt(0); + + if (identifier?.isCall()) { + return compileFieldAssign(opts); + } + + if (!identifier?.isIdentifier()) { + throw new Error(`Invalid assignment target ${identifier}`); + } + const value = compileExpression({ ...opts, expr: expr.argAt(1)!, @@ -341,6 +362,32 @@ const compileAssign = (opts: CompileExprOpts): number => { throw new Error(`${identifier} cannot be re-assigned`); }; +const compileFieldAssign = (opts: CompileExprOpts) => { + const { expr, mod } = opts; + const access = expr.callArgAt(0); + const member = access.identifierArgAt(1); + const target = access.exprArgAt(0); + const value = compileExpression({ + ...opts, + expr: expr.argAt(1)!, + isReturnExpr: false, + }); + + const type = getExprType(target) as ObjectType; + const index = type.getFieldIndex(member); + if (index === -1) { + throw new Error(`Field ${member} not found in ${type.id}`); + } + const memberIndex = type.getFieldIndex(member) + OBJECT_FIELDS_OFFSET; + + return gc.structSetFieldValue({ + mod, + ref: compileExpression({ ...opts, expr: target }), + fieldIndex: memberIndex, + value, + }); +}; + const compileBnrCall = (opts: CompileExprOpts): number => { const { expr } = opts; const funcId = expr.labeledArgAt(0) as Identifier; @@ -505,7 +552,7 @@ export const mapBinaryenType = ( if (isPrimitiveId(type, "voyd")) return binaryen.none; if (type.isObjectType()) return buildObjectType(opts, type); if (type.isUnionType()) return buildUnionType(opts, type); - if (type.isDsArrayType()) return buildDsArrayType(opts, type); + if (type.isFixedArrayType()) return buildFixedArrayType(opts, type); if (type.isIntersectionType()) return buildIntersectionType(opts, type); throw new Error(`Unsupported type ${type}`); }; @@ -513,7 +560,7 @@ export const mapBinaryenType = ( const isPrimitiveId = (type: Type, id: Primitive) => type.isPrimitiveType() && type.name.value === id; -const buildDsArrayType = (opts: CompileExprOpts, type: DsArrayType) => { +const buildFixedArrayType = (opts: CompileExprOpts, type: FixedArrayType) => { if (type.binaryenType) return type.binaryenType; const mod = opts.mod; const elemType = mapBinaryenType(opts, type.elemType!); @@ -578,6 +625,7 @@ const buildObjectType = (opts: MapBinTypeOpts, obj: ObjectType): TypeRef => { ...obj.fields.map((field) => ({ type: mapBinaryenType(opts, field.type!), name: field.name, + mutable: true, })), ], supertype: obj.parentObjType diff --git a/src/lib/binaryen-gc/index.ts b/src/lib/binaryen-gc/index.ts index 9248c78d..6b687340 100644 --- a/src/lib/binaryen-gc/index.ts +++ b/src/lib/binaryen-gc/index.ts @@ -25,7 +25,21 @@ export const defineStructType = ( ) ); const fieldMutablesPtr = allocU32Array( - fields.map(({ mutable }) => (mutable ? 1 : 0)) + fields.reduce((acc, { mutable }, index) => { + // Calculate which u32 slot this boolean belongs to + const u32Index = Math.floor(index / 4); + + // Ensure the slot exists and initialize it to 0 if it doesn't + if (typeof acc[u32Index] === "undefined") { + acc[u32Index] = 0; + } + + // Pack the boolean into the appropriate position in the u32 + const shiftAmount = (index % 4) * 8; + acc[u32Index] |= (mutable ? 1 : 0) << shiftAmount; + + return acc; + }, [] as number[]) ); bin._TypeBuilderSetStructType( @@ -187,6 +201,20 @@ export const structGetFieldValue = ({ ); }; +export const structSetFieldValue = ({ + mod, + fieldIndex, + ref, + value, +}: { + mod: binaryen.Module; + fieldIndex: number; + ref: ExpressionRef; + value: ExpressionRef; +}): ExpressionRef => { + return bin._BinaryenStructSet(mod.ptr, fieldIndex, ref, value); +}; + export const arrayGet = ( mod: binaryen.Module, arrayRef: ExpressionRef, @@ -216,10 +244,10 @@ export const arrayLen = ( export const arrayNew = ( mod: binaryen.Module, type: HeapTypeRef, - initialLength: ExpressionRef, + size: ExpressionRef, init: ExpressionRef ): ExpressionRef => { - return bin._BinaryenArrayNew(mod.ptr, type, initialLength, init); + return bin._BinaryenArrayNew(mod.ptr, type, size, init); }; export const arrayNewFixed = ( diff --git a/src/lib/host-runtime/bool-to-int.ts b/src/lib/host-runtime/bool-to-int.ts deleted file mode 100644 index e5dd067c..00000000 --- a/src/lib/host-runtime/bool-to-int.ts +++ /dev/null @@ -1 +0,0 @@ -export const boolToInt = (bool: boolean) => (bool ? 1 : 0); diff --git a/src/lib/host-runtime/index.ts b/src/lib/host-runtime/index.ts deleted file mode 100644 index e9a3ed18..00000000 --- a/src/lib/host-runtime/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./strings.js"; diff --git a/src/lib/host-runtime/strings.ts b/src/lib/host-runtime/strings.ts deleted file mode 100644 index a9ae8684..00000000 --- a/src/lib/host-runtime/strings.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { boolToInt } from "./bool-to-int.js"; - -export class StringsTable { - readonly rotateAfterIndex = 500000; - readonly strings = new Map(); - nextStringIndex = 0; - - private getNextIndex() { - const index = this.nextStringIndex; - - if (this.nextStringIndex >= this.rotateAfterIndex) { - this.nextStringIndex = 0; - return index; - } - - this.nextStringIndex += 1; - return index; - } - - allocString(): number { - const index = this.getNextIndex(); - this.strings.set(index, ""); - return index; - } - - deAllocString(index: number) { - this.strings.delete(index); - } - - strLength(index: number) { - return this.strings.get(index)?.length ?? -1; - } - - printStr(index: number) { - console.log(this.strings.get(index)); - } - - addCharCodeToString(code: number, index: number) { - const str = this.strings.get(index) ?? ""; - this.strings.set(index, str + String.fromCharCode(code)); - } - - getCharCodeFromString(charIndex: number, strIndex: number) { - return this.strings.get(strIndex)?.[charIndex] ?? -1; - } - - strEquals(aIndex: number, bIndex: number): number { - return boolToInt(this.strings.get(aIndex) === this.strings.get(bIndex)); - } - - strStartsWith(aIndex: number, bIndex: number): number { - return boolToInt( - !!this.strings.get(aIndex)?.startsWith(this.strings.get(bIndex) ?? "") // this could probably cause bugs FYI, consider returning false if either string doesn't exist - ); - } - - strEndsWith(aIndex: number, bIndex: number): number { - return boolToInt( - !!this.strings.get(aIndex)?.endsWith(this.strings.get(bIndex) ?? "") // this could probably cause bugs FYI, consider returning false if either string doesn't exist - ); - } - - strIncludes(aIndex: number, bIndex: number): number { - return boolToInt( - !!this.strings.get(aIndex)?.endsWith(this.strings.get(bIndex) ?? "") // this could probably cause bugs FYI, consider returning false if either string doesn't exist - ); - } - - /** Pass -1 for default flags (g) */ - strTest(strIndex: number, regexIndex: number, flagsIndex: number): number { - const str = this.strings.get(strIndex); - const regex = this.strings.get(regexIndex); - const flags = flagsIndex !== -1 ? this.strings.get(flagsIndex) : "g"; - if (str === undefined || regex === undefined) return 0; - return boolToInt(new RegExp(regex, flags).test(str)); - } -} diff --git a/src/parser/__tests__/__snapshots__/parser.test.ts.snap b/src/parser/__tests__/__snapshots__/parser.test.ts.snap index 3a82e3b3..b2e92eeb 100644 --- a/src/parser/__tests__/__snapshots__/parser.test.ts.snap +++ b/src/parser/__tests__/__snapshots__/parser.test.ts.snap @@ -819,7 +819,7 @@ exports[`parser supports generics 1`] = ` "=", "arr", [ - "ds_array_init", + "new_fixed_array", [ "generics", "i32", diff --git a/src/parser/__tests__/fixtures/voyd-file.ts b/src/parser/__tests__/fixtures/voyd-file.ts index 223f9d32..5df87b29 100644 --- a/src/parser/__tests__/fixtures/voyd-file.ts +++ b/src/parser/__tests__/fixtures/voyd-file.ts @@ -121,7 +121,7 @@ use std::all type DsArrayi32 = DsArray pub fn main() - let arr = ds_array_init(10) + let arr = new_fixed_array(10) arr.set(0, 1) arr.get(0) `; diff --git a/src/run.ts b/src/run.ts index 1657f1c8..af533316 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,35 +1,16 @@ import binaryen from "binaryen"; -import { StringsTable } from "./lib/host-runtime/strings.js"; export function run(mod: binaryen.Module) { const binary = mod.emitBinary(); const compiled = new WebAssembly.Module(binary); - const strings = new StringsTable(); const instance = new WebAssembly.Instance(compiled, { - strings: { - "alloc-string": () => strings.allocString(), - "de-alloc-string": (index: number) => strings.deAllocString(index), - "add-char-code-to-string": (code: number, index: number) => - strings.addCharCodeToString(code, index), - "str-len": (index: number) => strings.strLength(index), - "print-str": (index: number) => strings.printStr(index), - "get-char-code-from-string": (charIndex: number, strIndex: number) => - strings.getCharCodeFromString(charIndex, strIndex), - "str-equals": (aIndex: number, bIndex: number) => - strings.strEquals(aIndex, bIndex), - "str-starts-with": (aIndex: number, bIndex: number) => - strings.strStartsWith(aIndex, bIndex), - "str-ends-with": (aIndex: number, bIndex: number) => - strings.strEndsWith(aIndex, bIndex), - "str-includes": (aIndex: number, bIndex: number) => - strings.strIncludes(aIndex, bIndex), - "str-test": (strIndex: number, regexIndex: number, flagsIndex: number) => - strings.strTest(strIndex, regexIndex, flagsIndex), - }, utils: { log: (val: number) => console.log(val), }, }); - console.log((instance.exports as any).main()); + const fns = instance.exports as any; + const result = fns.main(); + + console.log(result); } diff --git a/src/semantics/check-types.ts b/src/semantics/check-types.ts index fb1f7fed..929ea8e3 100644 --- a/src/semantics/check-types.ts +++ b/src/semantics/check-types.ts @@ -18,6 +18,7 @@ import { ObjectLiteral, UnionType, IntersectionType, + FixedArrayType, } from "../syntax-objects/index.js"; import { Match } from "../syntax-objects/match.js"; import { getExprType } from "./resolution/get-expr-type.js"; @@ -37,6 +38,7 @@ export const checkTypes = (expr: Expr | undefined): Expr => { if (expr.isTypeAlias()) return checkTypeAlias(expr); if (expr.isObjectLiteral()) return checkObjectLiteralType(expr); if (expr.isUnionType()) return checkUnionType(expr); + if (expr.isFixedArrayType()) return checkFixedArrayType(expr); if (expr.isMatch()) return checkMatch(expr); if (expr.isIntersectionType()) return checkIntersectionType(expr); return expr; @@ -56,6 +58,7 @@ const checkCallTypes = (call: Call): Call | ObjectLiteral => { if (call.calls(":")) return checkLabeledArg(call); if (call.calls("=")) return checkAssign(call); if (call.calls("while")) return checkWhile(call); + if (call.calls("FixedArray")) return checkFixedArrayInit(call); if (call.calls("member-access")) return call; // TODO if (call.fn?.isObjectType()) return checkObjectInit(call); @@ -81,6 +84,26 @@ const checkCallTypes = (call: Call): Call | ObjectLiteral => { return call; }; +const checkFixedArrayInit = (call: Call) => { + const type = call.type; + + if (!type || !type.isFixedArrayType()) { + throw new Error(`Expected FixedArray type at ${call.location}`); + } + + checkFixedArrayType(type); + call.args.each((arg) => { + const argType = getExprType(arg); + if (!argType || !typesAreCompatible(argType, type.elemType)) { + throw new Error( + `Expected ${type.elemType?.name} got ${argType?.name} at ${arg.location}` + ); + } + }); + + return call; +}; + const checkWhile = (call: Call) => { const cond = call.argAt(0); const condType = getExprType(cond); @@ -112,7 +135,7 @@ const checkObjectInit = (call: Call): Call => { export const checkAssign = (call: Call) => { const id = call.argAt(0); if (!id?.isIdentifier()) { - throw new Error(`Can only assign to variables for now ${id}`); + return call; } const variable = id.resolve(); @@ -491,3 +514,11 @@ const checkUnionType = (union: UnionType) => { return union; }; + +const checkFixedArrayType = (array: FixedArrayType) => { + if (!array.elemType) { + throw new Error(`Unable to determine element type for ${array.location}`); + } + + return array; +}; diff --git a/src/semantics/index.ts b/src/semantics/index.ts index 33bb892d..35c35571 100644 --- a/src/semantics/index.ts +++ b/src/semantics/index.ts @@ -6,13 +6,13 @@ import { registerModules } from "./modules.js"; import { expandRegularMacros } from "./regular-macros.js"; import { ParsedModule } from "../parser/index.js"; import { Expr } from "../syntax-objects/expr.js"; -import { resolveTypes } from "./resolution/resolve-types.js"; +import { resolveEntities } from "./resolution/resolve-entities.js"; const semanticPhases: SemanticProcessor[] = [ expandRegularMacros, // Also handles use and module declaration initialization initPrimitiveTypes, initEntities, - resolveTypes, + resolveEntities, checkTypes, ]; diff --git a/src/semantics/init-entities.ts b/src/semantics/init-entities.ts index cb13d926..b415bfa5 100644 --- a/src/semantics/init-entities.ts +++ b/src/semantics/init-entities.ts @@ -11,10 +11,11 @@ import { TypeAlias, ObjectType, ObjectLiteral, - DsArrayType, + FixedArrayType, nop, UnionType, IntersectionType, + StringLiteral, } from "../syntax-objects/index.js"; import { Match, MatchCase } from "../syntax-objects/match.js"; import { SemanticProcessor } from "./types.js"; @@ -24,6 +25,8 @@ export const initEntities: SemanticProcessor = (expr) => { return expr.applyMap(initEntities); } + if (expr.isStringLiteral()) return initStringLiteral(expr); + if (!expr.isList()) return expr; if (expr.calls("define_function")) { @@ -256,10 +259,10 @@ const initVar = (varDef: List): Variable => { }; const initDeclaration = (decl: List) => { - const namespaceString = decl.at(1); + const namespace = decl.at(1); - if (!namespaceString?.isStringLiteral()) { - throw new Error("Expected namespace string"); + if (!namespace?.isIdentifier()) { + throw new Error("Expected namespace identifier"); } const fns = decl @@ -270,7 +273,7 @@ const initDeclaration = (decl: List) => { return new Declaration({ ...decl.metadata, - namespace: namespaceString.value, + namespace: namespace.value, fns, }); }; @@ -345,8 +348,8 @@ const initTypeExprEntities = (type?: Expr): Expr | undefined => { return initStructuralObjectType(type); } - if (type.calls("DsArray")) { - return initDsArray(type); + if (type.calls("FixedArray")) { + return initFixedArrayType(type); } if (type.calls("|")) { @@ -360,15 +363,15 @@ const initTypeExprEntities = (type?: Expr): Expr | undefined => { return initCall(type); }; -const initDsArray = (type: List) => { +const initFixedArrayType = (type: List) => { const generics = type.listAt(1); const elemTypeExpr = initTypeExprEntities(generics.at(1)); if (!elemTypeExpr) { - throw new Error("Invalid DsArray type"); + throw new Error("Invalid FixedArray type"); } - return new DsArrayType({ + return new FixedArrayType({ ...type.metadata, elemTypeExpr, name: type.syntaxId.toString(), @@ -521,3 +524,21 @@ const initImpl = (impl: List): Implementation => { /** Expects ["generics", ...Identifiers] */ const extractTypeParams = (list: List) => list.sliceAsArray(1).flatMap((p) => (p.isIdentifier() ? p : [])); + +const initStringLiteral = (str: StringLiteral) => + initEntities( + new List({ + ...str.metadata, + value: [ + "String", + [ + "object", + [ + ":", + "chars", + ["FixedArray", ...str.value.split("").map((c) => c.charCodeAt(0))], + ], + ], + ], + }) + ); diff --git a/src/semantics/resolution/combine-types.ts b/src/semantics/resolution/combine-types.ts new file mode 100644 index 00000000..35f8a196 --- /dev/null +++ b/src/semantics/resolution/combine-types.ts @@ -0,0 +1,50 @@ +import { + IntersectionType, + ObjectType, + Type, + UnionType, +} from "../../syntax-objects/types.js"; + +/** + * Combines types into their least common denominator. + * If all types are the same, it returns that type. + * If all types are different (but still object types), it returns a UnionType. + * If types are mixed and not all object types, it returns undefined. + */ +export const combineTypes = (types: Type[]): Type | undefined => { + const firstType = types[0]; + if (!types.length || !firstType?.isObjectType()) { + return firstType; + } + + let topType: ObjectType | IntersectionType | UnionType = firstType; + for (const type of types.slice(1)) { + if (type.id === topType.id) { + continue; + } + + if (isObjectOrIntersection(type) && isObjectOrIntersection(topType)) { + const union = new UnionType({ + name: `CombinedTypeUnion`, + }); + union.types = [topType, type]; + topType = union; + continue; + } + + if (isObjectOrIntersection(type) && topType.isUnionType()) { + topType.types.push(type); + continue; + } + + return undefined; + } + + return topType; +}; + +const isObjectOrIntersection = ( + type: Type +): type is ObjectType | IntersectionType => { + return type.isObjectType() || type.isIntersectionType(); +}; diff --git a/src/semantics/resolution/get-call-fn.ts b/src/semantics/resolution/get-call-fn.ts index 5f52a680..5a67cafa 100644 --- a/src/semantics/resolution/get-call-fn.ts +++ b/src/semantics/resolution/get-call-fn.ts @@ -1,7 +1,7 @@ import { Call, Expr, Fn } from "../../syntax-objects/index.js"; import { getExprType } from "./get-expr-type.js"; import { typesAreCompatible } from "./types-are-compatible.js"; -import { resolveFnTypes } from "./resolve-fn-type.js"; +import { resolveFn } from "./resolve-fn.js"; export const getCallFn = (call: Call): Fn | undefined => { if (isPrimitiveFnCall(call)) return undefined; @@ -42,7 +42,7 @@ const filterCandidates = (call: Call, candidates: Fn[]): Fn[] => return filterCandidateWithGenerics(call, candidate); } - resolveFnTypes(candidate); + resolveFn(candidate); return parametersMatch(candidate, call) && typeArgsMatch(call, candidate) ? candidate : []; @@ -50,7 +50,7 @@ const filterCandidates = (call: Call, candidates: Fn[]): Fn[] => const filterCandidateWithGenerics = (call: Call, candidate: Fn): Fn[] => { // Resolve generics - if (!candidate.genericInstances) resolveFnTypes(candidate, call); + if (!candidate.genericInstances) resolveFn(candidate, call); // Fn not compatible with call if (!candidate.genericInstances?.length) return []; @@ -64,7 +64,7 @@ const filterCandidateWithGenerics = (call: Call, candidate: Fn): Fn[] => { // If no instances, attempt to resolve generics with this call, as a compatible instance // is still possible const beforeLen = candidate.genericInstances.length; - resolveFnTypes(candidate, call); + resolveFn(candidate, call); const afterLen = candidate.genericInstances.length; if (beforeLen === afterLen) { diff --git a/src/semantics/resolution/get-expr-type.ts b/src/semantics/resolution/get-expr-type.ts index 70db29c2..0c3fb396 100644 --- a/src/semantics/resolution/get-expr-type.ts +++ b/src/semantics/resolution/get-expr-type.ts @@ -1,7 +1,7 @@ import { Expr } from "../../syntax-objects/expr.js"; import { Call, Identifier } from "../../syntax-objects/index.js"; import { Type, i32, f32, bool, i64, f64 } from "../../syntax-objects/types.js"; -import { resolveCallTypes } from "./resolve-call-types.js"; +import { resolveCall } from "./resolve-call.js"; export const getExprType = (expr?: Expr): Type | undefined => { if (!expr) return; @@ -9,7 +9,7 @@ export const getExprType = (expr?: Expr): Type | undefined => { if (expr.isFloat()) return typeof expr.value === "number" ? f32 : f64; if (expr.isBool()) return bool; if (expr.isIdentifier()) return getIdentifierType(expr); - if (expr.isCall()) return resolveCallTypes(expr)?.type; + if (expr.isCall()) return resolveCall(expr)?.type; if (expr.isFn()) return expr.getType(); if (expr.isTypeAlias()) return expr.type; if (expr.isType()) return expr; diff --git a/src/semantics/resolution/index.ts b/src/semantics/resolution/index.ts index 07faf449..c608a662 100644 --- a/src/semantics/resolution/index.ts +++ b/src/semantics/resolution/index.ts @@ -1,3 +1,3 @@ -export { resolveTypes } from "./resolve-types.js"; +export { resolveEntities } from "./resolve-entities.js"; export { typesAreCompatible } from "./types-are-compatible.js"; export { resolveModulePath } from "./resolve-use.js"; diff --git a/src/semantics/resolution/resolve-call-types.ts b/src/semantics/resolution/resolve-call.ts similarity index 70% rename from src/semantics/resolution/resolve-call-types.ts rename to src/semantics/resolution/resolve-call.ts index dab9c970..6b40664b 100644 --- a/src/semantics/resolution/resolve-call-types.ts +++ b/src/semantics/resolution/resolve-call.ts @@ -1,19 +1,26 @@ import { Call } from "../../syntax-objects/call.js"; import { Identifier, List, nop } from "../../syntax-objects/index.js"; -import { dVoid, ObjectType, TypeAlias } from "../../syntax-objects/types.js"; +import { + dVoid, + FixedArrayType, + ObjectType, + TypeAlias, +} from "../../syntax-objects/types.js"; import { getCallFn } from "./get-call-fn.js"; import { getExprType, getIdentifierType } from "./get-expr-type.js"; -import { resolveObjectTypeTypes } from "./resolve-object-type.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveObjectType } from "./resolve-object-type.js"; +import { resolveEntities } from "./resolve-entities.js"; import { resolveExport } from "./resolve-use.js"; +import { combineTypes } from "./combine-types.js"; -export const resolveCallTypes = (call: Call): Call => { +export const resolveCall = (call: Call): Call => { if (call.type) return call; if (call.calls("export")) return resolveExport(call); if (call.calls("if")) return resolveIf(call); - if (call.calls(":")) return checkLabeledArg(call); + if (call.calls(":")) return resolveLabeledArg(call); if (call.calls("while")) return resolveWhile(call); - call.args = call.args.map(resolveTypes); + if (call.calls("FixedArray")) return resolveFixedArray(call); + call.args = call.args.map(resolveEntities); const memberAccessCall = getMemberAccessCall(call); if (memberAccessCall) return memberAccessCall; @@ -30,7 +37,7 @@ export const resolveCallTypes = (call: Call): Call => { } if (call.typeArgs) { - call.typeArgs = call.typeArgs.map(resolveTypes); + call.typeArgs = call.typeArgs.map(resolveEntities); } call.fn = getCallFn(call); @@ -38,15 +45,15 @@ export const resolveCallTypes = (call: Call): Call => { return call; }; -export const checkLabeledArg = (call: Call) => { - call.args = call.args.map(resolveTypes); +export const resolveLabeledArg = (call: Call) => { + call.args = call.args.map(resolveEntities); const expr = call.argAt(1); call.type = getExprType(expr); return call; }; export const resolveObjectInit = (call: Call, type: ObjectType): Call => { - type = resolveObjectTypeTypes(type, call); + type = resolveObjectType(type, call); call.type = type; call.fn = type; return call; @@ -68,13 +75,36 @@ export const resolveTypeAlias = (call: Call, type: TypeAlias): Call => { }); } - alias.typeExpr = resolveTypes(alias.typeExpr); + alias.typeExpr = resolveEntities(alias.typeExpr); alias.type = getExprType(alias.typeExpr); call.type = alias.type; call.fn = call.type?.isObjectType() ? call.type : undefined; return call; }; +const resolveFixedArray = (call: Call) => { + call.args = call.args.map(resolveEntities); + + const elemTypeExpr = + call.typeArgs?.at(0) ?? + combineTypes( + call.args + .toArray() + .map(getExprType) + .filter((t) => !!t) + ) ?? + nop(); + + const elemType = getExprType(elemTypeExpr); + call.type = new FixedArrayType({ + ...call.metadata, + name: Identifier.from(`FixedArray#${call.syntaxId}`), + elemTypeExpr, + elemType, + }); + return call; +}; + const getMemberAccessCall = (call: Call): Call | undefined => { if (call.args.length > 1) return; const a1 = call.argAt(0); @@ -112,7 +142,7 @@ const getMemberAccessCall = (call: Call): Call | undefined => { }; export const resolveIf = (call: Call) => { - call.args = call.args.map(resolveTypes); + call.args = call.args.map(resolveEntities); const thenExpr = call.argAt(1); const elseExpr = call.argAt(2); @@ -128,7 +158,7 @@ export const resolveIf = (call: Call) => { }; export const resolveWhile = (call: Call) => { - call.args = call.args.map(resolveTypes); + call.args = call.args.map(resolveEntities); call.type = dVoid; return call; }; diff --git a/src/semantics/resolution/resolve-types.ts b/src/semantics/resolution/resolve-entities.ts similarity index 57% rename from src/semantics/resolution/resolve-types.ts rename to src/semantics/resolution/resolve-entities.ts index 400b4fc0..53e2e3b0 100644 --- a/src/semantics/resolution/resolve-types.ts +++ b/src/semantics/resolution/resolve-entities.ts @@ -5,20 +5,20 @@ import { List } from "../../syntax-objects/list.js"; import { VoidModule } from "../../syntax-objects/module.js"; import { ObjectLiteral } from "../../syntax-objects/object-literal.js"; import { - DsArrayType, + FixedArrayType, ObjectType, TypeAlias, voydBaseObject, } from "../../syntax-objects/types.js"; import { Variable } from "../../syntax-objects/variable.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveCallTypes } from "./resolve-call-types.js"; -import { resolveFnTypes } from "./resolve-fn-type.js"; +import { resolveCall } from "./resolve-call.js"; +import { resolveFn } from "./resolve-fn.js"; import { resolveImpl } from "./resolve-impl.js"; -import { resolveIntersection } from "./resolve-intersection.js"; +import { resolveIntersectionType } from "./resolve-intersection.js"; import { resolveMatch } from "./resolve-match.js"; -import { resolveObjectTypeTypes } from "./resolve-object-type.js"; -import { resolveUnion } from "./resolve-union.js"; +import { resolveObjectType } from "./resolve-object-type.js"; +import { resolveUnionType } from "./resolve-union.js"; import { resolveUse } from "./resolve-use.js"; /** @@ -28,34 +28,34 @@ import { resolveUse } from "./resolve-use.js"; * Should probably rename this to resolveEntities and separate type resolution * into a new resolveTypes function that returns Type | undefined */ -export const resolveTypes = (expr: Expr | undefined): Expr => { +export const resolveEntities = (expr: Expr | undefined): Expr => { if (!expr) return nop(); - if (expr.isBlock()) return resolveBlockTypes(expr); - if (expr.isCall()) return resolveCallTypes(expr); - if (expr.isFn()) return resolveFnTypes(expr); - if (expr.isVariable()) return resolveVarTypes(expr); - if (expr.isModule()) return resolveModuleTypes(expr); + if (expr.isBlock()) return resolveBlock(expr); + if (expr.isCall()) return resolveCall(expr); + if (expr.isFn()) return resolveFn(expr); + if (expr.isVariable()) return resolveVar(expr); + if (expr.isModule()) return resolveModule(expr); if (expr.isList()) return resolveListTypes(expr); - if (expr.isUse()) return resolveUse(expr, resolveModuleTypes); - if (expr.isObjectType()) return resolveObjectTypeTypes(expr); - if (expr.isDsArrayType()) return resolveDsArrayTypeTypes(expr); - if (expr.isTypeAlias()) return resolveTypeAliasTypes(expr); - if (expr.isObjectLiteral()) return resolveObjectLiteralTypes(expr); + if (expr.isUse()) return resolveUse(expr, resolveModule); + if (expr.isObjectType()) return resolveObjectType(expr); + if (expr.isFixedArrayType()) return resolveFixedArrayType(expr); + if (expr.isTypeAlias()) return resolveTypeAlias(expr); + if (expr.isObjectLiteral()) return resolveObjectLiteral(expr); if (expr.isMatch()) return resolveMatch(expr); if (expr.isImpl()) return resolveImpl(expr); - if (expr.isUnionType()) return resolveUnion(expr); - if (expr.isIntersectionType()) return resolveIntersection(expr); + if (expr.isUnionType()) return resolveUnionType(expr); + if (expr.isIntersectionType()) return resolveIntersectionType(expr); return expr; }; -const resolveBlockTypes = (block: Block): Block => { - block.applyMap(resolveTypes); +const resolveBlock = (block: Block): Block => { + block.applyMap(resolveEntities); block.type = getExprType(block.body.at(-1)); return block; }; -export const resolveVarTypes = (variable: Variable): Variable => { - const initializer = resolveTypes(variable.initializer); +export const resolveVar = (variable: Variable): Variable => { + const initializer = resolveEntities(variable.initializer); variable.initializer = initializer; variable.inferredType = getExprType(initializer); @@ -67,10 +67,10 @@ export const resolveVarTypes = (variable: Variable): Variable => { return variable; }; -export const resolveModuleTypes = (mod: VoidModule): VoidModule => { +export const resolveModule = (mod: VoidModule): VoidModule => { if (mod.phase >= 3) return mod; mod.phase = 3; - mod.each(resolveTypes); + mod.each(resolveEntities); mod.phase = 4; return mod; }; @@ -78,26 +78,26 @@ export const resolveModuleTypes = (mod: VoidModule): VoidModule => { const resolveListTypes = (list: List) => { console.log("Unexpected list"); console.log(JSON.stringify(list, undefined, 2)); - return list.map(resolveTypes); + return list.map(resolveEntities); }; -const resolveDsArrayTypeTypes = (arr: DsArrayType): DsArrayType => { - arr.elemTypeExpr = resolveTypes(arr.elemTypeExpr); +const resolveFixedArrayType = (arr: FixedArrayType): FixedArrayType => { + arr.elemTypeExpr = resolveEntities(arr.elemTypeExpr); arr.elemType = getExprType(arr.elemTypeExpr); arr.id = `${arr.id}#${arr.elemType?.id}`; return arr; }; -const resolveTypeAliasTypes = (alias: TypeAlias): TypeAlias => { +const resolveTypeAlias = (alias: TypeAlias): TypeAlias => { if (alias.type) return alias; - alias.typeExpr = resolveTypes(alias.typeExpr); + alias.typeExpr = resolveEntities(alias.typeExpr); alias.type = getExprType(alias.typeExpr); return alias; }; -const resolveObjectLiteralTypes = (obj: ObjectLiteral) => { +const resolveObjectLiteral = (obj: ObjectLiteral) => { obj.fields.forEach((field) => { - field.initializer = resolveTypes(field.initializer); + field.initializer = resolveEntities(field.initializer); field.type = getExprType(field.initializer); return field; }); diff --git a/src/semantics/resolution/resolve-fn-type.ts b/src/semantics/resolution/resolve-fn.ts similarity index 91% rename from src/semantics/resolution/resolve-fn-type.ts rename to src/semantics/resolution/resolve-fn.ts index cba3ab1b..8cfda0bc 100644 --- a/src/semantics/resolution/resolve-fn-type.ts +++ b/src/semantics/resolution/resolve-fn.ts @@ -6,7 +6,7 @@ import { List } from "../../syntax-objects/list.js"; import { Parameter } from "../../syntax-objects/parameter.js"; import { TypeAlias } from "../../syntax-objects/types.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveEntities } from "./resolve-entities.js"; export type ResolveFnTypesOpts = { typeArgs?: List; @@ -14,7 +14,7 @@ export type ResolveFnTypesOpts = { }; /** Pass call to potentially resolve generics */ -export const resolveFnTypes = (fn: Fn, call?: Call): Fn => { +export const resolveFn = (fn: Fn, call?: Call): Fn => { if (fn.typesResolved) { // Already resolved return fn; @@ -32,13 +32,13 @@ export const resolveFnTypes = (fn: Fn, call?: Call): Fn => { resolveParameters(fn.parameters); if (fn.returnTypeExpr) { - fn.returnTypeExpr = resolveTypes(fn.returnTypeExpr); + fn.returnTypeExpr = resolveEntities(fn.returnTypeExpr); fn.annotatedReturnType = getExprType(fn.returnTypeExpr); fn.returnType = fn.annotatedReturnType; } fn.typesResolved = true; - fn.body = resolveTypes(fn.body); + fn.body = resolveEntities(fn.body); fn.inferredReturnType = getExprType(fn.body); fn.returnType = fn.annotatedReturnType ?? fn.inferredReturnType; fn.parentImpl?.registerMethod(fn); // Maybe do this for module when not in an impl @@ -68,7 +68,7 @@ const resolveParameters = (params: Parameter[]) => { throw new Error(`Unable to determine type for ${p}`); } - p.typeExpr = resolveTypes(p.typeExpr); + p.typeExpr = resolveEntities(p.typeExpr); p.type = getExprType(p.typeExpr); }); }; @@ -107,7 +107,7 @@ const resolveGenericsWithTypeArgs = (fn: Fn, args: List): Fn => { newFn.registerEntity(type); }); - const resolvedFn = resolveFnTypes(newFn); + const resolvedFn = resolveFn(newFn); fn.registerGenericInstance(resolvedFn); return fn; }; diff --git a/src/semantics/resolution/resolve-impl.ts b/src/semantics/resolution/resolve-impl.ts index 99f3fa42..84e51a5f 100644 --- a/src/semantics/resolution/resolve-impl.ts +++ b/src/semantics/resolution/resolve-impl.ts @@ -2,8 +2,8 @@ import { nop } from "../../syntax-objects/helpers.js"; import { Implementation } from "../../syntax-objects/implementation.js"; import { ObjectType, TypeAlias } from "../../syntax-objects/types.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveObjectTypeTypes } from "./resolve-object-type.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveObjectType } from "./resolve-object-type.js"; +import { resolveEntities } from "./resolve-entities.js"; export const resolveImpl = ( impl: Implementation, @@ -41,7 +41,7 @@ export const resolveImpl = ( } impl.typesResolved = true; - impl.body.value = resolveTypes(impl.body.value); + impl.body.value = resolveEntities(impl.body.value); return impl; }; @@ -57,7 +57,7 @@ const resolveTargetType = (impl: Implementation): ObjectType | undefined => { if (!type || !type.isObjectType()) return; if (type.typeParameters?.length && expr.isCall()) { - const obj = resolveObjectTypeTypes(type, expr); + const obj = resolveObjectType(type, expr); // Object fully resolved to non-generic version i.e. `Vec` if (!obj.typeParameters?.length) return obj; } diff --git a/src/semantics/resolution/resolve-intersection.ts b/src/semantics/resolution/resolve-intersection.ts index 2f43181a..c6da101b 100644 --- a/src/semantics/resolution/resolve-intersection.ts +++ b/src/semantics/resolution/resolve-intersection.ts @@ -1,12 +1,14 @@ import { IntersectionType } from "../../syntax-objects/types.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveEntities } from "./resolve-entities.js"; -export const resolveIntersection = ( +export const resolveIntersectionType = ( inter: IntersectionType ): IntersectionType => { - inter.nominalTypeExpr.value = resolveTypes(inter.nominalTypeExpr.value); - inter.structuralTypeExpr.value = resolveTypes(inter.structuralTypeExpr.value); + inter.nominalTypeExpr.value = resolveEntities(inter.nominalTypeExpr.value); + inter.structuralTypeExpr.value = resolveEntities( + inter.structuralTypeExpr.value + ); const nominalType = getExprType(inter.nominalTypeExpr.value); const structuralType = getExprType(inter.structuralTypeExpr.value); diff --git a/src/semantics/resolution/resolve-match.ts b/src/semantics/resolution/resolve-match.ts index b4ec9aca..43020b6a 100644 --- a/src/semantics/resolution/resolve-match.ts +++ b/src/semantics/resolution/resolve-match.ts @@ -1,19 +1,12 @@ import { Block } from "../../syntax-objects/block.js"; -import { - Call, - IntersectionType, - ObjectType, - Parameter, - Type, - UnionType, - Variable, -} from "../../syntax-objects/index.js"; +import { Call, Parameter, Type, Variable } from "../../syntax-objects/index.js"; import { Match, MatchCase } from "../../syntax-objects/match.js"; +import { combineTypes } from "./combine-types.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveTypes, resolveVarTypes } from "./resolve-types.js"; +import { resolveEntities, resolveVar } from "./resolve-entities.js"; export const resolveMatch = (match: Match): Match => { - match.operand = resolveTypes(match.operand); + match.operand = resolveEntities(match.operand); match.baseType = getExprType(match.operand); const binding = getBinding(match); @@ -46,7 +39,7 @@ const resolveCase = ( // to avoid this. c.expr.registerEntity(localBinding); - const expr = resolveTypes(c.expr) as Call | Block; + const expr = resolveEntities(c.expr) as Call | Block; return { matchType: type?.isObjectType() ? type : undefined, @@ -57,7 +50,7 @@ const resolveCase = ( const getBinding = (match: Match): Parameter | Variable => { if (match.bindVariable) { - return resolveVarTypes(match.bindVariable); + return resolveVar(match.bindVariable); } const binding = match.bindIdentifier.resolve(); @@ -75,39 +68,5 @@ const resolveMatchReturnType = (match: Match): Type | undefined => { .concat(match.defaultCase?.expr.type) .filter((t) => t !== undefined); - const firstType = cases[0]; - if (!cases.length || !firstType?.isObjectType()) { - return firstType; - } - - let type: ObjectType | IntersectionType | UnionType = firstType; - for (const mCase of cases.slice(1)) { - if (mCase.id === type.id) { - continue; - } - - if (isObjectOrIntersection(mCase) && isObjectOrIntersection(type)) { - const union = new UnionType({ - name: `Union#match#(${match.syntaxId}`, - }); - union.types = [type, mCase]; - type = union; - continue; - } - - if (isObjectOrIntersection(mCase) && type.isUnionType()) { - type.types.push(mCase); - continue; - } - - return undefined; - } - - return type; -}; - -const isObjectOrIntersection = ( - type: Type -): type is ObjectType | IntersectionType => { - return type.isObjectType() || type.isIntersectionType(); + return combineTypes(cases); }; diff --git a/src/semantics/resolution/resolve-object-type.ts b/src/semantics/resolution/resolve-object-type.ts index 83ef23c7..e28bfc39 100644 --- a/src/semantics/resolution/resolve-object-type.ts +++ b/src/semantics/resolution/resolve-object-type.ts @@ -8,13 +8,10 @@ import { } from "../../syntax-objects/types.js"; import { getExprType } from "./get-expr-type.js"; import { implIsCompatible, resolveImpl } from "./resolve-impl.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveEntities } from "./resolve-entities.js"; import { typesAreCompatible } from "./types-are-compatible.js"; -export const resolveObjectTypeTypes = ( - obj: ObjectType, - call?: Call -): ObjectType => { +export const resolveObjectType = (obj: ObjectType, call?: Call): ObjectType => { if (obj.typesResolved) return obj; if (obj.typeParameters) { @@ -22,7 +19,7 @@ export const resolveObjectTypeTypes = ( } obj.fields.forEach((field) => { - field.typeExpr = resolveTypes(field.typeExpr); + field.typeExpr = resolveEntities(field.typeExpr); field.type = getExprType(field.typeExpr); }); @@ -77,7 +74,7 @@ const resolveGenericsWithTypeArgs = ( }); if (typesNotResolved) return obj; - const resolvedObj = resolveObjectTypeTypes(newObj); + const resolvedObj = resolveObjectType(newObj); obj.registerGenericInstance(resolvedObj); const implementations = newObj.implementations; diff --git a/src/semantics/resolution/resolve-union.ts b/src/semantics/resolution/resolve-union.ts index 11fcf4d6..61fbad98 100644 --- a/src/semantics/resolution/resolve-union.ts +++ b/src/semantics/resolution/resolve-union.ts @@ -1,9 +1,9 @@ import { UnionType } from "../../syntax-objects/types.js"; import { getExprType } from "./get-expr-type.js"; -import { resolveTypes } from "./resolve-types.js"; +import { resolveEntities } from "./resolve-entities.js"; -export const resolveUnion = (union: UnionType): UnionType => { - union.childTypeExprs.applyMap((expr) => resolveTypes(expr)); +export const resolveUnionType = (union: UnionType): UnionType => { + union.childTypeExprs.applyMap((expr) => resolveEntities(expr)); union.types = union.childTypeExprs.toArray().flatMap((expr) => { const type = getExprType(expr); diff --git a/src/semantics/resolution/resolve-use.ts b/src/semantics/resolution/resolve-use.ts index 93dac384..608ba07f 100644 --- a/src/semantics/resolution/resolve-use.ts +++ b/src/semantics/resolution/resolve-use.ts @@ -4,7 +4,7 @@ import { List } from "../../syntax-objects/list.js"; import { VoidModule } from "../../syntax-objects/module.js"; import { NamedEntity } from "../../syntax-objects/named-entity.js"; import { Use, UseEntities } from "../../syntax-objects/use.js"; -import { resolveModuleTypes, resolveTypes } from "./resolve-types.js"; +import { resolveModule, resolveEntities } from "./resolve-entities.js"; export type ModulePass = (mod: VoidModule) => VoidModule; @@ -145,8 +145,8 @@ export const resolveExport = (call: Call) => { const block = call.argAt(0); if (!block?.isBlock()) return call; - const entities = block.body.map(resolveTypes); - registerExports(call, entities, resolveModuleTypes); + const entities = block.body.map(resolveEntities); + registerExports(call, entities, resolveModule); return call; }; diff --git a/src/semantics/resolution/types-are-compatible.ts b/src/semantics/resolution/types-are-compatible.ts index 5d91b2ca..4d218fb1 100644 --- a/src/semantics/resolution/types-are-compatible.ts +++ b/src/semantics/resolution/types-are-compatible.ts @@ -48,7 +48,7 @@ export const typesAreCompatible = ( return a.extends(b.nominalType) && typesAreCompatible(a, b.structuralType); } - if (a.isDsArrayType() && b.isDsArrayType()) { + if (a.isFixedArrayType() && b.isFixedArrayType()) { return typesAreCompatible(a.elemType, b.elemType); } diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index e12ddc1b..f7a5ac02 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -22,7 +22,7 @@ import type { ObjectType, Type, TypeAlias, - DsArrayType, + FixedArrayType, UnionType, IntersectionType, } from "./types.js"; @@ -208,8 +208,8 @@ export abstract class Syntax { return this.isType() && this.kindOfType === "intersection"; } - isDsArrayType(): this is DsArrayType { - return this.isType() && this.kindOfType === "ds-array"; + isFixedArrayType(): this is FixedArrayType { + return this.isType() && this.kindOfType === "fixed-array"; } isPrimitiveType(): this is PrimitiveType { diff --git a/src/syntax-objects/types.ts b/src/syntax-objects/types.ts index a1e33eed..f455362e 100644 --- a/src/syntax-objects/types.ts +++ b/src/syntax-objects/types.ts @@ -15,7 +15,7 @@ export type Type = | IntersectionType | ObjectType | TupleType - | DsArrayType + | FixedArrayType | FnType | TypeAlias; @@ -279,9 +279,8 @@ export class ObjectType extends BaseType implements ScopedEntity { } /** Dynamically Sized Array (The raw gc array type) */ -export class DsArrayType extends BaseType { - readonly kindOfType = "ds-array"; - readonly size = Infinity; +export class FixedArrayType extends BaseType { + readonly kindOfType = "fixed-array"; elemTypeExpr: Expr; elemType?: Type; /** Type used for locals, globals, function return type */ @@ -294,15 +293,15 @@ export class DsArrayType extends BaseType { this.elemType = opts.elemType; } - clone(parent?: Expr): DsArrayType { - return new DsArrayType({ + clone(parent?: Expr): FixedArrayType { + return new FixedArrayType({ ...super.getCloneOpts(parent), elemTypeExpr: this.elemTypeExpr.clone(), }); } toJSON(): TypeJSON { - return ["type", ["DsArray", this.elemType]]; + return ["type", ["FixedArray", this.elemType]]; } } diff --git a/std/array.voyd b/std/array.voyd index e2ab1fec..f343022b 100644 --- a/std/array.voyd +++ b/std/array.voyd @@ -10,15 +10,33 @@ macro bin_type_to_heap_type(type) namespace: gc args: [BnrType<($type)>] -pub fn ds_array_init(size: i32) -> DsArray - binaryen_gc_call(arrayNew, [bin_type_to_heap_type(DsArray), size]) +pub fn new_fixed_array(size: i32) -> FixedArray + binaryen_gc_call(arrayNew, [bin_type_to_heap_type(FixedArray), size]) -pub fn get(arr: DsArray, index: i32) -> T +pub fn get(arr: FixedArray, index: i32) -> T binaryen_gc_call( arrayGet, [arr, index, BnrType, BnrConst(false)] ) -pub fn set(arr: DsArray, index: i32, value: T) -> DsArray +pub fn set(arr: FixedArray, index: i32, value: T) -> FixedArray binaryen_gc_call(arraySet, [arr, index, value]) arr + +pub fn copy(destArr: FixedArray, opts: { + from: FixedArray, + toIndex: i32, + fromIndex: i32, + count: i32 +}) -> FixedArray + binaryen_gc_call(arrayCopy, [ + destArr, + opts.toIndex, + opts.from, + opts.fromIndex, + opts.count + ]) + destArr + +pub fn length(arr: FixedArray) -> i32 + binaryen_gc_call(arrayLen, [arr]) diff --git a/std/index.voyd b/std/index.voyd index 14f07201..87246c9c 100644 --- a/std/index.voyd +++ b/std/index.voyd @@ -3,5 +3,6 @@ pub mod macros pub mod macros::all pub mod operators::all pub mod utils::all -pub mod strings::all +pub mod string::all pub mod array::all +pub mod string diff --git a/std/operators.voyd b/std/operators.voyd index 17941c8f..39337e0c 100644 --- a/std/operators.voyd +++ b/std/operators.voyd @@ -58,3 +58,21 @@ pub def_wasm_operator('+', add, f64, f64) pub def_wasm_operator('-', sub, f64, f64) pub def_wasm_operator('*', mul, f64, f64) pub def_wasm_operator('/', div, f64, f64) + +pub fn '=='(left: bool, right: bool) -> bool + binaryen + func: eq + namespace: i32 + args: [left, right] + +pub fn 'and'(left: bool, right: bool) -> bool + binaryen + func: 'and' + namespace: i32 + args: [left, right] + +pub fn 'or'(left: bool, right: bool) -> bool + binaryen + func: 'or' + namespace: i32 + args: [left, right] diff --git a/std/string.voyd b/std/string.voyd new file mode 100644 index 00000000..5e0be137 --- /dev/null +++ b/std/string.voyd @@ -0,0 +1,60 @@ +use std::macros::all +use array::all +use operators::all + +pub obj String { + chars: FixedArray +} + +impl String + pub fn slice(self, start: i32, end: i32) -> String + let new_length = end - start + let new_chars = new_fixed_array(new_length) + new_chars.copy({ + from: self.chars, + toIndex: 0, + fromIndex: start, + count: new_length + }) + String { chars: new_chars } + + pub fn char_code_at(self, index: i32) -> i32 + if (index < 0) or (index >= self.chars.length()) then: + -1 + else: self.chars.get(index) + + pub fn length(self) -> i32 + self.chars.length() + + pub fn '+'(self, other: String) -> String + let new_length = self.chars.length() + other.chars.length() + let new_chars = new_fixed_array(new_length) + new_chars.copy({ + from: self.chars, + toIndex: 0, + fromIndex: 0, + count: self.chars.length() + }) + new_chars.copy({ + from: other.chars, + toIndex: self.chars.length(), + fromIndex: 0, + count: other.chars.length() + }) + String { chars: new_chars } + +obj StringReader { + str: String, + index: i32 +} + +pub fn new_string_reader(str: String) -> StringReader + StringReader { str: str, index: 0 } + +pub fn read_next_char(reader: StringReader) -> i32 + if reader.index >= reader.str.length() then: + -1 + else: + let char = reader.str.char_code_at(reader.index) + reader.index = reader.index + 1 + char diff --git a/std/strings.voyd b/std/strings.voyd deleted file mode 100644 index 49bbf5a8..00000000 --- a/std/strings.voyd +++ /dev/null @@ -1,36 +0,0 @@ -use std::macros::all - -type String = i32 - -declare "strings" - pub fn alloc_string() -> String - - pub fn de_alloc_string(str: String) -> void - - pub fn str_len(str: String) -> i32 - - // Returns -1 if not found - pub fn get_char_code_from_string(charIndex: i32, str:String) -> i32 - - pub fn add_char_code_to_string(char:i32, str:String) -> void - - pub fn print_str(str: String) -> void - - pub fn str_equals(a:String, b:String) -> void - - pub fn str_starts_with(str: String, startsWith: String) -> void - - pub fn str_ends_with(str: String, endsWith: String) -> void - - pub fn str_includes(str:String, includes: String) -> void - -// Regex test (pass -1 to flags for default (g)) - pub fn str_test(str: String, regex:String, flags: String) -> void - -pub macro string(str) - let add_codes = str.split("").map (char) => - ` add_char_code_to_string $(char_to_code char) index - ` - let index: String = alloc_string() - $@add_codes - index diff --git a/std/utils.voyd b/std/utils.voyd index a4f74c71..e4016a7a 100644 --- a/std/utils.voyd +++ b/std/utils.voyd @@ -1,6 +1,6 @@ use std::macros::all -declare "utils" +declare 'utils' pub fn log(val:i32) -> void pub fn log(val:f32) -> void pub fn log(val:f64) -> void