diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 96c922f..9a8b8b9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -51,6 +51,7 @@ module.exports = { // This lint is horrible with noisy false positives every time there are typescript errors. "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-assignment": "off", + "eslint@typescript-eslint/no-unsafe-argument": "off", // Useful extra lints that are not on by default: "@typescript-eslint/explicit-module-boundary-types": "warn", diff --git a/src/ast.ts b/src/ast.ts index ba9eca3..c5f8936 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -144,10 +144,20 @@ export type FunctionArg

= { export type TypeDef

= { name: string; - fields: FieldDef

[]; + type: TypeDefKind

; ty?: TyStruct; }; +export type TypeDefKind

= + | { + kind: "struct"; + fields: FieldDef

[]; + } + | { + kind: "alias"; + type: Type

; + }; + export type FieldDef

= { name: Ident; type: Type

; @@ -624,15 +634,29 @@ export function superFoldItem( }; } case "type": { - const fields = item.node.fields.map(({ name, type }) => ({ - name, - type: folder.type(type), - })); + const typeKind = item.node.type; + let type: TypeDefKind; + switch (typeKind.kind) { + case "struct": { + const fields = typeKind.fields.map(({ name, type }) => ({ + name, + type: folder.type(type), + })); + type = { kind: "struct", fields }; + break; + } + case "alias": { + type = { + kind: "alias", + type: folder.type(typeKind.type), + }; + } + } return { ...item, kind: "type", - node: { name: item.node.name, fields }, + node: { name: item.node.name, type }, }; } case "import": { diff --git a/src/codegen.ts b/src/codegen.ts index 44e796b..870539e 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -36,8 +36,8 @@ const WASM_PAGE = 65536; const DUMMY_IDX = 9999999; -const ALLOCATE_ITEM: string[] = ["std", "rt", "allocateItem"]; -const DEALLOCATE_ITEM: string[] = ["std", "rt", "deallocateItem"]; +const ALLOCATE_ITEM: string[] = ["std", "rt", "alloc", "allocateItem"]; +const DEALLOCATE_ITEM: string[] = ["std", "rt", "alloc", "deallocateItem"]; type RelocationKind = | { diff --git a/src/context.ts b/src/context.ts index 06ccbe6..c0a2ea5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -79,7 +79,11 @@ export function parseArgs(hardcodedInput: string): Options { } input = fs.readFileSync(filename, { encoding: "utf-8" }); - packageName = path.basename(filename, ".nil"); + if (filename.endsWith(".mod.nil")) { + packageName = path.basename(filename, ".mod.nil"); + } else { + packageName = path.basename(filename, ".nil"); + } const debugArg = process.argv.find((arg) => arg.startsWith("--debug=")); if (debugArg !== undefined) { diff --git a/src/index.ts b/src/index.ts index 7756035..016738d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,9 +13,14 @@ import { GlobalContext, parseArgs } from "./context"; import { loadCrate } from "./loader"; const INPUT = ` -type A = { a: Int }; +type A = struct { a: Int }; + +type What = What; + +type Uwu = (Int, Int); function main() = ( + let a: What = 0; uwu(); ); @@ -28,7 +33,7 @@ function uwu() = ( /*-1*/ ); -type B = { +type B = struct { a: (Int, Int, Int, Int, Int), }; @@ -59,7 +64,9 @@ function main() { () => { const start = Date.now(); - gcx.crateLoader(gcx, "std", Span.startOfFile(file)); + if (packageName !== "std") { + gcx.crateLoader(gcx, "std", Span.startOfFile(file)); + } const tokens = tokenize(file); if (debug.has("tokens")) { diff --git a/src/lexer.ts b/src/lexer.ts index a5e9d7b..e4785ea 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -13,6 +13,7 @@ export type DatalessToken = | "extern" | "mod" | "global" + | "struct" | "(" | ")" | "{" @@ -323,6 +324,7 @@ const KEYOWRDS: DatalessToken[] = [ "extern", "mod", "global", + "struct", ]; const KEYWORD_SET = new Set(KEYOWRDS); diff --git a/src/parser.ts b/src/parser.ts index d8e4116..714dd8e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -31,6 +31,7 @@ import { ItemId, GlobalItem, StructLiteralField, + TypeDefKind, } from "./ast"; import { CompilerError, LoadedFile, Span } from "./error"; import { @@ -107,32 +108,46 @@ function parseItem(t: State): [State, Item] { let name; [t, name] = expectNext(t, "identifier"); [t] = expectNext(t, "="); - [t] = expectNext(t, "{"); - let fields; - [t, fields] = parseCommaSeparatedList>(t, "}", (t) => { - let name; - [t, name] = expectNext(t, "identifier"); - [t] = expectNext(t, ":"); - let type; - [t, type] = parseType(t); - return [ - t, - { - name: { - name: name.ident, - span: name.span, + let type: TypeDefKind; + + let struct; + [t, struct] = eat(t, "struct"); + if (struct) { + [t] = expectNext(t, "{"); + + let fields; + [t, fields] = parseCommaSeparatedList>(t, "}", (t) => { + let name; + [t, name] = expectNext(t, "identifier"); + [t] = expectNext(t, ":"); + let type; + [t, type] = parseType(t); + return [ + t, + { + name: { + name: name.ident, + span: name.span, + }, + type, }, - type, - }, - ]; - }); + ]; + }); + + type = { kind: "struct", fields }; + } else { + let aliased: Type; + [t, aliased] = parseType(t); + + type = { kind: "alias", type: aliased }; + } [t] = expectNext(t, ";"); const def: TypeDef = { name: name.ident, - fields, + type, }; return [ diff --git a/src/printer.ts b/src/printer.ts index 119aef3..ed5376d 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -63,14 +63,22 @@ function printFunction(func: FunctionDef): string { } function printTypeDef(type: TypeDef): string { - const fields = type.fields.map( - ({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`, - ); + switch (type.type.kind) { + case "struct": { + const { fields } = type.type; - const fieldPart = - type.fields.length === 0 ? "{}" : `{\n${fields.join("\n")}\n}`; + const fieldStr = fields.map( + ({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`, + ); + const fieldPart = + fields.length === 0 ? "{}" : `{\n${fieldStr.join("\n")}\n}`; - return `type ${type.name} = ${fieldPart};`; + return `type ${type.name} = ${fieldPart};`; + } + case "alias": { + return `type ${type.name} = ${printType(type.type.type)}`; + } + } } function printImportDef(def: ImportDef): string { diff --git a/src/typeck.ts b/src/typeck.ts index 20ee69a..2f2c174 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -175,23 +175,29 @@ export function typeck( return ty; } case "type": { - const ty: Ty = { - kind: "struct", - name: item.node.name, - fields: [ - /*dummy*/ - ], - }; + switch (item.node.type.kind) { + case "struct": { + const ty: Ty = { + kind: "struct", + name: item.node.name, + fields: [ + /*dummy*/ + ], + }; - itemTys.set(item.id, ty); + itemTys.set(item.id, ty); - const fields = item.node.fields.map<[string, Ty]>(({ name, type }) => [ - name.name, - lowerAstTy(type), - ]); + const fields = item.node.type.fields.map<[string, Ty]>( + ({ name, type }) => [name.name, lowerAstTy(type)], + ); - ty.fields = fields; - return ty; + ty.fields = fields; + return ty; + } + case "alias": { + return lowerAstTy(item.node.type.type); + } + } } case "mod": { throw new CompilerError( @@ -313,32 +319,51 @@ export function typeck( }; } case "type": { - const fieldNames = new Set(); - item.node.fields.forEach(({ name }) => { - if (fieldNames.has(name)) { - throw new CompilerError( - `type ${item.node.name} has a duplicate field: ${name.name}`, - name.span, - ); - } - fieldNames.add(name); - }); + switch (item.node.type.kind) { + case "struct": { + const fieldNames = new Set(); + item.node.type.fields.forEach(({ name }) => { + if (fieldNames.has(name)) { + throw new CompilerError( + `type ${item.node.name} has a duplicate field: ${name.name}`, + name.span, + ); + } + fieldNames.add(name); + }); - const ty = typeOfItem(item.id, item.span) as TyStruct; + const ty = typeOfItem(item.id, item.span) as TyStruct; - return { - ...item, - node: { - name: item.node.name, - fields: item.node.fields.map((field, i) => ({ - name: field.name, - type: { - ...field.type, - ty: ty.fields[i][1], + return { + ...item, + node: { + name: item.node.name, + type: { + kind: "struct", + fields: item.node.type.fields.map((field, i) => ({ + name: field.name, + type: { + ...field.type, + ty: ty.fields[i][1], + }, + })), + }, }, - })), - }, - }; + }; + } + case "alias": { + return { + ...item, + node: { + name: item.node.name, + type: { + kind: "alias", + type: item.node.type.type, + }, + }, + }; + } + } } case "mod": { return { diff --git a/std/rt.nil b/std/rt.nil deleted file mode 100644 index baed765..0000000 --- a/std/rt.nil +++ /dev/null @@ -1,44 +0,0 @@ -// Start the heap at 1024. In practice this could probably be as low as we want. -// TODO: The compiler should set this global to whatever it has calculated the heap -// start to be. But well, 1024 ought to be enough for now. lol. -global HEAD_PTR: I32 = 1024_I32; - -// Every struct has a header of an I32 as a refcount. - -// Allocate a new item. We do not deallocate anything yet. -// lol. -function allocateItem(objSize: I32, align: I32): I32 = ( - if align < 4_I32 then std.abort("invalid alignment"); - - // Include the refcount header. - let actualSize = 4_I32 + objSize; - - // Let's see whether we can fit the refcount into the align bits. - // I happen to know that everything will always be at least 4 bytes aligned. - let alignedPtr = std.alignUp(HEAD_PTR, align); - let actualObjPtr = if (alignedPtr - HEAD_PTR) > align then ( - alignedPtr - 4_I32 - ) else ( - // Take up the next spot. - alignedPtr + align - 4_I32 - ); - - let newHeadPtr = actualObjPtr + actualSize; - - if newHeadPtr > __memory_size() then ( - // 16 pages, very arbitrary. - let result = __memory_grow(16_I32); - // If allocation failed we get -1. We don't have negative numbers yet, lol. - if result > 4294967295_I32 then ( - std.abort("failed to grow memory"); - ); - ); - - HEAD_PTR = newHeadPtr; - - actualObjPtr -); - -function deallocateItem(ptr: I32, objSize: I32) = ( - std.println("uwu deawwocate :3"); -); \ No newline at end of file diff --git a/std/rt/alloc.nil b/std/rt/alloc.nil new file mode 100644 index 0000000..c5c2534 --- /dev/null +++ b/std/rt/alloc.nil @@ -0,0 +1,112 @@ +// Start the heap at 1024. In practice this could probably be as low as we want. +// TODO: The compiler should set this global to whatever it has calculated the heap +// start to be. But well, 1024 ought to be enough for now. lol. +global HEAD_PTR: I32 = 1024_I32; + +// Every struct has a header of an I32 as a refcount. + +// Allocate a new item. We do not deallocate anything yet. +// lol. +function allocateItem(objSize: I32, align: I32): I32 = ( + if align < 4_I32 then std.abort("invalid alignment"); + + // Include the refcount header. + let actualSize = 4_I32 + objSize; + + // Let's see whether we can fit the refcount into the align bits. + // I happen to know that everything will always be at least 4 bytes aligned. + let alignedPtr = std.alignUp(HEAD_PTR, align); + let actualObjPtr = if (alignedPtr - HEAD_PTR) > align then ( + alignedPtr - 4_I32 + ) else ( + // Take up the next spot. + alignedPtr + align - 4_I32 + ); + + let newHeadPtr = actualObjPtr + actualSize; + + if newHeadPtr > __memory_size() then ( + // 16 pages, very arbitrary. + let result = __memory_grow(16_I32); + // If allocation failed we get -1. We don't have negative numbers yet, lol. + if result > 4294967295_I32 then ( + std.abort("failed to grow memory"); + ); + ); + + HEAD_PTR = newHeadPtr; + + actualObjPtr +); + +function deallocateItem(ptr: I32, objSize: I32) = ( + std.println("uwu deawwocate :3"); +); + +// Port of https://github.com/CCareaga/heap_allocator +// +// MIT License +// +// Copyright (c) 2017 Chris Careaga +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +global HEAP_START: I32 = 2048_I32; +// heap size = start+end+bin_t*BIN_COUNT +// 4+ 4+ 4* 9 = 8+36=42 (round to 64) +global HEAP_REGION_START: I32 = 2112_I32; + +// typedef struct node_t { +// uint hole; +// uint size; +// struct node_t* next; +// struct node_t* prev; +// } node_t; + +// typedef struct { +// node_t *header; +// } footer_t; + +// typedef struct { +// node_t* head; +// } bin_t; + +global SIZEOF_NODE: I32 = 16_I32; +global SIZEOF_FOOTER: I32 = 4_I32; + +function initHeap() = ( + let heap_init_size = 65536_I32 - HEAP_REGION_START; + + __i32_store(HEAP_REGION_START, 1_I32); // START.hole = + __i32_store(HEAP_REGION_START + 4_I32, heap_init_size - SIZEOF_NODE - SIZEOF_FOOTER); // START.size = +); + +function createFoot(head_node: I32) = ( + let foot = getFoot(head_node); + __i32_store(foot, head_node); // foot.header = head_node +); + +function getFoot(node: I32): I32 = ( + let node_size = __i32_load(node + 4_I32); // node.size + node + SIZEOF_NODE + node_size +); + +function addNode(bin: I32, node: I32) = ; + +function test() =; \ No newline at end of file diff --git a/std/rt/rt.mod.nil b/std/rt/rt.mod.nil new file mode 100644 index 0000000..485398b --- /dev/null +++ b/std/rt/rt.mod.nil @@ -0,0 +1 @@ +mod alloc; diff --git a/std/std.mod.nil b/std/std.mod.nil index 8fed608..a7980f1 100644 --- a/std/std.mod.nil +++ b/std/std.mod.nil @@ -79,5 +79,5 @@ function abort(message: String) = ( ); function main() = ( - std.rt.allocateItem(100000000_I32, 8_I32); + std.rt.alloc.test(); ); \ No newline at end of file diff --git a/ui-tests/ui/type_alias.nil b/ui-tests/ui/type_alias.nil new file mode 100644 index 0000000..755e49f --- /dev/null +++ b/ui-tests/ui/type_alias.nil @@ -0,0 +1,7 @@ +//@check-pass + +type A = (Int, Int); + +function main() = ( + let a: A = (0, 0); +); \ No newline at end of file diff --git a/ui-tests/ui/type_assignments.nil b/ui-tests/ui/type_assignments.nil index 623c8fb..8238428 100644 --- a/ui-tests/ui/type_assignments.nil +++ b/ui-tests/ui/type_assignments.nil @@ -1,6 +1,6 @@ //@check-pass -type CustomType = {}; +type CustomType = struct {}; function main() = (); diff --git a/vscode/syntaxes/riverdelta.tmLanguage.json b/vscode/syntaxes/riverdelta.tmLanguage.json index c25d1be..d62e714 100644 --- a/vscode/syntaxes/riverdelta.tmLanguage.json +++ b/vscode/syntaxes/riverdelta.tmLanguage.json @@ -17,7 +17,7 @@ "patterns": [ { "name": "keyword.control.riverdelta", - "match": "\\b(function|let|if|then|else|type|loop|break|import|extern|mod|global)\\b" + "match": "\\b(function|let|if|then|else|type|loop|break|import|extern|mod|global|struct)\\b" } ] },