From f05e5520f3c48feea6a71d0ea1c4f0bcf9fa6ff1 Mon Sep 17 00:00:00 2001
From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com>
Date: Thu, 3 Aug 2023 13:22:35 +0200
Subject: [PATCH] allocators and type aliases
---
.eslintrc.cjs | 1 +
src/ast.ts | 36 +++++--
src/codegen.ts | 4 +-
src/context.ts | 6 +-
src/index.ts | 13 ++-
src/lexer.ts | 2 +
src/parser.ts | 53 ++++++----
src/printer.ts | 20 ++--
src/typeck.ts | 99 +++++++++++-------
std/rt.nil | 44 --------
std/rt/alloc.nil | 112 +++++++++++++++++++++
std/rt/rt.mod.nil | 1 +
std/std.mod.nil | 2 +-
ui-tests/ui/type_alias.nil | 7 ++
ui-tests/ui/type_assignments.nil | 2 +-
vscode/syntaxes/riverdelta.tmLanguage.json | 2 +-
16 files changed, 283 insertions(+), 121 deletions(-)
delete mode 100644 std/rt.nil
create mode 100644 std/rt/alloc.nil
create mode 100644 std/rt/rt.mod.nil
create mode 100644 ui-tests/ui/type_alias.nil
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"
}
]
},