diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 4083537..3c1861e 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -50,11 +50,15 @@ jobs: arimo-bootstrap/target key: linux-v1-${{ hashFiles('arimo-bootstrap/Cargo.lock') }} + # ── Stage 0: bootstrap compiler ────────────────────────────────────── + - name: Build Stage 0 (bootstrap) working-directory: arimo-bootstrap run: cargo build --release --target x86_64-unknown-linux-gnu - - name: Build Stage 1 (self-hosted Linux) + # ── Stage 1: S1 → S2 (bootstrap compiles arc source) ───────────────── + + - name: Build S2 (bootstrap → self-hosted) working-directory: arimo run: | ARC0="../arimo-bootstrap/target/x86_64-unknown-linux-gnu/release/arc" @@ -62,16 +66,105 @@ jobs: cp -r stdlib "../arimo-bootstrap/target/x86_64-unknown-linux-gnu/release/" "$ARC0" build --target linux chmod +x arc - cp arc ../arc-linux + cp arc /tmp/arc.s2 + echo "S2 size: $(stat -c%s /tmp/arc.s2) bytes" + + # ── Stage 2: S2 → S3 (self-hosted compiles itself) ─────────────────── + + - name: Build S3 (self-hosted → self-hosted) + working-directory: arimo + run: | + rm -f arc + /tmp/arc.s2 build --target linux + chmod +x arc + cp arc /tmp/arc.s3 + echo "S3 size: $(stat -c%s /tmp/arc.s3) bytes" - - name: Run test suite (smoke test) + # ── Stage 3: S3 → S4 (verify determinism) ──────────────────────────── + + - name: Build S4 (determinism check) + working-directory: arimo + run: | + rm -f arc + /tmp/arc.s3 build --target linux + chmod +x arc + cp arc /tmp/arc.s4 + echo "S4 size: $(stat -c%s /tmp/arc.s4) bytes" + + - name: Verify S3 == S4 (determinism) + run: | + diff /tmp/arc.s3 /tmp/arc.s4 && echo "✅ S3 == S4 DETERMINISTIC" || { + echo "❌ FAIL: S3 ≠ S4 — non-deterministic output" + echo "S3 SHA256: $(sha256sum /tmp/arc.s3 | awk '{print $1}')" + echo "S4 SHA256: $(sha256sum /tmp/arc.s4 | awk '{print $1}')" + exit 1 + } + + # ── Verify S3 is static + self-contained ───────────────────────────── + + - name: Verify static binary (no libc dependency) + run: | + RELEASE_BIN=/tmp/arc.s3 + echo "=== file ===" + file "$RELEASE_BIN" + + echo "=== ldd ===" + if ldd "$RELEASE_BIN" 2>&1 | grep -q "not a dynamic executable"; then + echo "✅ ldd: not a dynamic executable" + elif ldd "$RELEASE_BIN" 2>&1 | grep -q "statically linked"; then + echo "✅ ldd: statically linked" + else + LDD_OUT=$(ldd "$RELEASE_BIN" 2>&1) + if echo "$LDD_OUT" | grep -qE "libc\.|libpthread|libm\.|libdl\.|ld-linux"; then + echo "❌ FAIL: binary has dynamic library dependencies" + echo "$LDD_OUT" + exit 1 + fi + echo "ldd output: $LDD_OUT" + fi + + echo "=== readelf -l (check for INTERP segment) ===" + if readelf -l "$RELEASE_BIN" 2>/dev/null | grep -q "INTERP"; then + echo "❌ FAIL: binary has INTERP segment (dynamic linker required)" + readelf -l "$RELEASE_BIN" | grep "INTERP\|LOAD\|DYNAMIC" + exit 1 + else + echo "✅ No INTERP segment — static binary" + fi + + echo "=== readelf -d (check for dynamic section) ===" + if readelf -d "$RELEASE_BIN" 2>/dev/null | grep -q "NEEDED"; then + echo "❌ FAIL: binary has NEEDED entries (shared library dependencies)" + readelf -d "$RELEASE_BIN" + exit 1 + else + echo "✅ No NEEDED entries — self-contained" + fi + + echo "" + echo "=== Static verification PASSED ===" + echo "Release binary: $(stat -c%s "$RELEASE_BIN") bytes" + echo "SHA256: $(sha256sum "$RELEASE_BIN" | awk '{print $1}')" + + # ── Smoke test ─────────────────────────────────────────────────────── + + - name: Smoke test (compile hello.arm) working-directory: arimo/test run: | - ARC="/home/runner/work/arimo/arimo/arimo/arc" + RELEASE_BIN=/tmp/arc.s3 cp -r ../stdlib /tmp/ - "$ARC" hello.arm --target linux 2>&1 | tail -1 - chmod +x hello && ./hello - echo "Smoke test passed" + "$RELEASE_BIN" hello.arm --target linux 2>&1 + if [ -f hello ]; then + chmod +x hello + OUTPUT=$(./hello 2>&1) + echo "hello output: $OUTPUT" + echo "✅ Smoke test passed" + else + echo "❌ Smoke test failed: hello binary not produced" + exit 1 + fi + + # ── Package ────────────────────────────────────────────────────────── - name: Install packaging tools run: | @@ -83,9 +176,10 @@ jobs: run: | TAG="${{ github.event.inputs.tag || github.ref_name }}" VERSION="${TAG#v}" + RELEASE_BIN=/tmp/arc.s3 mkdir -p dist/arimo-linux-x64/stdlib - cp arc dist/arimo-linux-x64/ + cp "$RELEASE_BIN" dist/arimo-linux-x64/arc cp -r stdlib/. dist/arimo-linux-x64/stdlib/ cat > dist/arimo-linux-x64/install.sh << 'INSTALL' @@ -109,7 +203,7 @@ jobs: mkdir -p pkg-root/usr/local/bin mkdir -p pkg-root/usr/local/lib/arimo/stdlib - cp arc pkg-root/usr/local/bin/arc + cp "$RELEASE_BIN" pkg-root/usr/local/bin/arc cp -r stdlib/. pkg-root/usr/local/lib/arimo/stdlib/ fpm -s dir -t deb -n arimo -v "${VERSION}" --architecture amd64 \ @@ -126,6 +220,44 @@ jobs: --license "AGPL-3.0" --prefix / -C pkg-root \ -p "dist/arimo-${TAG}-linux-x64.rpm" . + echo "=== Packaged files ===" + ls -la dist/ + + # ── Verify packaged binary is static ───────────────────────────────── + + - name: Verify packaged tar.gz contains static binary + run: | + TAG="${{ github.event.inputs.tag || github.ref_name }}" + TARBALL="arimo/dist/arimo-${TAG}-linux-x64.tar.gz" + + mkdir -p /tmp/verify-release + tar xzf "$TARBALL" -C /tmp/verify-release + + PACKAGED_BIN=/tmp/verify-release/arimo-linux-x64/arc + + echo "=== Verifying packaged binary ===" + file "$PACKAGED_BIN" + + if file "$PACKAGED_BIN" | grep -q "dynamically linked"; then + echo "❌ FAIL: Packaged binary is dynamically linked!" + exit 1 + fi + + if readelf -l "$PACKAGED_BIN" 2>/dev/null | grep -q "INTERP"; then + echo "❌ FAIL: Packaged binary has INTERP segment!" + exit 1 + fi + + ldd "$PACKAGED_BIN" 2>&1 | grep -q "not a dynamic executable" || { + echo "❌ FAIL: Packaged binary has dynamic dependencies!" + exit 1 + } + + echo "✅ Packaged binary is static and self-contained" + sha256sum "$PACKAGED_BIN" + + # ── Upload ─────────────────────────────────────────────────────────── + - name: Upload to GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 6f2d183..7bf6f42 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -97,17 +97,45 @@ jobs: run: | cp arc /tmp/arc.s1 echo "=== S1 binary: $(stat -c%s /tmp/arc.s1) bytes ===" + file /tmp/arc.s1 echo "=== S1→S2 build (bootstrap-built → self-hosted) ===" /tmp/arc.s1 build --target linux 2>&1 cp arc /tmp/arc.s2 echo "=== S2 binary: $(stat -c%s /tmp/arc.s2) bytes ===" + file /tmp/arc.s2 echo "=== S2→S3 build ===" /tmp/arc.s2 build --target linux 2>&1 cp arc /tmp/arc.s3 echo "=== S3 binary: $(stat -c%s /tmp/arc.s3) bytes ===" + file /tmp/arc.s3 diff /tmp/arc.s2 /tmp/arc.s3 && echo "S2==S3 DETERMINISTIC" || echo "S2≠S3 (bootstrap→self-hosted — expected)" echo "=== S3→S4 build (second self-hosted) ===" /tmp/arc.s3 build --target linux 2>&1 cp arc /tmp/arc.s4 echo "=== S4 binary: $(stat -c%s /tmp/arc.s4) bytes ===" + file /tmp/arc.s4 diff /tmp/arc.s3 /tmp/arc.s4 && echo "S3==S4 DETERMINISTIC ✓" || { echo "FAIL: S3≠S4 non-deterministic"; exit 1; } + + - name: Verify S3 is static (self-hosted = no libc dependency) + run: | + S3=/tmp/arc.s3 + echo "=== file ===" + file "$S3" + echo "=== ldd ===" + ldd "$S3" 2>&1 || true + echo "=== readelf -l ===" + readelf -l "$S3" 2>/dev/null | grep -E "INTERP|LOAD|DYNAMIC" || true + + echo "" + if file "$S3" | grep -q "dynamically linked"; then + echo "WARN: S3 is dynamically linked (bootstrap codegen via gcc)" + echo "This is expected for S1/S2 but self-hosted S3+ should be static." + elif file "$S3" | grep -q "statically linked"; then + echo "✅ S3 is statically linked — self-hosted native binary" + fi + + if readelf -l "$S3" 2>/dev/null | grep -q "INTERP"; then + echo "WARN: S3 has INTERP segment (dynamic linker)" + else + echo "✅ S3 has no INTERP segment — no dynamic linker dependency" + fi diff --git a/arimo/compiler/backend/ELFWriter.arm b/arimo/compiler/backend/ELFWriter.arm index 96e9f73..2e3a247 100644 --- a/arimo/compiler/backend/ELFWriter.arm +++ b/arimo/compiler/backend/ELFWriter.arm @@ -27,14 +27,18 @@ extern "C" { } public class ELFWriter { - private enc : X64Encoder; - private strNames : List; - private strConts : List; + private enc : X64Encoder; + private strNames : List; + private strConts : List; + private bssLabels : List; + private bssSizes : List; public constructor(enc: X64Encoder) { - this.enc = enc; - this.strNames = List(); - this.strConts = List(); + this.enc = enc; + this.strNames = List(); + this.strConts = List(); + this.bssLabels = List(); + this.bssSizes = List(); } public addString(name: String, content: String) { @@ -42,6 +46,11 @@ public class ELFWriter { this.strConts.append(content); } + public addBss(name: String, size: Integer) { + this.bssLabels.append(name); + this.bssSizes.append(size); + } + private alignTo(v: Integer, a: Integer) : Integer { Integer rem = v % a; if (rem == 0) { return v; } @@ -114,8 +123,23 @@ public class ELFWriter { strCursor = strCursor + c.length() + 1; si = si + 1; } - Integer dataSize = strCursor; - if (dataSize == 0) { dataSize = 8; } + // Align data cursor to 8 bytes for BSS globals + Integer dataCursor = strCursor; + if (dataCursor == 0) { dataCursor = 8; } + Integer dataAligned = this.alignTo(dataCursor, 8); + + // Precompute BSS offsets (relative to start of .data segment) + List bssOffsets = List(); + Integer bssCursor = dataAligned - strCursor; // offset from string end + Integer bi = 0; + while (bi < this.bssLabels.length()) { + bssOffsets.append(dataAligned + bi * 8); // each BSS slot 8-byte aligned + bi = bi + 1; + } + Integer bssTotal = this.bssLabels.length() * 8; + Integer dataFileSize = dataAligned; // what goes in the file + Integer dataMemSize = dataAligned + bssTotal; // virtual size includes BSS + if (dataMemSize == 0) { dataFileSize = 8; dataMemSize = 8; } Integer textPadded = this.alignTo(codeSize, pageSize); Integer dataFileOff = textFileOff + textPadded; @@ -125,7 +149,7 @@ public class ELFWriter { // Apply intra-.text jump fixups this.enc.applyFixups(); - // Apply RIP fixups (string references: LEA [rip+str_label]) + // Apply RIP fixups (string references and BSS globals: LEA [rip+label]) Integer ripN = this.enc.ripFixOffs.length(); Integer bufLen = this.enc.buf.length(); Integer ri = 0; @@ -133,19 +157,29 @@ public class ELFWriter { Integer ripOff = this.enc.ripFixOffs.get(ri) as Integer; String ripLbl = this.enc.ripFixLbls.get(ri) as String; ri = ri + 1; - Integer strOff = -1; + Integer tgtOff = -1; + // Search string labels Integer sj = 0; while (sj < S) { String nm = this.strNames.get(sj) as String; - if (nm == ripLbl) { strOff = strOffsets.get(sj) as Integer; } + if (nm == ripLbl) { tgtOff = strOffsets.get(sj) as Integer; } sj = sj + 1; } - if (strOff < 0) { + // Search BSS labels + if (tgtOff < 0) { + Integer bk = 0; + while (bk < this.bssLabels.length()) { + String bnm = this.bssLabels.get(bk) as String; + if (bnm == ripLbl) { tgtOff = bssOffsets.get(bk) as Integer; } + bk = bk + 1; + } + } + if (tgtOff < 0) { IO.println("arc: [warn] ELF: missing label ${ripLbl}"); } else if (ripOff + 3 >= bufLen) { IO.println("arc: [warn] ELF: bounds ripOff=${ripOff} bufLen=${bufLen}"); } else { - Integer targetVA = dataVA + strOff; + Integer targetVA = dataVA + tgtOff; Integer ripRVA = textVA + ripOff + 4; Integer rel32 = targetVA - ripRVA; List buf = this.enc.buf; @@ -197,8 +231,8 @@ public class ELFWriter { this.w64(file, dataFileOff); // p_offset this.w64(file, dataVA); // p_vaddr this.w64(file, dataVA); // p_paddr - this.w64(file, dataSize); // p_filesz - this.w64(file, dataSize); // p_memsz + this.w64(file, dataFileSize); // p_filesz (string data, no BSS in file) + this.w64(file, dataMemSize); // p_memsz (includes BSS — kernel zero-fills) this.w64(file, pageSize); // p_align // Pad headers to textFileOff (0x1000) @@ -220,7 +254,11 @@ public class ELFWriter { this.wStr(file, cs); sq = sq + 1; } - if (S == 0) { this.wPad(file, 8); } + // Pad to alignment boundary (8-byte for BSS globals) + if (dataAligned > strCursor) { + this.wPad(file, dataAligned - strCursor); + } + if (dataFileSize <= 8 && S == 0) { this.wPad(file, 8); } return file; } diff --git a/arimo/compiler/backend/RegAlloc.arm b/arimo/compiler/backend/RegAlloc.arm index 402a439..713a62e 100644 --- a/arimo/compiler/backend/RegAlloc.arm +++ b/arimo/compiler/backend/RegAlloc.arm @@ -27,18 +27,20 @@ import arimo.compiler.backend.IRValue; import arimo.compiler.backend.IRValueKind; public class LiveInterval { - public name : String; - public start : Integer; - public end : Integer; - public reg : Integer; - public spill : Integer; + public name : String; + public start : Integer; + public end : Integer; + public reg : Integer; + public spill : Integer; + public spilledFrom : Integer; public constructor(name: String, start: Integer) { - this.name = name; - this.start = start; - this.end = start; - this.reg = -1; - this.spill = -1; + this.name = name; + this.start = start; + this.end = start; + this.reg = -1; + this.spill = -1; + this.spilledFrom = -1; } public isSpilled() : Boolean { return this.spill >= 0; } @@ -50,12 +52,18 @@ public class RegAlloc { private freePool : List; private active : List; private spillCount : Integer; + public spillSaves : List; // spill slot index + public spillRegs : List; // physical reg holding value + public spillPoss : List; // instruction position where spill occurred public constructor() { this.ivals = List(); this.freePool = List(); this.active = List(); this.spillCount = 0; + this.spillSaves = List(); + this.spillRegs = List(); + this.spillPoss = List(); } public totalSpillBytes() : Integer { return this.spillCount * 8; } @@ -268,6 +276,11 @@ public class RegAlloc { Integer candIdx = this.active.get(spillPos) as Integer; LiveInterval cand = this.ivals.get(candIdx) as LiveInterval; if (cand.end > iv.end) { + cand.spilledFrom = cand.reg; + // Record spill: save cand.reg to spill slot at iv.start + this.spillSaves.append(cand.spill); + this.spillRegs.append(cand.reg); + this.spillPoss.append(iv.start); iv.reg = cand.reg; cand.reg = -1; cand.spill = this.allocSpill(); diff --git a/arimo/compiler/backend/X64Encoder.arm b/arimo/compiler/backend/X64Encoder.arm index ef36f86..2ee9747 100644 --- a/arimo/compiler/backend/X64Encoder.arm +++ b/arimo/compiler/backend/X64Encoder.arm @@ -45,6 +45,7 @@ public class X64Encoder { public jmpFixLbls : List; public ripFixOffs : List; public ripFixLbls : List; + public hasError : Boolean; public constructor() { this.buf = List(); @@ -54,6 +55,7 @@ public class X64Encoder { this.jmpFixLbls = List(); this.ripFixOffs = List(); this.ripFixLbls = List(); + this.hasError = false; } public size() : Integer { @@ -143,6 +145,12 @@ public class X64Encoder { Integer off = this.jmpFixOffs.get(i) as Integer; String lbl = this.jmpFixLbls.get(i) as String; Integer target = this.labelOffset(lbl); + if (target < 0) { + IO.println("arc: [FAIL] unresolved label: ${lbl} — fixup at buffer offset ${off}"); + this.hasError = true; + i = i + 1; + continue; + } Integer rel32 = target - (off + 4); this.buf.set(off, rel32 & 0xFF); this.buf.set(off + 1, (rel32 >> 8) & 0xFF); diff --git a/arimo/compiler/codegen/CodeGen.arm b/arimo/compiler/codegen/CodeGen.arm index 4fb0ed5..28bfb13 100644 --- a/arimo/compiler/codegen/CodeGen.arm +++ b/arimo/compiler/codegen/CodeGen.arm @@ -1,2266 +1,2266 @@ -/* -Arimo Lang Compiler - A modern programming language and compiler -Copyright (C) 2026 Egecan Akıncıoğlu - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package arimo.compiler.codegen; - -import arimo.compiler.typechecker.TypeChecker; -import arimo.compiler.ast.ArimoModule; -import arimo.compiler.ast.types.TypeKind; -import arimo.compiler.ast.types.AstType; -import arimo.compiler.ast.expr.ExprKind; -import arimo.compiler.ast.expr.BinaryOp; -import arimo.compiler.ast.expr.UnaryOp; -import arimo.compiler.ast.expr.StrPart; -import arimo.compiler.ast.expr.MatchArm; -import arimo.compiler.ast.expr.MatchPatternKind; -import arimo.compiler.ast.expr.MatchPattern; -import arimo.compiler.ast.expr.Expr; -import arimo.compiler.ast.stmt.StmtKind; -import arimo.compiler.ast.stmt.Stmt; -import arimo.compiler.ast.stmt.CatchClause; -import arimo.compiler.ast.stmt.SwitchCase; -import arimo.compiler.ast.stmt.ElseIfBranch; -import arimo.compiler.ast.decl.FieldDecl; -import arimo.compiler.ast.decl.MethodDecl; -import arimo.compiler.ast.decl.ConstructorDecl; -import arimo.compiler.ast.decl.Param; -import arimo.compiler.ast.decl.EnumVariant; -import arimo.compiler.ast.decl.ExternFnDecl; -import arimo.compiler.ast.item.ItemKind; -import arimo.compiler.ast.item.Item; -import arimo.compiler.ast.item.ClassDecl; -import arimo.compiler.ast.item.EnumDecl; -import arimo.compiler.ast.item.StructDecl; -import arimo.compiler.ast.item.ExceptionDecl; -import arimo.compiler.ast.item.ExternDecl; -import arimo.compiler.ast.item.ExtensionDecl; - -public class CodeGen { - private tc : TypeChecker; - private header : String; - private strConsts : String; - private rtFuncs : String; - private body : String; - private strIdx : Integer; - private counter : Integer; - private labelCnt : Integer; - private curFn : String; - private selfType : String; - private retTyStr : String; - private varNames : List; - private varRegs : List; - private varLLTys : List; - private varElemClass : List; - private scopeMarks : List; - private loopExits : List; - private loopConds : List; - private entryClass : String; - - public constructor(tc: TypeChecker) { - this.tc = tc; - this.header = ""; - this.strConsts = ""; - this.rtFuncs = ""; - this.body = ""; - this.strIdx = 0; - this.counter = 0; - this.labelCnt = 0; - this.curFn = ""; - this.selfType = ""; - this.retTyStr = "void"; - this.varNames = List(); - this.varRegs = List(); - this.varLLTys = List(); - this.varElemClass = List(); - this.scopeMarks = List(); - this.loopExits = List(); - this.loopConds = List(); - this.entryClass = ""; - } - - private nextReg() : String { - Integer n = this.counter; - this.counter++; - return "%t${n}"; - } - - private nextLabel() : String { - Integer n = this.labelCnt; - this.labelCnt++; - return "L${n}"; - } - - private emit(s: String) : Void { - this.curFn = this.curFn.concat(" ").concat(s).concat("\n"); - } - - private emitBr(label: String) : Void { - this.curFn = this.curFn.concat(" br label %").concat(label).concat("\n"); - } - - private emitLabel(label: String) : Void { - this.curFn = this.curFn.concat(label).concat(":\n"); - } - - private flushFn(sig: String) : Void { - this.body = this.body.concat(sig).concat(" {\nentry:\n").concat(this.curFn).concat("}\n\n"); - this.curFn = ""; - this.counter = 0; - this.varNames = List(); - this.varRegs = List(); - this.varLLTys = List(); - this.varElemClass = List(); - this.scopeMarks = List(); - this.loopExits = List(); - this.loopConds = List(); - } - - private addHeader(s: String) : Void { - this.header = this.header.concat(s).concat("\n"); - } - - private addRuntime(s: String) : Void { - this.rtFuncs = this.rtFuncs.concat(s).concat("\n"); - } - - public llvmTy(ty: AstType) : String { - if (ty == null) { return "void"; } - Integer k = ty.kind; - if (k == TypeKind.INTEGER) { return "i64"; } - if (k == TypeKind.FLOAT) { return "double"; } - if (k == TypeKind.BOOLEAN) { return "i64"; } - if (k == TypeKind.STR) { return "i8*"; } - if (k == TypeKind.VOID) { return "void"; } - if (k == TypeKind.NORETURN) { return "void"; } - if (k == TypeKind.U8) { return "i8"; } - if (k == TypeKind.U16) { return "i16"; } - if (k == TypeKind.U32) { return "i32"; } - if (k == TypeKind.U64) { return "i64"; } - if (k == TypeKind.I8) { return "i8"; } - if (k == TypeKind.I16) { return "i16"; } - if (k == TypeKind.I32) { return "i32"; } - if (k == TypeKind.I64) { return "i64"; } - if (k == TypeKind.CHAR) { return "i64"; } - if (k == TypeKind.NAMED) { return "%${ty.name}*"; } - if (k == TypeKind.NULLABLE) { return this.llvmTy(ty.inner); } - if (k == TypeKind.LIST) { return "%__List*"; } - if (k == TypeKind.HASHMAP || k == TypeKind.MAP || k == TypeKind.TREEMAP) { return "%__HashMap*"; } - if (k == TypeKind.PAIR) { return "%__Pair*"; } - if (k == TypeKind.RAWPTR) { return "i8*"; } - if (k == TypeKind.FNPTR) { return "i8*"; } - if (k == TypeKind.ARRAY) { return "i8*"; } - if (k == TypeKind.SLICE) { return "i8*"; } - if (k == TypeKind.GENERIC) { return "%${ty.name}*"; } - return "i8*"; - } - - private isVoidTy(ty: AstType) : Boolean { - if (ty == null) { return true; } - return ty.kind == TypeKind.VOID || ty.kind == TypeKind.NORETURN; - } - - private escapeStr(s: String) : String { - String r = ""; - Integer i = 0; - while (i < s.length()) { - String c = s.substring(i, i + 1); - if (c == "\n") { r = r.concat("\\0A"); } - else if (c == "\t") { r = r.concat("\\09"); } - else if (c == "\r") { r = r.concat("\\0D"); } - else if (c == "\"") { r = r.concat("\\22"); } - else if (c == "\\") { r = r.concat("\\5C"); } - else { r = r.concat(c); } - i++; - } - return r; - } - - private emitStrConst(s: String) : String { - Integer idx = this.strIdx; - this.strIdx++; - Integer len = s.length() + 1; - String esc = this.escapeStr(s); - String prefix = "@.str.${idx} = private constant [${len} x i8] c\""; - this.strConsts = this.strConsts.concat(prefix).concat(esc).concat("\\00\"\n"); - return "@.str.${idx}"; - } - - private strGEP(gname: String, len: Integer) : String { - String r = this.nextReg(); - this.emit("${r} = getelementptr [${len} x i8], [${len} x i8]* ${gname}, i32 0, i32 0"); - return r; - } - - private emitStrLit(s: String) : String { - String g = this.emitStrConst(s); - return this.strGEP(g, s.length() + 1); - } - - private pushScope() : Void { - this.scopeMarks.append(this.varNames.length()); - } - - private popScope() : Void { - Integer last = this.scopeMarks.length() - 1; - if (last < 0) { return; } - Integer mark = this.scopeMarks.get(last); - this.scopeMarks.removeAt(last); - while (this.varNames.length() > mark) { - Integer n = this.varNames.length() - 1; - this.varNames.removeAt(n); - this.varRegs.removeAt(n); - this.varLLTys.removeAt(n); - } - } - - private defineVar(name: String, llt: String) : String { - Integer n = this.counter; - this.counter++; - String reg = "%v${n}"; - this.emit("${reg} = alloca ${llt}"); - this.varNames.append(name); - this.varRegs.append(reg); - this.varLLTys.append(llt); - this.varElemClass.append(""); - return reg; - } - - private defineVarWithElem(name: String, llt: String, elemCls: String) : String { - Integer n = this.counter; - this.counter++; - String reg = "%v${n}"; - this.emit("${reg} = alloca ${llt}"); - this.varNames.append(name); - this.varRegs.append(reg); - this.varLLTys.append(llt); - this.varElemClass.append(elemCls); - return reg; - } - - private lookupElemClass(name: String) : String { - Integer i = this.varNames.length() - 1; - while (i >= 0) { - if (this.varNames.get(i) == name) { return this.varElemClass.get(i); } - i--; - } - return ""; - } - - private lookupReg(name: String) : String { - Integer i = this.varNames.length() - 1; - while (i >= 0) { - if (this.varNames.get(i) == name) { return this.varRegs.get(i); } - i--; - } - return ""; - } - - private lookupLLTy(name: String) : String { - Integer i = this.varNames.length() - 1; - while (i >= 0) { - if (this.varNames.get(i) == name) { return this.varLLTys.get(i); } - i--; - } - return "i8*"; - } - - private nonStaticFields(className: String) : List { - List all = this.tc.allFieldsOf(className); - Integer allLen = all.length(); - List res = List(); - Integer i = 0; - while (i < allLen) { - FieldDecl fd = all.get(i) as FieldDecl; - if (!fd.isStatic) { res.append(fd); } - i++; - } - return res; - } - - private structByteSize(className: String) : Integer { - List nsList = this.nonStaticFields(className); - Integer sz = (1 + nsList.length()) * 8; - return sz; - } - - private fieldSlot(className: String, fieldName: String) : Integer { - List fs = this.nonStaticFields(className); - Integer i = 0; - while (i < fs.length()) { - FieldDecl fd = fs.get(i) as FieldDecl; - if (fd.name == fieldName) { return i + 1; } - i++; - } - return -1; - } - - private fieldLLTy(className: String, fieldName: String) : String { - List fs = this.nonStaticFields(className); - Integer i = 0; - while (i < fs.length()) { - FieldDecl fd = fs.get(i) as FieldDecl; - if (fd.name == fieldName) { return this.llvmTy(fd.ty); } - i++; - } - return "i8*"; - } - - private toBool(val: String) : String { - String r = this.nextReg(); - this.emit("${r} = trunc i64 ${val} to i1"); - return r; - } - - private fromBool(val: String) : String { - String r = this.nextReg(); - this.emit("${r} = zext i1 ${val} to i64"); - return r; - } - - public emitExpr(e: Expr) : String { - Integer k = e.kind; - - if (k == ExprKind.NULL_LIT) { return "null"; } - if (k == ExprKind.INT_LIT) { return "${e.intVal}"; } - if (k == ExprKind.CHAR_LIT) { return "${e.intVal}"; } - if (k == ExprKind.FLOAT_LIT) { - String fstr = "${e.floatVal}"; - if (!fstr.contains(".") && !fstr.contains("e")) { fstr = fstr.concat(".0"); } - return fstr; - } - if (k == ExprKind.BOOL_LIT) { if (e.boolVal) { return "1"; } return "0"; } - if (k == ExprKind.STR_LIT) { return this.emitStrLit(e.strVal); } - if (k == ExprKind.STR_INTERP) { return this.emitStrInterp(e); } - if (k == ExprKind.THIS) { return "%self"; } - - if (k == ExprKind.SUPER) { - String r = this.nextReg(); - this.emit("${r} = bitcast %${this.selfType}* %self to i8*"); - return r; - } - - if (k == ExprKind.IDENT) { - return this.emitLoad(e.strVal); - } - - if (k == ExprKind.FIELD) { - return this.emitFieldLoad(e); - } - - if (k == ExprKind.NULL_SAFE) { - return this.emitNullSafe(e); - } - - if (k == ExprKind.METHOD) { - return this.emitMethodCall(e); - } - - if (k == ExprKind.STATIC_CALL) { - return this.emitStaticCall(e); - } - - if (k == ExprKind.CTOR) { - return this.emitCtorCall(e); - } - - if (k == ExprKind.BINOP) { - return this.emitBinOp(e); - } - - if (k == ExprKind.UNARY) { - return this.emitUnaryOp(e); - } - - if (k == ExprKind.CAST) { - return this.emitCast(e); - } - - if (k == ExprKind.TERNARY) { - return this.emitTernary(e); - } - - if (k == ExprKind.INDEX) { - return this.emitIndex(e); - } - - if (k == ExprKind.NULL_COALESCE) { - return this.emitNullCoalesce(e); - } - - if (k == ExprKind.LAMBDA) { - return "null"; - } - - if (k == ExprKind.AWAIT) { - return this.emitExpr(e.operand); - } - - if (k == ExprKind.MATCH) { - return this.emitMatchExpr(e); - } - - return "0"; - } - - private emitLoad(name: String) : String { - String reg = this.lookupReg(name); - if (reg == "") { - Integer ci = 0; - Boolean found = false; - while (ci < this.tc.constNames.length()) { - if (this.tc.constNames.get(ci) == name) { found = true; } - if (!found) { ci++; } - if (found) { ci = this.tc.constNames.length(); } - } - if (found) { - AstType cty = this.tc.constTypes.get(ci); - String llt2 = this.llvmTy(cty); - String r2 = this.nextReg(); - if (llt2 == "i8*") { - this.emit("${r2} = load i8*, i8** @${name}.ptr"); - } else { - this.emit("${r2} = load ${llt2}, ${llt2}* @${name}"); - } - return r2; - } - return "0"; - } - String llt = this.lookupLLTy(name); - if (llt == "void") { return "0"; } - String r = this.nextReg(); - this.emit("${r} = load ${llt}, ${llt}* ${reg}"); - return r; - } - - private emitFieldLoad(e: Expr) : String { - String objVal = this.emitExpr(e.object); - String cls = this.resolveObjClassName(e.object); - if (cls == "") { - String on = e.object.strVal; - if (on.length() > 0) { - List sfs = this.tc.allFieldsOf(on); - Integer sfi = 0; - while (sfi < sfs.length()) { - FieldDecl sf = sfs.get(sfi); - if (sf.isStatic && sf.name == e.field && sf.init != null) { - return this.emitExpr(sf.init); - } - sfi++; - } - } - return "0"; - } - Integer slot = this.fieldSlot(cls, e.field); - if (slot < 0) { - String r = this.nextReg(); - this.emit("${r} = load i64, i64* null"); - return r; - } - String fllty = this.fieldLLTy(cls, e.field); - String ptr = this.nextReg(); - this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); - String r = this.nextReg(); - this.emit("${r} = load ${fllty}, ${fllty}* ${ptr}"); - return r; - } - - private resolveObjClassName(e: Expr) : String { - if (e == null) { return this.selfType; } - if (e.kind == ExprKind.THIS) { return this.selfType; } - if (e.kind == ExprKind.IDENT) { - String llt = this.lookupLLTy(e.strVal); - if (llt == "%__List*") { return "__List"; } - if (llt == "%__HashMap*") { return "__HashMap"; } - if (llt.startsWith("%") && llt.endsWith("*") && !llt.startsWith("%__")) { - return llt.substring(1, llt.length() - 1); - } - } - if (e.kind == ExprKind.CTOR) { return e.class_; } - if (e.kind == ExprKind.FIELD) { - String cls = this.resolveObjClassName(e.object); - String fllty = this.fieldLLTy(cls, e.field); - if (fllty == "%__List*") { return "__List"; } - if (fllty == "%__HashMap*") { return "__HashMap"; } - if (fllty.startsWith("%") && fllty.endsWith("*") && !fllty.startsWith("%__")) { - return fllty.substring(1, fllty.length() - 1); - } - } - if (e.kind == ExprKind.METHOD) { - if (e.object != null) { - String objCls = this.resolveObjClassName(e.object); - if (objCls == "__List" && e.method == "get") { - if (e.object.kind == ExprKind.FIELD && e.object.object != null) { - String ownerCls = this.resolveObjClassName(e.object.object); - if (ownerCls.length() > 0 && ownerCls != "__List" && ownerCls != "__HashMap") { - List fds = this.tc.allFieldsOf(ownerCls); - Integer fi = 0; - while (fi < fds.length()) { - FieldDecl fd = fds.get(fi) as FieldDecl; - if (fd != null) { - if (fd.name == e.object.field) { - if (fd.ty != null) { - if (fd.ty.kind == TypeKind.LIST && fd.ty.inner != null) { - String en = fd.ty.inner.name; - if (en.length() > 0) { return en; } - } - } - fi = fds.length(); - } - } - fi++; - } - } - } - } - if (objCls.length() > 0) { - MethodDecl? mm = this.tc.findMethodOn(objCls, e.method); - if (mm != null) { - if (mm.returnTy != null) { - String rty = this.llvmTy(mm.returnTy); - if (rty == "%__List*") { return "__List"; } - if (rty == "%__HashMap*") { return "__HashMap"; } - if (rty.startsWith("%") && rty.endsWith("*") && !rty.startsWith("%__")) { - return rty.substring(1, rty.length() - 1); - } - } - } - } - } - } - if (e.kind == ExprKind.STATIC_CALL) { - MethodDecl? sm = this.tc.findMethodOn(e.class_, e.method); - if (sm != null) { - if (sm.returnTy != null) { - String rty = this.llvmTy(sm.returnTy); - if (rty == "%__List*") { return "__List"; } - if (rty.startsWith("%") && rty.endsWith("*") && !rty.startsWith("%__")) { - return rty.substring(1, rty.length() - 1); - } - } - } - } - return ""; - } - - private emitNullSafe(e: Expr) : String { - String objVal = this.emitExpr(e.object); - String nullLbl = this.nextLabel(); - String okLbl = this.nextLabel(); - String endLbl = this.nextLabel(); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp eq i8* ${objVal}, null"); - this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${nullLbl}, label %${okLbl}\n"); - this.emitLabel(okLbl); - String resVal = "null"; - if (e.args.length() > 0) { - String cls = this.resolveObjClassName(e.object); - resVal = this.dispatchMethod(cls, objVal, e.field, e.args); - } else { - String cls = this.resolveObjClassName(e.object); - Integer slot = this.fieldSlot(cls, e.field); - if (slot >= 0) { - String fllty = this.fieldLLTy(cls, e.field); - String ptr = this.nextReg(); - this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); - resVal = this.nextReg(); - this.emit("${resVal} = load ${fllty}, ${fllty}* ${ptr}"); - } - } - this.emitBr(endLbl); - this.emitLabel(nullLbl); - this.emitBr(endLbl); - this.emitLabel(endLbl); - return resVal; - } - - private emitCharMethod(charVal: String, method: String) : String { - if (method == "code") { return charVal; } - if (method == "isDigit") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_digit(i64 ${charVal})"); - return r; - } - if (method == "isAlpha") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_alpha(i64 ${charVal})"); - return r; - } - if (method == "isAlphaNum") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_alphanum(i64 ${charVal})"); - return r; - } - if (method == "isUpper") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_upper(i64 ${charVal})"); - return r; - } - if (method == "isLower") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_lower(i64 ${charVal})"); - return r; - } - if (method == "isWhitespace") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_is_space(i64 ${charVal})"); - return r; - } - if (method == "toUpper") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_to_upper(i64 ${charVal})"); - return r; - } - if (method == "toLower") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_char_to_lower(i64 ${charVal})"); - return r; - } - if (method == "toString") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_char_to_string(i64 ${charVal})"); - return r; - } - return charVal; - } - - private emitMethodCall(e: Expr) : String { - String objVal = this.emitExpr(e.object); - String cls = this.resolveObjClassName(e.object); - if (cls == "String") { - return this.emitStringMethod(objVal, e.method, e.args); - } - if (e.object != null && e.object.kind == ExprKind.CHAR_LIT) { - return this.emitCharMethod(objVal, e.method); - } - if (cls != "") { - return this.dispatchMethod(cls, objVal, e.method, e.args); - } - if (objVal == "0") { - String n = e.object.strVal; - if (n == "IO") { return this.emitIO(e.method, e.args); } - if (n == "Math") { return this.emitMath(e.method, e.args); } - if (n == "Memory") { return this.emitMemory(e.method, e.args); } - if (n == "Time") { return this.emitTime(e.method, e.args); } - MethodDecl? sm = this.tc.findMethodOn(n, e.method); - String retLLTy = "i8*"; - if (sm != null) { if (sm.returnTy != null) { retLLTy = this.llvmTy(sm.returnTy); } } - if (retLLTy == "i8*") { - String meth = e.method; - if (meth == "exists" || meth == "success" || meth == "delete") { retLLTy = "i64"; } - if (meth == "write" || meth == "append" || meth == "check") { retLLTy = "void"; } - if (meth == "exec") { retLLTy = "i8*"; } - } - String argStr2 = ""; - Integer si = 0; - while (si < e.args.length()) { - String av2 = this.emitExpr(e.args.get(si)); - String at2 = this.inferExprLLTy(e.args.get(si)); - if (argStr2.length() > 0) { argStr2 = argStr2.concat(", "); } - argStr2 = argStr2.concat("${at2} ${av2}"); - si++; - } - if (retLLTy == "void") { - this.emit("call void @${n}__${e.method}(${argStr2})"); - return ""; - } - String sr = this.nextReg(); - this.emit("${sr} = call ${retLLTy} @${n}__${e.method}(${argStr2})"); - return sr; - } - return this.emitStringMethod(objVal, e.method, e.args); - } - - private dispatchMethod(cls: String, objVal: String, method: String, args: List) : String { - if (cls == "__List" || cls == "List") { - return this.emitListMethod(objVal, method, args); - } - if (cls == "__HashMap" || cls == "HashMap") { - return this.emitHashMapMethod(objVal, method, args); - } - MethodDecl? m = this.tc.findMethodOn(cls, method); - String retLLTy = "i8*"; - if (m != null) { if (m.returnTy != null) { retLLTy = this.llvmTy(m.returnTy); } } - String argStr = "%${cls}* ${objVal}"; - Integer i = 0; - while (i < args.length()) { - String av = this.emitExpr(args.get(i)); - String at = this.inferExprLLTy(args.get(i)); - argStr = argStr.concat(", ${at} ${av}"); - i++; - } - if (retLLTy == "void") { - this.emit("call void @${cls}__${method}(${argStr})"); - return ""; - } - String r = this.nextReg(); - this.emit("${r} = call ${retLLTy} @${cls}__${method}(${argStr})"); - return r; - } - - private emitStaticCall(e: Expr) : String { - String cls = e.class_; - String method = e.method; - if (cls == "IO") { return this.emitIO(method, e.args); } - if (cls == "Math") { return this.emitMath(method, e.args); } - if (cls == "Memory") { return this.emitMemory(method, e.args); } - if (cls == "Time") { return this.emitTime(method, e.args); } - MethodDecl? m = this.tc.findMethodOn(cls, method); - String retLLTy = "i8*"; - if (m != null) { if (m.returnTy != null) { retLLTy = this.llvmTy(m.returnTy); } } - String argStr = ""; - Integer i = 0; - while (i < e.args.length()) { - String av = this.emitExpr(e.args.get(i)); - String at = this.inferExprLLTy(e.args.get(i)); - if (argStr.length() > 0) { argStr = argStr.concat(", "); } - argStr = argStr.concat("${at} ${av}"); - i++; - } - if (retLLTy == "void") { - this.emit("call void @${cls}__${method}(${argStr})"); - return ""; - } - String r = this.nextReg(); - this.emit("${r} = call ${retLLTy} @${cls}__${method}(${argStr})"); - return r; - } - - private emitCtorCall(e: Expr) : String { - String cls = e.class_; - if (cls == "List") { String r = this.nextReg(); this.emit("${r} = call %__List* @__List__new()"); return r; } - if (cls == "HashMap") { String r = this.nextReg(); this.emit("${r} = call %__HashMap* @__HashMap__new()"); return r; } - if (cls == "Pair") { return this.emitPairCtor(e.args); } - if (cls == "__super__") { return ""; } - ConstructorDecl? ctor = this.tc.findConstructor(cls); - if (ctor == null) { - ExternFnDecl? ef = this.tc.findExternFn(cls); - if (ef != null) { - String retLLTy = "i8*"; - if (ef.returnTy != null) { retLLTy = this.llvmTy(ef.returnTy); } - String eArgStr = ""; - Integer ei = 0; - while (ei < e.args.length()) { - String av = this.emitExpr(e.args.get(ei)); - String at = this.inferExprLLTy(e.args.get(ei)); - if (eArgStr.length() > 0) { eArgStr = eArgStr.concat(", "); } - eArgStr = eArgStr.concat("${at} ${av}"); - ei++; - } - if (retLLTy == "void") { - this.emit("call void @${cls}(${eArgStr})"); - return ""; - } - String er = this.nextReg(); - this.emit("${er} = call ${retLLTy} @${cls}(${eArgStr})"); - return er; - } - } - String argStr = ""; - Integer i = 0; - while (i < e.args.length()) { - String av = this.emitExpr(e.args.get(i)); - String at = this.inferExprLLTy(e.args.get(i)); - if (argStr.length() > 0) { argStr = argStr.concat(", "); } - argStr = argStr.concat("${at} ${av}"); - i++; - } - String r = this.nextReg(); - this.emit("${r} = call %${cls}* @${cls}__new(${argStr})"); - return r; - } - - private emitPairCtor(args: List) : String { - String r = this.nextReg(); - this.emit("${r} = call %__Pair* @__Pair__new()"); - if (args.length() >= 1) { - String v0 = this.emitExpr(args.get(0)); - String t0 = this.inferExprLLTy(args.get(0)); - String bc = this.nextReg(); - if (this.isIntTy(t0)) { this.emit("${bc} = inttoptr ${t0} ${v0} to i8*"); } - else { this.emit("${bc} = bitcast ${t0} ${v0} to i8*"); } - this.emit("call void @__Pair__setFirst(%__Pair* ${r}, i8* ${bc})"); - } - if (args.length() >= 2) { - String v1 = this.emitExpr(args.get(1)); - String t1 = this.inferExprLLTy(args.get(1)); - String bc = this.nextReg(); - if (this.isIntTy(t1)) { this.emit("${bc} = inttoptr ${t1} ${v1} to i8*"); } - else { this.emit("${bc} = bitcast ${t1} ${v1} to i8*"); } - this.emit("call void @__Pair__setSecond(%__Pair* ${r}, i8* ${bc})"); - } - return r; - } - - private inferExprLLTy(e: Expr) : String { - if (e == null) { return "i8*"; } - Integer k = e.kind; - if (k == ExprKind.INT_LIT) { return "i64"; } - if (k == ExprKind.CHAR_LIT) { return "i64"; } - if (k == ExprKind.FLOAT_LIT) { return "double"; } - if (k == ExprKind.BOOL_LIT) { return "i64"; } - if (k == ExprKind.STR_LIT) { return "i8*"; } - if (k == ExprKind.NULL_LIT) { return "i8*"; } - if (k == ExprKind.THIS) { return "%${this.selfType}*"; } - if (k == ExprKind.IDENT) { return this.lookupLLTy(e.strVal); } - if (k == ExprKind.CTOR) { - if (e.class_ == "List") { return "%__List*"; } - if (e.class_ == "HashMap" || e.class_ == "Map" || e.class_ == "TreeMap") { return "%__HashMap*"; } - if (e.class_ == "Pair") { return "%__Pair*"; } - if (e.class_ == "__super__") { return "void"; } - ExternFnDecl? ef = this.tc.findExternFn(e.class_); - if (ef != null) { - if (ef.returnTy != null) { return this.llvmTy(ef.returnTy); } - return "void"; - } - return "%${e.class_}*"; - } - if (k == ExprKind.STR_INTERP){ return "i8*"; } - if (k == ExprKind.CAST && e.castTy != null) { return this.llvmTy(e.castTy); } - if (k == ExprKind.FIELD) { - String fcls = this.resolveObjClassName(e.object); - if (fcls != "") { return this.fieldLLTy(fcls, e.field); } - if (e.object != null && e.object.strVal.length() > 0) { - List fsf = this.tc.allFieldsOf(e.object.strVal); - Integer fsi = 0; - while (fsi < fsf.length()) { - FieldDecl fd = fsf.get(fsi) as FieldDecl; - if (fd.isStatic && fd.name == e.field) { return this.llvmTy(fd.ty); } - fsi++; - } - } - return "i64"; - } - if (k == ExprKind.METHOD) { - String method = e.method; - if (method == "length" || method == "indexOf" || method == "charCodeAt" || - method == "compareTo" || method == "contains" || method == "startsWith" || - method == "endsWith" || method == "isEmpty") { return "i64"; } - if (method == "parseFloat" || method == "toFloat") { return "double"; } - if (method == "parseInt" || method == "toInt" || method == "parseHex") { return "i64"; } - if (method == "exists" || method == "success" || method == "delete") { return "i64"; } - if (e.object != null) { - String cls2 = this.resolveObjClassName(e.object); - if (cls2 != "" && cls2 != "String" && cls2 != "__List" && cls2 != "__HashMap") { - MethodDecl? mm = this.tc.findMethodOn(cls2, method); - if (mm != null) { if (mm.returnTy != null) { return this.llvmTy(mm.returnTy); } } - } - if (cls2 == "" && e.object.strVal.length() > 0) { - String llt2 = this.lookupLLTy(e.object.strVal); - if (llt2 == "") { - MethodDecl? mm2 = this.tc.findMethodOn(e.object.strVal, method); - if (mm2 != null) { if (mm2.returnTy != null) { return this.llvmTy(mm2.returnTy); } } - } - } - } - return "i8*"; - } - if (k == ExprKind.STATIC_CALL) { - MethodDecl? sm = this.tc.findMethodOn(e.class_, e.method); - if (sm != null) { if (sm.returnTy != null) { return this.llvmTy(sm.returnTy); } } - return "i8*"; - } - if (k == ExprKind.BINOP) { - Integer op = e.op; - if (op == BinaryOp.EQ || op == BinaryOp.NE || op == BinaryOp.LT || - op == BinaryOp.LE || op == BinaryOp.GT || op == BinaryOp.GE || - op == BinaryOp.AND || op == BinaryOp.OR) { return "i64"; } - return this.inferExprLLTy(e.left); - } - if (k == ExprKind.UNARY) { - if (e.op == UnaryOp.NOT) { return "i64"; } - return this.inferExprLLTy(e.operand); - } - return "i8*"; - } - - private emitBinOp(e: Expr) : String { - Integer op = e.op; - if (op == BinaryOp.ASSIGN || op == BinaryOp.ADD_ASSIGN || - op == BinaryOp.SUB_ASSIGN || op == BinaryOp.MUL_ASSIGN || - op == BinaryOp.DIV_ASSIGN) { - return this.emitAssign(e); - } - String lv = this.emitExpr(e.left); - String rv = this.emitExpr(e.right); - Boolean flt = e.left != null && e.left.kind == ExprKind.FLOAT_LIT; - if (!flt && e.left != null && e.left.kind == ExprKind.IDENT) { - flt = this.lookupLLTy(e.left.strVal) == "double"; - } - String r = this.nextReg(); - if (op == BinaryOp.ADD) { - String addLlt = this.inferExprLLTy(e.left); - if (addLlt == "i8*") { this.emit("${r} = call i8* @__arimo_str_concat(i8* ${lv}, i8* ${rv})"); } - else if (flt) { this.emit("${r} = fadd double ${lv}, ${rv}"); } - else { this.emit("${r} = add i64 ${lv}, ${rv}"); } - } - else if (op == BinaryOp.SUB) { this.emit(flt ? "${r} = fsub double ${lv}, ${rv}" : "${r} = sub i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.MUL) { this.emit(flt ? "${r} = fmul double ${lv}, ${rv}" : "${r} = mul i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.DIV) { this.emit(flt ? "${r} = fdiv double ${lv}, ${rv}" : "${r} = sdiv i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.MOD) { this.emit("${r} = srem i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.BITAND) { this.emit("${r} = and i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.BITOR) { this.emit("${r} = or i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.XOR) { this.emit("${r} = xor i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.SHL) { this.emit("${r} = shl i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.SHR) { this.emit("${r} = ashr i64 ${lv}, ${rv}"); } - else if (op == BinaryOp.AND) { - String lb = this.toBool(lv); - String rb = this.toBool(rv); - String cb = this.nextReg(); - this.emit("${cb} = and i1 ${lb}, ${rb}"); - return this.fromBool(cb); - } - else if (op == BinaryOp.OR) { - String lb = this.toBool(lv); - String rb = this.toBool(rv); - String cb = this.nextReg(); - this.emit("${cb} = or i1 ${lb}, ${rb}"); - return this.fromBool(cb); - } - else if (op == BinaryOp.EQ) { - String llt = this.inferExprLLTy(e.left); - String cmp = this.nextReg(); - if (llt == "double") { this.emit("${cmp} = fcmp oeq double ${lv}, ${rv}"); } - else if (llt == "i8*") { - if (rv == "null" || lv == "null") { - this.emit("${cmp} = icmp eq i8* ${lv}, ${rv}"); - } else { - String cr = this.nextReg(); - this.emit("${cr} = call i32 @strcmp(i8* ${lv}, i8* ${rv})"); - this.emit("${cmp} = icmp eq i32 ${cr}, 0"); - } - } else if (llt.endsWith("*")) { this.emit("${cmp} = icmp eq ${llt} ${lv}, ${rv}"); } - else { this.emit("${cmp} = icmp eq i64 ${lv}, ${rv}"); } - return this.fromBool(cmp); - } - else if (op == BinaryOp.NE) { - String llt = this.inferExprLLTy(e.left); - String cmp = this.nextReg(); - if (llt == "double") { this.emit("${cmp} = fcmp one double ${lv}, ${rv}"); } - else if (llt == "i8*") { - if (rv == "null" || lv == "null") { - this.emit("${cmp} = icmp ne i8* ${lv}, ${rv}"); - } else { - String cr = this.nextReg(); - this.emit("${cr} = call i32 @strcmp(i8* ${lv}, i8* ${rv})"); - this.emit("${cmp} = icmp ne i32 ${cr}, 0"); - } - } else if (llt.endsWith("*")) { this.emit("${cmp} = icmp ne ${llt} ${lv}, ${rv}"); } - else { this.emit("${cmp} = icmp ne i64 ${lv}, ${rv}"); } - return this.fromBool(cmp); - } - else if (op == BinaryOp.LT) { String c = this.nextReg(); this.emit("${c} = icmp slt i64 ${lv}, ${rv}"); return this.fromBool(c); } - else if (op == BinaryOp.LE) { String c = this.nextReg(); this.emit("${c} = icmp sle i64 ${lv}, ${rv}"); return this.fromBool(c); } - else if (op == BinaryOp.GT) { String c = this.nextReg(); this.emit("${c} = icmp sgt i64 ${lv}, ${rv}"); return this.fromBool(c); } - else if (op == BinaryOp.GE) { String c = this.nextReg(); this.emit("${c} = icmp sge i64 ${lv}, ${rv}"); return this.fromBool(c); } - else { this.emit("${r} = add i64 0, 0"); } - return r; - } - - private emitAssign(e: Expr) : String { - String rv = this.emitExpr(e.right); - String rt = this.inferExprLLTy(e.right); - this.storeToLval(e.left, rv, rt); - return rv; - } - - private storeToLval(target: Expr, val: String, ty: String) : Void { - if (target == null) { return; } - if (target.kind == ExprKind.IDENT) { - String reg = this.lookupReg(target.strVal); - if (reg == "") { - String llt = ty; - reg = this.defineVar(target.strVal, llt); - } - this.emit("store ${ty} ${val}, ${ty}* ${reg}"); - } else if (target.kind == ExprKind.FIELD) { - String objVal = this.emitExpr(target.object); - String cls = this.resolveObjClassName(target.object); - Integer slot = this.fieldSlot(cls, target.field); - if (slot >= 0) { - String fllty = this.fieldLLTy(cls, target.field); - String ptr = this.nextReg(); - this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); - String sv = val; - if (fllty != ty && ty != "void") { - String bc = this.nextReg(); - if (this.isIntTy(ty) && fllty.endsWith("*")) { this.emit("${bc} = inttoptr ${ty} ${val} to ${fllty}"); } - else if (ty.endsWith("*") && this.isIntTy(fllty)) { this.emit("${bc} = ptrtoint ${ty} ${val} to ${fllty}"); } - else { this.emit("${bc} = bitcast ${ty} ${val} to ${fllty}"); } - sv = bc; - } - this.emit("store ${fllty} ${sv}, ${fllty}* ${ptr}"); - } - } else if (target.kind == ExprKind.INDEX) { - String listVal = this.emitExpr(target.object); - String idxVal = this.emitExpr(target.index); - String bc = this.nextReg(); - if (this.isIntTy(ty)) { this.emit("${bc} = inttoptr ${ty} ${val} to i8*"); } - else { this.emit("${bc} = bitcast ${ty} ${val} to i8*"); } - this.emit("call void @__List__set(%__List* ${listVal}, i64 ${idxVal}, i8* ${bc})"); - } - } - - private emitUnaryOp(e: Expr) : String { - Integer op = e.op; - if (op == UnaryOp.NOT) { - String v = this.emitExpr(e.operand); - String b = this.toBool(v); - String n = this.nextReg(); - this.emit("${n} = xor i1 ${b}, true"); - return this.fromBool(n); - } - if (op == UnaryOp.NEG) { - String v = this.emitExpr(e.operand); - String llt = this.inferExprLLTy(e.operand); - String r = this.nextReg(); - if (llt == "double") { this.emit("${r} = fneg double ${v}"); } - else { this.emit("${r} = sub i64 0, ${v}"); } - return r; - } - if (op == UnaryOp.BITNOT) { - String v = this.emitExpr(e.operand); - String r = this.nextReg(); - this.emit("${r} = xor i64 ${v}, -1"); - return r; - } - if (op == UnaryOp.PRE_INC || op == UnaryOp.POST_INC) { - String v = this.emitExpr(e.operand); - String r = this.nextReg(); - this.emit("${r} = add i64 ${v}, 1"); - this.storeToLval(e.operand, r, "i64"); - return r; - } - if (op == UnaryOp.PRE_DEC || op == UnaryOp.POST_DEC) { - String v = this.emitExpr(e.operand); - String r = this.nextReg(); - this.emit("${r} = sub i64 ${v}, 1"); - this.storeToLval(e.operand, r, "i64"); - return r; - } - return "0"; - } - - private isIntTy(t: String) : Boolean { - return t == "i64" || t == "i32" || t == "i16" || t == "i8" || t == "i1"; - } - - private emitCast(e: Expr) : String { - String v = this.emitExpr(e.operand); - String from = this.inferExprLLTy(e.operand); - String to = this.llvmTy(e.castTy); - if (from == to) { return v; } - String r = this.nextReg(); - if (from == "double" && to == "i64") { this.emit("${r} = fptosi double ${v} to i64"); return r; } - if (from == "i64" && to == "double") { this.emit("${r} = sitofp i64 ${v} to double"); return r; } - if (from == "double" && to == "i8*") { this.emit("${r} = inttoptr i64 0 to i8*"); return r; } - if (to == "i8*") { - if (this.isIntTy(from)) { this.emit("${r} = inttoptr ${from} ${v} to i8*"); } - else { this.emit("${r} = bitcast ${from} ${v} to i8*"); } - return r; - } - if (from == "i8*") { - if (this.isIntTy(to)) { this.emit("${r} = ptrtoint i8* ${v} to ${to}"); } - else { this.emit("${r} = bitcast i8* ${v} to ${to}"); } - return r; - } - if (this.isIntTy(from) && to.endsWith("*")) { this.emit("${r} = inttoptr ${from} ${v} to ${to}"); return r; } - if (from.endsWith("*") && this.isIntTy(to)) { this.emit("${r} = ptrtoint ${from} ${v} to ${to}"); return r; } - if (from.startsWith("%") && to.startsWith("%")) { this.emit("${r} = bitcast ${from} ${v} to ${to}"); return r; } - this.emit("${r} = bitcast ${from} ${v} to ${to}"); - return r; - } - - private emitTernary(e: Expr) : String { - String cond = this.emitExpr(e.cond); - String condB = this.toBool(cond); - String thenL = this.nextLabel(); - String elseL = this.nextLabel(); - String endL = this.nextLabel(); - this.curFn = this.curFn.concat(" br i1 ${condB}, label %${thenL}, label %${elseL}\n"); - this.emitLabel(thenL); - String tv = this.emitExpr(e.then_); - String tty = this.inferExprLLTy(e.then_); - this.emitBr(endL); - this.emitLabel(elseL); - String ev = this.emitExpr(e.else_); - this.emitBr(endL); - this.emitLabel(endL); - String r = this.nextReg(); - this.emit("${r} = phi ${tty} [ ${tv}, %${thenL} ], [ ${ev}, %${elseL} ]"); - return r; - } - - private emitIndex(e: Expr) : String { - String listVal = this.emitExpr(e.object); - String idxVal = this.emitExpr(e.index); - String raw = this.nextReg(); - this.emit("${raw} = call i8* @__List__get(%__List* ${listVal}, i64 ${idxVal})"); - return raw; - } - - private emitNullCoalesce(e: Expr) : String { - String lv = this.emitExpr(e.left); - String nullL = this.nextLabel(); - String okL = this.nextLabel(); - String endL = this.nextLabel(); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp eq i8* ${lv}, null"); - this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${nullL}, label %${okL}\n"); - this.emitLabel(okL); - this.emitBr(endL); - this.emitLabel(nullL); - String rv = this.emitExpr(e.right); - this.emitBr(endL); - this.emitLabel(endL); - String r = this.nextReg(); - this.emit("${r} = phi i8* [ ${lv}, %${okL} ], [ ${rv}, %${nullL} ]"); - return r; - } - - private emitMatchExpr(e: Expr?) : String { - if (e != null) { - String subj = this.emitExpr(e.matchExpr); - String endL = this.nextLabel(); - String resReg = this.nextReg(); - this.emit("${resReg} = alloca i8*"); - Integer i = 0; - while (i < e.arms.length()) { - MatchArm arm = e.arms.get(i); - String bodyL = this.nextLabel(); - String nextL = this.nextLabel(); - if (arm.pattern.kind == MatchPatternKind.WILDCARD || arm.pattern.kind == MatchPatternKind.BINDING) { - this.emitBr(bodyL); - } else if (arm.pattern.kind == MatchPatternKind.VARIANT) { - Integer varIdx = this.tc.enumVariantIndex(arm.pattern.enumName, arm.pattern.variant); - String tagPtr = this.nextReg(); - this.emit("${tagPtr} = bitcast i8* ${subj} to i64*"); - String tag = this.nextReg(); - this.emit("${tag} = load i64, i64* ${tagPtr}"); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp eq i64 ${tag}, ${varIdx}"); - this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${nextL}\n"); - } else { - this.emitBr(bodyL); - } - this.emitLabel(bodyL); - String bv = this.emitExpr(arm.body); - String bc = this.nextReg(); - this.emit("${bc} = bitcast i8* ${bv} to i8*"); - this.emit("store i8* ${bc}, i8** ${resReg}"); - this.emitBr(endL); - this.emitLabel(nextL); - i++; - } - this.emitBr(endL); - this.emitLabel(endL); - String final = this.nextReg(); - this.emit("${final} = load i8*, i8** ${resReg}"); - return final; - } - return "0"; - } - - private emitStrInterp(e: Expr) : String { - if (e.parts == null || e.parts.length() == 0) { return this.emitStrLit(""); } - String acc = ""; - Integer i = 0; - while (i < e.parts.length()) { - StrPart p = e.parts.get(i); - String pv = ""; - if (p.isLit) { - pv = this.emitStrLit(p.lit); - } else { - String exprVal = this.emitExpr(p.expr); - String exprTy = this.inferExprLLTy(p.expr); - if (exprTy == "i8*") { - pv = exprVal; - } else if (exprTy == "double") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_f64_to_str(double ${exprVal})"); - pv = r; - } else { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_i64_to_str(i64 ${exprVal})"); - pv = r; - } - } - if (acc == "") { - acc = pv; - } else { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_concat(i8* ${acc}, i8* ${pv})"); - acc = r; - } - i++; - } - return acc; - } - - private emitIO(method: String, args: List) : String { - String fmtNl = this.emitStrConst("%s\n"); - String fmtI = this.emitStrConst("%lld\n"); - String fmtF = this.emitStrConst("%f\n"); - String fmtNlP = this.strGEP(fmtNl, 4); - String fmtIP = this.strGEP(fmtI, 6); - String fmtFP = this.strGEP(fmtF, 4); - if (method == "println" || method == "print" || method == "error") { - if (args.length() == 0) { - String nl = this.emitStrLit("\n"); - this.emit("call i32 (i8*, ...) @printf(i8* ${fmtNlP}, i8* ${nl})"); - this.emit("call i32 @fflush(i8* null)"); - return ""; - } - Expr arg = args.get(0); - String av = this.emitExpr(arg); - String at = this.inferExprLLTy(arg); - if (at == "double") { - this.emit("call i32 (i8*, ...) @printf(i8* ${fmtFP}, double ${av})"); - } else if (at == "i8*") { - String use = fmtNlP; - if (method == "print") { use = this.strGEP(this.emitStrConst("%s"), 3); } - this.emit("call i32 (i8*, ...) @printf(i8* ${use}, i8* ${av})"); - } else { - this.emit("call i32 (i8*, ...) @printf(i8* ${fmtIP}, i64 ${av})"); - } - this.emit("call i32 @fflush(i8* null)"); - } else if (method == "read") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_readline()"); - return r; - } - return ""; - } - - private emitMath(method: String, args: List) : String { - if (method == "PI") { String r = this.nextReg(); this.emit("${r} = fadd double 0.0, 3.14159265358979"); return r; } - if (method == "E") { String r = this.nextReg(); this.emit("${r} = fadd double 0.0, 2.71828182845905"); return r; } - String v0 = ""; - if (args.length() >= 1) { v0 = this.emitExpr(args.get(0)); } - if (method == "sqrt") { String r = this.nextReg(); this.emit("${r} = call double @llvm.sqrt.f64(double ${v0})"); return r; } - if (method == "abs") { String r = this.nextReg(); this.emit("${r} = call double @llvm.fabs.f64(double ${v0})"); return r; } - if (method == "floor") { String r = this.nextReg(); this.emit("${r} = call double @llvm.floor.f64(double ${v0})"); return r; } - if (method == "ceil") { String r = this.nextReg(); this.emit("${r} = call double @llvm.ceil.f64(double ${v0})"); return r; } - if (method == "sin") { String r = this.nextReg(); this.emit("${r} = call double @llvm.sin.f64(double ${v0})"); return r; } - if (method == "cos") { String r = this.nextReg(); this.emit("${r} = call double @llvm.cos.f64(double ${v0})"); return r; } - if (method == "log") { String r = this.nextReg(); this.emit("${r} = call double @llvm.log.f64(double ${v0})"); return r; } - if (method == "pow") { - String v1 = ""; - if (args.length() >= 2) { v1 = this.emitExpr(args.get(1)); } - String r = this.nextReg(); - this.emit("${r} = call double @llvm.pow.f64(double ${v0}, double ${v1})"); - return r; - } - if (method == "min" || method == "max") { - String v1 = ""; - if (args.length() >= 2) { v1 = this.emitExpr(args.get(1)); } - String t0 = "i64"; - String r = this.nextReg(); - String op = "slt"; - if (method == "max") { op = "sgt"; } - String cmp = this.nextReg(); - this.emit("${cmp} = icmp ${op} i64 ${v0}, ${v1}"); - this.emit("${r} = select i1 ${cmp}, i64 ${v0}, i64 ${v1}"); - return r; - } - return "0"; - } - - private emitMemory(method: String, args: List) : String { - if (method == "alloc") { - String sz = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i8* @malloc(i64 ${sz})"); - return r; - } - if (method == "free") { - String ptr = this.emitExpr(args.get(0)); - this.emit("call void @free(i8* ${ptr})"); - return ""; - } - return "0"; - } - - private emitTime(method: String, args: List) : String { - if (method == "now") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_time_now()"); - return r; - } - if (method == "generateId") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_generate_id()"); - return r; - } - if (method == "nowMillis") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_now_millis()"); - return r; - } - return "0"; - } - - private emitStringMethod(strVal: String, method: String, args: List) : String { - if (method == "length") { - String r = this.nextReg(); - this.emit("${r} = call i64 @strlen(i8* ${strVal})"); - return r; - } - if (method == "concat") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_concat(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "contains") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_str_contains(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "startsWith") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_str_starts_with(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "endsWith") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_str_ends_with(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "substring") { - String from = this.emitExpr(args.get(0)); - String to = this.emitExpr(args.get(1)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_substring(i8* ${strVal}, i64 ${from}, i64 ${to})"); - return r; - } - if (method == "indexOf") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_str_index_of(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "toLower") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_to_lower(i8* ${strVal})"); - return r; - } - if (method == "toUpper") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_to_upper(i8* ${strVal})"); - return r; - } - if (method == "trim") { - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_trim(i8* ${strVal})"); - return r; - } - if (method == "parseInt") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__arimo_str_parse_int(i8* ${strVal})"); - return r; - } - if (method == "parseFloat") { - String r = this.nextReg(); - this.emit("${r} = call double @__arimo_str_parse_float(i8* ${strVal})"); - return r; - } - if (method == "compareTo") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i32 @strcmp(i8* ${strVal}, i8* ${a})"); - String r2 = this.nextReg(); - this.emit("${r2} = sext i32 ${r} to i64"); - return r2; - } - if (method == "replace") { - String a = this.emitExpr(args.get(0)); - String b = this.emitExpr(args.get(1)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__arimo_str_replace(i8* ${strVal}, i8* ${a}, i8* ${b})"); - return r; - } - if (method == "split") { - String a = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call %__List* @__arimo_str_split(i8* ${strVal}, i8* ${a})"); - return r; - } - if (method == "equals") { - String a = this.emitExpr(args.get(0)); - String cr = this.nextReg(); - this.emit("${cr} = call i32 @strcmp(i8* ${strVal}, i8* ${a})"); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp eq i32 ${cr}, 0"); - return this.fromBool(cmp); - } - return "0"; - } - - private emitListMethod(listVal: String, method: String, args: List) : String { - if (method == "append" || method == "add") { - String v = this.emitExpr(args.get(0)); - String t = this.inferExprLLTy(args.get(0)); - String bc = v; - if (t != "i8*") { - bc = this.nextReg(); - if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } - else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } - } - this.emit("call void @__List__append(%__List* ${listVal}, i8* ${bc})"); - return ""; - } - if (method == "get") { - String i = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__List__get(%__List* ${listVal}, i64 ${i})"); - return r; - } - if (method == "length" || method == "size") { - String r = this.nextReg(); - this.emit("${r} = call i64 @__List__length(%__List* ${listVal})"); - return r; - } - if (method == "removeAt" || method == "remove") { - String i = this.emitExpr(args.get(0)); - this.emit("call void @__List__removeAt(%__List* ${listVal}, i64 ${i})"); - return ""; - } - if (method == "set") { - String i = this.emitExpr(args.get(0)); - String v = this.emitExpr(args.get(1)); - String t = this.inferExprLLTy(args.get(1)); - String bc = v; - if (t != "i8*") { - bc = this.nextReg(); - if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } - else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } - } - this.emit("call void @__List__set(%__List* ${listVal}, i64 ${i}, i8* ${bc})"); - return ""; - } - return "0"; - } - - private emitHashMapMethod(mapVal: String, method: String, args: List) : String { - if (method == "set" || method == "put") { - String k = this.emitExpr(args.get(0)); - String v = this.emitExpr(args.get(1)); - String t = this.inferExprLLTy(args.get(1)); - String bc = v; - if (t != "i8*") { - bc = this.nextReg(); - if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } - else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } - } - this.emit("call void @__HashMap__set(%__HashMap* ${mapVal}, i8* ${k}, i8* ${bc})"); - return ""; - } - if (method == "get") { - String k = this.emitExpr(args.get(0)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__HashMap__get(%__HashMap* ${mapVal}, i8* ${k})"); - return r; - } - if (method == "getOrDefault") { - String k = this.emitExpr(args.get(0)); - String d = this.emitExpr(args.get(1)); - String r = this.nextReg(); - this.emit("${r} = call i8* @__HashMap__getOrDefault(%__HashMap* ${mapVal}, i8* ${k}, i8* ${d})"); - return r; - } - return "0"; - } - - public emitStmt(s: Stmt) : Void { - if (s == null) { return; } - Integer k = s.kind; - - if (k == StmtKind.VAR_DECL) { - String llt = this.llvmTy(s.ty); - String reg = this.defineVar(s.name, llt); - if (s.initExpr != null) { - String v = this.emitExpr(s.initExpr); - String vt = this.inferExprLLTy(s.initExpr); - String sv = v; - if (vt != llt && llt != "void" && vt != "void") { - String bc = this.nextReg(); - if (this.isIntTy(vt) && llt.endsWith("*")) { this.emit("${bc} = inttoptr ${vt} ${v} to ${llt}"); } - else if (vt.endsWith("*") && this.isIntTy(llt)) { this.emit("${bc} = ptrtoint ${vt} ${v} to ${llt}"); } - else { this.emit("${bc} = bitcast ${vt} ${v} to ${llt}"); } - sv = bc; - } - if (llt != "void") { this.emit("store ${llt} ${sv}, ${llt}* ${reg}"); } - } else if (llt != "void") { - if (llt == "i64" || llt == "i32" || llt == "i16" || llt == "i8") { - this.emit("store ${llt} 0, ${llt}* ${reg}"); - } else if (llt == "double") { - this.emit("store double 0.0, double* ${reg}"); - } else { - this.emit("store ${llt} null, ${llt}* ${reg}"); - } - } - } - - else if (k == StmtKind.EXPR) { - this.emitExpr(s.expr); - } - - else if (k == StmtKind.RETURN) { - if (s.expr == null || this.retTyStr == "void") { - this.emit("ret void"); - } else { - String v = this.emitExpr(s.expr); - String vt = this.inferExprLLTy(s.expr); - String rv = v; - if (vt != this.retTyStr && vt != "void" && this.retTyStr != "void") { - String bc = this.nextReg(); - if (this.isIntTy(vt) && this.retTyStr.endsWith("*")) { this.emit("${bc} = inttoptr ${vt} ${v} to ${this.retTyStr}"); } - else if (vt.endsWith("*") && this.isIntTy(this.retTyStr)) { this.emit("${bc} = ptrtoint ${vt} ${v} to ${this.retTyStr}"); } - else { this.emit("${bc} = bitcast ${vt} ${v} to ${this.retTyStr}"); } - rv = bc; - } - this.emit("ret ${this.retTyStr} ${rv}"); - } - String dead = this.nextLabel(); - this.emitLabel(dead); - } - - else if (k == StmtKind.THROW) { - this.emitExpr(s.expr); - this.emit("call void @abort()"); - this.emit("unreachable"); - String dead = this.nextLabel(); - this.emitLabel(dead); - } - - else if (k == StmtKind.IF) { - String cond = this.emitExpr(s.cond); - String condB = this.toBool(cond); - String thenL = this.nextLabel(); - String endL = this.nextLabel(); - Boolean hasElse = s.elseBody.length() > 0; - Boolean hasEi = s.elseIfs.length() > 0 && s.elseIfs.length() > 0; - String falseL = endL; - if (hasElse || hasEi) { falseL = this.nextLabel(); } - this.curFn = this.curFn.concat(" br i1 ${condB}, label %${thenL}, label %${falseL}\n"); - this.emitLabel(thenL); - this.pushScope(); - this.emitStmts(s.thenBody); - this.popScope(); - this.emitBr(endL); - if (hasEi) { - this.emitLabel(falseL); - Integer ei = 0; - while (ei < s.elseIfs.length()) { - ElseIfBranch eib = s.elseIfs.get(ei); - String eic = this.emitExpr(eib.cond); - String eiB = this.toBool(eic); - String eiBody = this.nextLabel(); - String eiNext = endL; - if (ei + 1 < s.elseIfs.length() || hasElse) { eiNext = this.nextLabel(); } - this.curFn = this.curFn.concat(" br i1 ${eiB}, label %${eiBody}, label %${eiNext}\n"); - this.emitLabel(eiBody); - this.pushScope(); - this.emitStmts(eib.body); - this.popScope(); - this.emitBr(endL); - falseL = eiNext; - if (eiNext != endL) { this.emitLabel(eiNext); } - ei++; - } - if (hasElse) { - this.pushScope(); - this.emitStmts(s.elseBody); - this.popScope(); - this.emitBr(endL); - } - } else if (hasElse) { - this.emitLabel(falseL); - this.pushScope(); - this.emitStmts(s.elseBody); - this.popScope(); - this.emitBr(endL); - } - this.emitLabel(endL); - } - - else if (k == StmtKind.WHILE) { - String condL = this.nextLabel(); - String bodyL = this.nextLabel(); - String exitL = this.nextLabel(); - this.loopConds.append(condL); - this.loopExits.append(exitL); - this.emitBr(condL); - this.emitLabel(condL); - String cond = this.emitExpr(s.cond); - String condB = this.toBool(cond); - this.curFn = this.curFn.concat(" br i1 ${condB}, label %${bodyL}, label %${exitL}\n"); - this.emitLabel(bodyL); - this.pushScope(); - this.emitStmts(s.body); - this.popScope(); - this.emitBr(condL); - this.emitLabel(exitL); - Integer last = this.loopConds.length() - 1; - this.loopConds.removeAt(last); - this.loopExits.removeAt(last); - } - - else if (k == StmtKind.FOR) { - String condL = this.nextLabel(); - String bodyL = this.nextLabel(); - String exitL = this.nextLabel(); - this.loopConds.append(condL); - this.loopExits.append(exitL); - this.pushScope(); - if (s.forInit != null) { this.emitStmt(s.forInit); } - this.emitBr(condL); - this.emitLabel(condL); - String cond = this.emitExpr(s.forCond); - String condB = this.toBool(cond); - this.curFn = this.curFn.concat(" br i1 ${condB}, label %${bodyL}, label %${exitL}\n"); - this.emitLabel(bodyL); - this.emitStmts(s.forBody); - if (s.forStep != null) { this.emitExpr(s.forStep); } - this.popScope(); - this.emitBr(condL); - this.emitLabel(exitL); - Integer last = this.loopConds.length() - 1; - this.loopConds.removeAt(last); - this.loopExits.removeAt(last); - } - - else if (k == StmtKind.FOR_EACH) { - String iterVal = this.emitExpr(s.iterable); - String lenReg = this.nextReg(); - this.emit("${lenReg} = call i64 @__List__length(%__List* ${iterVal})"); - String idxReg = this.defineVar("__i_${this.labelCnt}", "i64"); - this.emit("store i64 0, i64* ${idxReg}"); - String condL = this.nextLabel(); - String bodyL = this.nextLabel(); - String exitL = this.nextLabel(); - this.loopConds.append(condL); - this.loopExits.append(exitL); - this.emitBr(condL); - this.emitLabel(condL); - String idxVal = this.nextReg(); - this.emit("${idxVal} = load i64, i64* ${idxReg}"); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp slt i64 ${idxVal}, ${lenReg}"); - this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${exitL}\n"); - this.emitLabel(bodyL); - this.pushScope(); - String llt = this.llvmTy(s.iterTy); - String elemReg = this.defineVar(s.iterName, llt); - String raw = this.nextReg(); - this.emit("${raw} = call i8* @__List__get(%__List* ${iterVal}, i64 ${idxVal})"); - if (llt == "i8*") { - this.emit("store i8* ${raw}, i8** ${elemReg}"); - } else if (llt == "i64") { - String ptrCast = this.nextReg(); - this.emit("${ptrCast} = ptrtoint i8* ${raw} to i64"); - this.emit("store i64 ${ptrCast}, i64* ${elemReg}"); - } else { - String bc = this.nextReg(); - this.emit("${bc} = bitcast i8* ${raw} to ${llt}"); - this.emit("store ${llt} ${bc}, ${llt}* ${elemReg}"); - } - this.emitStmts(s.body); - String nextIdx = this.nextReg(); - this.emit("${nextIdx} = add i64 ${idxVal}, 1"); - this.emit("store i64 ${nextIdx}, i64* ${idxReg}"); - this.popScope(); - this.emitBr(condL); - this.emitLabel(exitL); - Integer last = this.loopConds.length() - 1; - this.loopConds.removeAt(last); - this.loopExits.removeAt(last); - } - - else if (k == StmtKind.SWITCH) { - String sv = this.emitExpr(s.switchExpr); - String endL = this.nextLabel(); - this.loopExits.append(endL); - Integer i = 0; - while (i < s.cases.length()) { - SwitchCase sc = s.cases.get(i); - String bodyL = this.nextLabel(); - String nextL = endL; - if (i + 1 < s.cases.length()) { nextL = this.nextLabel(); } - if (!sc.isDefault) { - String pv = this.emitExpr(sc.pattern); - String cmp = this.nextReg(); - this.emit("${cmp} = icmp eq i64 ${sv}, ${pv}"); - this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${nextL}\n"); - this.emitLabel(bodyL); - } else { - this.emitBr(bodyL); - this.emitLabel(bodyL); - } - this.pushScope(); - this.emitStmts(sc.body); - this.popScope(); - this.emitBr(endL); - if (nextL != endL) { this.emitLabel(nextL); } - i++; - } - this.emitLabel(endL); - Integer last = this.loopExits.length() - 1; - this.loopExits.removeAt(last); - } - - else if (k == StmtKind.TRY) { - this.pushScope(); - this.emitStmts(s.tryBody); - this.popScope(); - if (s.catches.length() > 0) { - Integer i = 0; - while (i < s.catches.length()) { - CatchClause cc = s.catches.get(i); - this.pushScope(); - String llt = this.llvmTy(cc.exType); - String reg = this.defineVar(cc.name, llt); - if (llt != "void") { this.emit("store ${llt} null, ${llt}* ${reg}"); } - this.emitStmts(cc.body); - this.popScope(); - i++; - } - } - if (s.finallyBody.length() > 0) { - this.pushScope(); - this.emitStmts(s.finallyBody); - this.popScope(); - } - } - - else if (k == StmtKind.BREAK) { - Integer last = this.loopExits.length() - 1; - if (last >= 0) { this.emitBr(this.loopExits.get(last)); } - String dead = this.nextLabel(); - this.emitLabel(dead); - } - - else if (k == StmtKind.CONTINUE) { - Integer last = this.loopConds.length() - 1; - if (last >= 0) { this.emitBr(this.loopConds.get(last)); } - String dead = this.nextLabel(); - this.emitLabel(dead); - } - - else if (k == StmtKind.BLOCK) { - this.pushScope(); - this.emitStmts(s.body); - this.popScope(); - } - - else if (k == StmtKind.ASM) { - this.emit("call void asm sideeffect \"${s.asmText}\", \"\"()"); - } - - else if (k == StmtKind.DEFER) { - this.emitExpr(s.expr); - } - } - - private emitStmts(stmts: List) : Void { - if (stmts.length() >= 0) { - Integer i = 0; - while (i < stmts.length()) { - this.emitStmt(stmts.get(i)); - i++; - } - } - } - - private emitConstructor(c: ClassDecl?) : Void { - if (c != null) { - if (c.ctor != null) { this.emitConstructorInner(c, c.ctor); } - } - } - - private emitConstructorInner(c: ClassDecl, ctor: ConstructorDecl) : Void { - if (ctor.params == null) { return; } - String paramStr = ""; - Integer i = 0; - while (i < ctor.params.length()) { - Param p = ctor.params.get(i); - String pt = this.llvmTy(p.ty); - if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } - paramStr = paramStr.concat("${pt} %p.${p.name}"); - i++; - } - String sig = "define %${c.name}* @${c.name}__new(${paramStr})"; - this.curFn = ""; - this.selfType = c.name; - this.retTyStr = "%${c.name}*"; - this.varNames = List(); - this.varRegs = List(); - this.varLLTys = List(); - this.varElemClass = List(); - this.scopeMarks = List(); - this.loopExits = List(); - this.loopConds = List(); - Integer sz = this.structByteSize(c.name); - String raw = this.nextReg(); - this.emit("${raw} = call i8* @malloc(i64 ${sz})"); - String self = this.nextReg(); - this.emit("${self} = bitcast i8* ${raw} to %${c.name}*"); - String zraw = this.nextReg(); - this.emit("${zraw} = bitcast %${c.name}* ${self} to i8*"); - this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); - String rcPtr = this.nextReg(); - this.emit("${rcPtr} = getelementptr inbounds %${c.name}, %${c.name}* ${self}, i32 0, i32 0"); - this.emit("store i64 1, i64* ${rcPtr}"); - this.emit("%self = bitcast %${c.name}* ${self} to %${c.name}*"); - Integer j = 0; - while (j < ctor.params.length()) { - Param p = ctor.params.get(j); - String pt = this.llvmTy(p.ty); - String reg = this.defineVar(p.name, pt); - this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); - j++; - } - this.emitStmts(ctor.body); - this.emit("ret %${c.name}* ${self}"); - this.flushFn(sig); - } - - private emitExceptionCtorInner(ed: ExceptionDecl, ctor: ConstructorDecl) : Void { - if (ctor.params == null) { return; } - String paramStr = ""; - Integer pi = 0; - while (pi < ctor.params.length()) { - Param p = ctor.params.get(pi); - String pt = this.llvmTy(p.ty); - if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } - paramStr = paramStr.concat("${pt} %p.${p.name}"); - pi++; - } - String sig = "define %${ed.name}* @${ed.name}__new(${paramStr})"; - this.curFn = ""; this.selfType = ed.name; this.retTyStr = "%${ed.name}*"; - this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); - this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); - List edFlds = this.nonStaticFields(ed.name); - Integer sz = (1 + edFlds.length()) * 8; - String raw = this.nextReg(); - this.emit("${raw} = call i8* @malloc(i64 ${sz})"); - String self = this.nextReg(); - this.emit("${self} = bitcast i8* ${raw} to %${ed.name}*"); - String zraw = this.nextReg(); - this.emit("${zraw} = bitcast %${ed.name}* ${self} to i8*"); - this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); - this.emit("%self = bitcast %${ed.name}* ${self} to %${ed.name}*"); - Integer pi2 = 0; - while (pi2 < ctor.params.length()) { - Param p = ctor.params.get(pi2); - String pt = this.llvmTy(p.ty); - String reg = this.defineVar(p.name, pt); - this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); - pi2++; - } - this.emitStmts(ctor.body); - this.emit("ret %${ed.name}* ${self}"); - this.flushFn(sig); - } - - private emitMethod(className: String, m: MethodDecl) : Void { - if (m.body == null) { return; } - String paramStr = ""; - if (!m.isStatic) { paramStr = "%${className}* %self"; } - Integer i = 0; - while (i < m.params.length()) { - Param p = m.params.get(i); - String pt = this.llvmTy(p.ty); - if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } - paramStr = paramStr.concat("${pt} %p.${p.name}"); - i++; - } - String retTy = "void"; - if (m.returnTy != null) { retTy = this.llvmTy(m.returnTy); } - String sig = "define ${retTy} @${className}__${m.name}(${paramStr})"; - this.curFn = ""; - this.selfType = className; - this.retTyStr = retTy; - this.varNames = List(); - this.varRegs = List(); - this.varLLTys = List(); - this.varElemClass = List(); - this.scopeMarks = List(); - this.loopExits = List(); - this.loopConds = List(); - Integer j = 0; - while (j < m.params.length()) { - Param p = m.params.get(j); - String pt = this.llvmTy(p.ty); - String reg = this.defineVar(p.name, pt); - this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); - j++; - } - this.emitStmts(m.body); - if (retTy == "void") { this.emit("ret void"); } - else { this.emit("ret ${retTy} ${this.defVal(retTy)}"); } - this.flushFn(sig); - } - - private defVal(llt: String) : String { - if (llt == "i64" || llt == "i32" || llt == "i16" || llt == "i8") { return "0"; } - if (llt == "double") { return "0.0"; } - return "null"; - } - - private emitClassType(c: ClassDecl?) : Void { - if (c != null) { - ClassDecl cn = c as ClassDecl; - List fields = this.nonStaticFields(cn.name); - String fieldTypes = "i64"; - Integer i = 0; - while (i < fields.length()) { - FieldDecl fd = fields.get(i) as FieldDecl; - fieldTypes = fieldTypes.concat(", ").concat(this.llvmTy(fd.ty)); - i++; - } - this.addHeader("%${cn.name} = type { ${fieldTypes} }"); - Integer fi = 0; - while (fi < cn.fields.length()) { - FieldDecl f = cn.fields.get(fi) as FieldDecl; - if (f.isStatic) { - String llt = this.llvmTy(f.ty); - String init = this.defVal(llt); - if (f.init != null) { - String iv = this.emitExpr(f.init); - if (iv.length() > 0 && iv != "null") { init = iv; } - } - this.addHeader("@${cn.name}__${f.name} = global ${llt} ${init}"); - } - fi++; - } - } - } - - private emitEnumType(e: EnumDecl?) : Void { - if (e != null) { - EnumDecl en = e as EnumDecl; - this.addHeader("%${en.name} = type { i64, i64, i64 }"); - Integer i = 0; - while (i < en.variants.length()) { - EnumVariant ev = en.variants.get(i) as EnumVariant; - this.addHeader("@${en.name}__${ev.name} = constant i64 ${i}"); - i++; - } - } - } - - private emitModuleHeader() : Void { - this.addHeader("target datalayout = \"e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\""); - this.addHeader("target triple = \"x86_64-pc-windows-gnu\""); - this.addHeader(""); - this.addHeader("declare i8* @malloc(i64)"); - this.addHeader("declare void @free(i8*)"); - this.addHeader("declare i64 @strlen(i8*)"); - this.addHeader("declare i8* @strcpy(i8*, i8*)"); - this.addHeader("declare i8* @strcat(i8*, i8*)"); - this.addHeader("declare i32 @strcmp(i8*, i8*)"); - this.addHeader("declare i8* @strstr(i8*, i8*)"); - this.addHeader("declare i32 @printf(i8*, ...)"); - this.addHeader("declare i32 @sprintf(i8*, i8*, ...)"); - this.addHeader("declare i32 @fflush(i8*)"); - this.addHeader("declare void @abort()"); - this.addHeader("declare void @llvm.memset.p0i8.i64(i8*, i8, i64, i1)"); - this.addHeader("declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i1)"); - this.addHeader("declare double @llvm.sqrt.f64(double)"); - this.addHeader("declare double @llvm.fabs.f64(double)"); - this.addHeader("declare double @llvm.floor.f64(double)"); - this.addHeader("declare double @llvm.ceil.f64(double)"); - this.addHeader("declare double @llvm.sin.f64(double)"); - this.addHeader("declare double @llvm.cos.f64(double)"); - this.addHeader("declare double @llvm.log.f64(double)"); - this.addHeader("declare double @llvm.pow.f64(double, double)"); - this.addHeader(""); - this.addHeader("%__List = type { i8*, i64, i64 }"); - this.addHeader("%__HashMap = type { i8*, i64, i64 }"); - this.addHeader("%__Pair = type { i8*, i8* }"); - this.addHeader(""); - } - - private emitRuntimeFunctions() : Void { - this.addRuntime("define %__List* @__List__new() {\nentry:\n %raw = call i8* @malloc(i64 32)\n %p = bitcast i8* %raw to %__List*\n %z = bitcast %__List* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 32, i1 false)\n %cp = getelementptr inbounds %__List, %__List* %p, i32 0, i32 2\n store i64 4, i64* %cp\n %dp = getelementptr inbounds %__List, %__List* %p, i32 0, i32 0\n %data = call i8* @malloc(i64 32)\n store i8* %data, i8** %dp\n ret %__List* %p\n}\n"); - this.addRuntime("define i64 @__List__length(%__List* %list) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %l = load i64, i64* %lp\n ret i64 %l\n}\n"); - this.addRuntime("define void @__List__append(%__List* %list, i8* %elem) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %cp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 2\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %l = load i64, i64* %lp\n %c = load i64, i64* %cp\n %d = load i8*, i8** %dp\n %full = icmp sge i64 %l, %c\n br i1 %full, label %grow, label %store\ngrow:\n %nc = mul i64 %c, 2\n %ns = mul i64 %nc, 8\n %nd = call i8* @malloc(i64 %ns)\n %os = mul i64 %l, 8\n call void @llvm.memset.p0i8.i64(i8* %nd, i8 0, i64 %ns, i1 false)\n %src = bitcast i8* %d to i8*\n call void @llvm.memcpy.p0i8.p0i8.i64(i8* %nd, i8* %src, i64 %os, i1 false)\n store i8* %nd, i8** %dp\n store i64 %nc, i64* %cp\n %d2 = load i8*, i8** %dp\n br label %store\nstore:\n %d3 = load i8*, i8** %dp\n %arr = bitcast i8* %d3 to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %l\n store i8* %elem, i8** %ep\n %nl = add i64 %l, 1\n store i64 %nl, i64* %lp\n ret void\n}\n"); - this.addRuntime("define i8* @__List__get(%__List* %list, i64 %i) {\nentry:\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %i\n %v = load i8*, i8** %ep\n ret i8* %v\n}\n"); - this.addRuntime("define void @__List__set(%__List* %list, i64 %i, i8* %v) {\nentry:\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %i\n store i8* %v, i8** %ep\n ret void\n}\n"); - this.addRuntime("define void @__List__removeAt(%__List* %list, i64 %i) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %l = load i64, i64* %lp\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %j0 = add i64 %i, 1\n br label %loop\nloop:\n %j = phi i64 [ %j0, %entry ], [ %jn, %shift ]\n %done = icmp sge i64 %j, %l\n br i1 %done, label %exit, label %shift\nshift:\n %jm1 = sub i64 %j, 1\n %jn = add i64 %j, 1\n %src = getelementptr i8*, i8** %arr, i64 %j\n %dst = getelementptr i8*, i8** %arr, i64 %jm1\n %sv = load i8*, i8** %src\n store i8* %sv, i8** %dst\n br label %loop\nexit:\n %nl = sub i64 %l, 1\n store i64 %nl, i64* %lp\n ret void\n}\n"); - this.addRuntime("define i8* @__arimo_str_concat(i8* %a, i8* %b) {\nentry:\n %la = call i64 @strlen(i8* %a)\n %lb = call i64 @strlen(i8* %b)\n %lt = add i64 %la, %lb\n %lt1 = add i64 %lt, 1\n %buf = call i8* @malloc(i64 %lt1)\n call i8* @strcpy(i8* %buf, i8* %a)\n call i8* @strcat(i8* %buf, i8* %b)\n ret i8* %buf\n}\n"); - this.addRuntime("define i64 @__arimo_str_contains(i8* %s, i8* %sub) {\nentry:\n %r = call i8* @strstr(i8* %s, i8* %sub)\n %null = icmp eq i8* %r, null\n %res = select i1 %null, i64 0, i64 1\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_str_starts_with(i8* %s, i8* %pre) {\nentry:\n %l = call i64 @strlen(i8* %pre)\n %r = call i32 @strncmp(i8* %s, i8* %pre, i64 %l)\n %ok = icmp eq i32 %r, 0\n %res = select i1 %ok, i64 1, i64 0\n ret i64 %res\n}\ndeclare i32 @strncmp(i8*, i8*, i64)\n"); - this.addRuntime("define i64 @__arimo_str_ends_with(i8* %s, i8* %suf) {\nentry:\n %ls = call i64 @strlen(i8* %s)\n %lp = call i64 @strlen(i8* %suf)\n %diff = sub i64 %ls, %lp\n %neg = icmp slt i64 %diff, 0\n br i1 %neg, label %no, label %check\ncheck:\n %arr = bitcast i8* %s to i8*\n %ptr = getelementptr i8, i8* %arr, i64 %diff\n %r = call i32 @strcmp(i8* %ptr, i8* %suf)\n %ok = icmp eq i32 %r, 0\n %res = select i1 %ok, i64 1, i64 0\n ret i64 %res\nno:\n ret i64 0\n}\n"); - this.addRuntime("define i8* @__arimo_str_substring(i8* %s, i64 %from, i64 %to) {\nentry:\n %len = sub i64 %to, %from\n %len1 = add i64 %len, 1\n %buf = call i8* @malloc(i64 %len1)\n %src = getelementptr i8, i8* %s, i64 %from\n call i8* @strncpy(i8* %buf, i8* %src, i64 %len)\n %end = getelementptr i8, i8* %buf, i64 %len\n store i8 0, i8* %end\n ret i8* %buf\n}\ndeclare i8* @strncpy(i8*, i8*, i64)\n"); - this.addRuntime("define i64 @__arimo_str_index_of(i8* %s, i8* %sub) {\nentry:\n %r = call i8* @strstr(i8* %s, i8* %sub)\n %isnull = icmp eq i8* %r, null\n br i1 %isnull, label %notfound, label %found\nfound:\n %ri = ptrtoint i8* %r to i64\n %si = ptrtoint i8* %s to i64\n %idx = sub i64 %ri, %si\n ret i64 %idx\nnotfound:\n ret i64 -1\n}\n"); - this.addRuntime("define i8* @__arimo_str_to_lower(i8* %s) {\nentry:\n %l = call i64 @strlen(i8* %s)\n %l1 = add i64 %l, 1\n %buf = call i8* @malloc(i64 %l1)\n call i8* @strcpy(i8* %buf, i8* %s)\n br label %loop\nloop:\n %i = phi i64 [ 0, %entry ], [ %in, %body ]\n %done = icmp sge i64 %i, %l\n br i1 %done, label %exit, label %body\nbody:\n %cp = getelementptr i8, i8* %buf, i64 %i\n %c = load i8, i8* %cp\n %ci = sext i8 %c to i32\n %upper = icmp uge i32 %ci, 65\n %lower = icmp ule i32 %ci, 90\n %isUp = and i1 %upper, %lower\n %lc = add i32 %ci, 32\n %lc8 = trunc i32 %lc to i8\n %nc = select i1 %isUp, i8 %lc8, i8 %c\n store i8 %nc, i8* %cp\n %in = add i64 %i, 1\n br label %loop\nexit:\n ret i8* %buf\n}\n"); - this.addRuntime("define i8* @__arimo_i64_to_str(i64 %n) {\nentry:\n %buf = call i8* @malloc(i64 32)\n %fmt = getelementptr [5 x i8], [5 x i8]* @.fmt_i64, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 %n)\n ret i8* %buf\n}\n@.fmt_i64 = private constant [5 x i8] c\"%lld\\00\"\n"); - this.addRuntime("define i8* @__arimo_f64_to_str(double %f) {\nentry:\n %buf = call i8* @malloc(i64 64)\n %fmt = getelementptr [3 x i8], [3 x i8]* @.fmt_f64, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, double %f)\n ret i8* %buf\n}\n@.fmt_f64 = private constant [3 x i8] c\"%g\\00\"\n"); - this.addRuntime("define i64 @__arimo_str_parse_int(i8* %s) {\nentry:\n %r = call i64 @strtoll(i8* %s, i8** null, i32 10)\n ret i64 %r\n}\ndeclare i64 @strtoll(i8*, i8**, i32)\n"); - this.addRuntime("define double @__arimo_str_parse_float(i8* %s) {\nentry:\n %r = call double @strtod(i8* %s, i8** null)\n ret double %r\n}\ndeclare double @strtod(i8*, i8**)\n"); - this.addRuntime("define i8* @__arimo_str_trim(i8* %s) {\nentry:\n %l = call i64 @strlen(i8* %s)\n %l1 = add i64 %l, 1\n %buf = call i8* @malloc(i64 %l1)\n call i8* @strcpy(i8* %buf, i8* %s)\n ret i8* %buf\n}\n"); - this.addRuntime("define i8* @__arimo_str_replace(i8* %s, i8* %old, i8* %new) {\nentry:\n %r = call i8* @__arimo_str_concat(i8* %s, i8* %s)\n ret i8* %r\n}\n"); - this.addRuntime("define i8* @__arimo_str_to_upper(i8* %s) {\nentry:\n %r = call i8* @__arimo_str_to_lower(i8* %s)\n ret i8* %r\n}\n"); - this.addRuntime("define %__List* @__arimo_str_split(i8* %s, i8* %delim) {\nentry:\n %r = call %__List* @__List__new()\n ret %__List* %r\n}\n"); - this.addRuntime("define i8* @__arimo_time_now() {\nentry:\n %buf = call i8* @malloc(i64 32)\n %fmt = getelementptr [3 x i8], [3 x i8]* @.fmt_now, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 0)\n ret i8* %buf\n}\n@.fmt_now = private constant [3 x i8] c\"0s\\00\"\n"); - this.addRuntime("define i8* @__arimo_generate_id() {\nentry:\n %buf = call i8* @malloc(i64 64)\n %fmt = getelementptr [10 x i8], [10 x i8]* @.fmt_id, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 0, i64 0)\n ret i8* %buf\n}\n@.fmt_id = private constant [10 x i8] c\"%llx-%llx\\00\"\n"); - this.addRuntime("define i64 @__arimo_now_millis() {\nentry:\n ret i64 0\n}\n"); - this.addRuntime("define i8* @__arimo_readline() {\nentry:\n %buf = call i8* @malloc(i64 1024)\n store i8 0, i8* %buf\n %fmt = getelementptr [11 x i8], [11 x i8]* @.fmt_rd, i32 0, i32 0\n %n = call i32 (i8*, ...) @scanf(i8* %fmt, i8* %buf)\n ret i8* %buf\n}\n@.fmt_rd = private constant [11 x i8] c\" %1023[^\\0A]\\00\"\ndeclare i32 @scanf(i8*, ...)\n"); - this.addRuntime("define %__HashMap* @__HashMap__new() {\nentry:\n %raw = call i8* @malloc(i64 64)\n %p = bitcast i8* %raw to %__HashMap*\n %z = bitcast %__HashMap* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 64, i1 false)\n ret %__HashMap* %p\n}\n"); - this.addRuntime("define void @__HashMap__set(%__HashMap* %m, i8* %k, i8* %v) {\nentry:\n ret void\n}\ndefine i8* @__HashMap__get(%__HashMap* %m, i8* %k) {\nentry:\n ret i8* null\n}\ndefine i8* @__HashMap__getOrDefault(%__HashMap* %m, i8* %k, i8* %d) {\nentry:\n ret i8* %d\n}\n"); - this.addRuntime("define %__Pair* @__Pair__new() {\nentry:\n %raw = call i8* @malloc(i64 16)\n %p = bitcast i8* %raw to %__Pair*\n %z = bitcast %__Pair* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 16, i1 false)\n ret %__Pair* %p\n}\ndefine void @__Pair__setFirst(%__Pair* %p, i8* %v) {\nentry:\n %fp = getelementptr inbounds %__Pair, %__Pair* %p, i32 0, i32 0\n store i8* %v, i8** %fp\n ret void\n}\ndefine void @__Pair__setSecond(%__Pair* %p, i8* %v) {\nentry:\n %sp = getelementptr inbounds %__Pair, %__Pair* %p, i32 0, i32 1\n store i8* %v, i8** %sp\n ret void\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_digit(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 48\n %le = icmp sle i64 %c, 57\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_upper(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 65\n %le = icmp sle i64 %c, 90\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_lower(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 97\n %le = icmp sle i64 %c, 122\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_alpha(i64 %c) {\nentry:\n %u = call i64 @__arimo_char_is_upper(i64 %c)\n %l = call i64 @__arimo_char_is_lower(i64 %c)\n %ur = icmp ne i64 %u, 0\n %lr = icmp ne i64 %l, 0\n %r = or i1 %ur, %lr\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_alphanum(i64 %c) {\nentry:\n %a = call i64 @__arimo_char_is_alpha(i64 %c)\n %d = call i64 @__arimo_char_is_digit(i64 %c)\n %ar = icmp ne i64 %a, 0\n %dr = icmp ne i64 %d, 0\n %r = or i1 %ar, %dr\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_is_space(i64 %c) {\nentry:\n %s = icmp eq i64 %c, 32\n %t = icmp eq i64 %c, 9\n %n = icmp eq i64 %c, 10\n %cr = icmp eq i64 %c, 13\n %st = or i1 %s, %t\n %nc = or i1 %n, %cr\n %rb = or i1 %st, %nc\n %res = select i1 %rb, i64 1, i64 0\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_to_upper(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 97\n %le = icmp sle i64 %c, 122\n %is_lo = and i1 %ge, %le\n %up = sub i64 %c, 32\n %res = select i1 %is_lo, i64 %up, i64 %c\n ret i64 %res\n}\n"); - this.addRuntime("define i64 @__arimo_char_to_lower(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 65\n %le = icmp sle i64 %c, 90\n %is_up = and i1 %ge, %le\n %lo = add i64 %c, 32\n %res = select i1 %is_up, i64 %lo, i64 %c\n ret i64 %res\n}\n"); - this.addRuntime("define i8* @__arimo_char_to_string(i64 %c) {\nentry:\n %buf = call i8* @malloc(i64 2)\n %c8 = trunc i64 %c to i8\n store i8 %c8, i8* %buf\n %end = getelementptr i8, i8* %buf, i64 1\n store i8 0, i8* %end\n ret i8* %buf\n}\n"); - } - - private emitExternDecl(ext: ExternDecl?) : Void { - if (ext != null) { - ExternDecl exn = ext as ExternDecl; - Integer i = 0; - while (i < exn.decls.length()) { - ExternFnDecl fn = exn.decls.get(i) as ExternFnDecl; - String paramStr = ""; - Integer j = 0; - while (j < fn.params.length()) { - Param p = fn.params.get(j) as Param; - String pt = this.llvmTy(p.ty); - if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } - paramStr = paramStr.concat(pt); - j++; - } - if (fn.isVariadic) { paramStr = paramStr.concat(", ..."); } - String retTy = "void"; - if (fn.returnTy != null) { retTy = this.llvmTy(fn.returnTy); } - this.addHeader("declare ${retTy} @${fn.name}(${paramStr})"); - i++; - } - } - } - - private emitTypeDecls(module: ArimoModule) : Void { - Integer i = 0; - while (i < module.items.length()) { - Item item = module.items.get(i); - if (item.kind == ItemKind.CLASS) { this.emitClassType(item.classDecl); } - else if (item.kind == ItemKind.ENUM) { this.emitEnumType(item.enumDecl); } - else if (item.kind == ItemKind.STRUCT) { - StructDecl sd = item.structDecl; - if (sd != null) { - StructDecl sdn = sd as StructDecl; - List fields = sdn.fields; - String fts = "i64"; - Integer fi = 0; - while (fi < fields.length()) { - FieldDecl sfd = fields.get(fi) as FieldDecl; - fts = fts.concat(", ").concat(this.llvmTy(sfd.ty)); - fi++; - } - this.addHeader("%${sdn.name} = type { ${fts} }"); - } - } - else if (item.kind == ItemKind.EXCEPTION) { - ExceptionDecl? ed0 = item.exDecl; - if (ed0 != null) { - ExceptionDecl edn = ed0 as ExceptionDecl; - String fts = "i64"; - Integer fi = 0; - List efields = this.nonStaticFields(edn.name); - while (fi < efields.length()) { - FieldDecl efd = efields.get(fi) as FieldDecl; - fts = fts.concat(", ").concat(this.llvmTy(efd.ty)); - fi++; - } - this.addHeader("%${edn.name} = type { ${fts} }"); - } - } - else if (item.kind == ItemKind.EXTERN) { this.emitExternDecl(item.externDecl); } - else if (item.kind == ItemKind.CONST) { - String cnam = item.constName; - AstType cty = item.constType; - Expr cex = item.constExpr; - String llt = this.llvmTy(cty); - if (cex != null && cex.kind == ExprKind.INT_LIT) { - this.addHeader("@${cnam} = private constant ${llt} ${cex.intVal}"); - } else if (cex != null && cex.kind == ExprKind.FLOAT_LIT) { - this.addHeader("@${cnam} = private constant double ${cex.floatVal}"); - } else if (cex != null && cex.kind == ExprKind.STR_LIT) { - String sc = this.emitStrConst(cex.strVal); - this.addHeader("@${cnam}.ptr = private constant i8* ${sc}"); - } else if (cex != null && cex.kind == ExprKind.BOOL_LIT) { - Integer bv = 0; - if (cex.boolVal) { bv = 1; } - this.addHeader("@${cnam} = private constant i64 ${bv}"); - } else if (cex != null && cex.kind == ExprKind.CHAR_LIT) { - this.addHeader("@${cnam} = private constant i64 ${cex.intVal}"); - } - } - i++; - } - } - - private emitClassFunctions(c: ClassDecl) : Void { - this.emitConstructor(c); - Integer mi = 0; - while (mi < c.methods.length()) { - this.emitMethod(c.name, c.methods.get(mi)); - mi++; - } - if (c.ctor == null) { - Integer sz = this.structByteSize(c.name); - String sig = "define %${c.name}* @${c.name}__new()"; - this.curFn = ""; - this.selfType = c.name; - this.retTyStr = "%${c.name}*"; - this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); - this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); - String raw = this.nextReg(); - this.emit("${raw} = call i8* @malloc(i64 ${sz})"); - String self = this.nextReg(); - this.emit("${self} = bitcast i8* ${raw} to %${c.name}*"); - String zraw = this.nextReg(); - this.emit("${zraw} = bitcast %${c.name}* ${self} to i8*"); - this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); - String rcPtr = this.nextReg(); - this.emit("${rcPtr} = getelementptr inbounds %${c.name}, %${c.name}* ${self}, i32 0, i32 0"); - this.emit("store i64 1, i64* ${rcPtr}"); - this.emit("ret %${c.name}* ${self}"); - this.flushFn(sig); - } - if (c.name == this.entryClass || this.entryClass == "") { - Integer hasMain = 0; - Integer mi2 = 0; - while (mi2 < c.methods.length()) { - if (c.methods.get(mi2).name == "main" && c.methods.get(mi2).isStatic) { hasMain = 1; } - mi2++; - } - if (hasMain == 1) { this.entryClass = c.name; } - } - } - - private emitFunctionBodies(module: ArimoModule) : Void { - Integer j = 0; - while (j < module.items.length()) { - Item item = module.items.get(j); - if (item.kind == ItemKind.CLASS) { - ClassDecl c = item.classDecl; - if (c != null) { this.emitClassFunctions(c); } - } - else if (item.kind == ItemKind.EXCEPTION) { - ExceptionDecl ed = item.exDecl; - if (ed != null) { - if (ed.ctor != null) { this.emitExceptionCtorInner(ed, ed.ctor); } - Integer mi = 0; - while (mi < ed.methods.length()) { - this.emitMethod(ed.name, ed.methods.get(mi)); - mi++; - } - } - } - else if (item.kind == ItemKind.EXTENSION) { - ExtensionDecl ext = item.extDecl; - if (ext != null) { - Integer mi = 0; - while (mi < ext.methods.length()) { - this.emitMethod(ext.target, ext.methods.get(mi)); - mi++; - } - } - } - j++; - } - } - - private emitMainBridge() : Void { - if (this.entryClass != "") { - String mainSig = "define i32 @main(i32 %argc, i8** %argv)"; - this.curFn = ""; - this.selfType = ""; - this.retTyStr = "i32"; - this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); - this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); - this.emit("call void @${this.entryClass}__main()"); - this.emit("ret i32 0"); - this.flushFn(mainSig); - } - } - - public generateAll(modules: List) : String { - this.emitModuleHeader(); - this.emitRuntimeFunctions(); - Integer m = 0; - while (m < modules.length()) { - this.emitTypeDecls(modules.get(m)); - m++; - } - this.addHeader(""); - Integer m2 = 0; - while (m2 < modules.length()) { - this.emitFunctionBodies(modules.get(m2)); - m2++; - } - this.emitMainBridge(); - return this.header.concat(this.strConsts).concat(this.rtFuncs).concat(this.body); - } - - public generate(module: ArimoModule) : String { - this.emitModuleHeader(); - Integer ci = 0; - while (ci < this.tc.classNames.length()) { - String cname = this.tc.classNames.get(ci); - Boolean inMod = false; - Integer mi2 = 0; - while (mi2 < module.items.length()) { - Item it2 = module.items.get(mi2); - if (it2.kind == ItemKind.CLASS && it2.classDecl != null) { - if (it2.classDecl.name == cname) { inMod = true; } - } - mi2++; - } - if (!inMod) { - List cfields = this.nonStaticFields(cname); - String cfts = "i64"; - Integer cfi = 0; - while (cfi < cfields.length()) { - FieldDecl cfd = cfields.get(cfi) as FieldDecl; - cfts = cfts.concat(", ").concat(this.llvmTy(cfd.ty)); - cfi++; - } - this.addHeader("%${cname} = type { ${cfts} }"); - } - ci++; - } - this.emitRuntimeFunctions(); - this.emitTypeDecls(module); - this.addHeader(""); - this.emitFunctionBodies(module); - this.emitMainBridge(); - return this.header.concat(this.strConsts).concat(this.rtFuncs).concat(this.body); - } -} +/* +Arimo Lang Compiler - A modern programming language and compiler +Copyright (C) 2026 Egecan Akıncıoğlu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package arimo.compiler.codegen; + +import arimo.compiler.typechecker.TypeChecker; +import arimo.compiler.ast.ArimoModule; +import arimo.compiler.ast.types.TypeKind; +import arimo.compiler.ast.types.AstType; +import arimo.compiler.ast.expr.ExprKind; +import arimo.compiler.ast.expr.BinaryOp; +import arimo.compiler.ast.expr.UnaryOp; +import arimo.compiler.ast.expr.StrPart; +import arimo.compiler.ast.expr.MatchArm; +import arimo.compiler.ast.expr.MatchPatternKind; +import arimo.compiler.ast.expr.MatchPattern; +import arimo.compiler.ast.expr.Expr; +import arimo.compiler.ast.stmt.StmtKind; +import arimo.compiler.ast.stmt.Stmt; +import arimo.compiler.ast.stmt.CatchClause; +import arimo.compiler.ast.stmt.SwitchCase; +import arimo.compiler.ast.stmt.ElseIfBranch; +import arimo.compiler.ast.decl.FieldDecl; +import arimo.compiler.ast.decl.MethodDecl; +import arimo.compiler.ast.decl.ConstructorDecl; +import arimo.compiler.ast.decl.Param; +import arimo.compiler.ast.decl.EnumVariant; +import arimo.compiler.ast.decl.ExternFnDecl; +import arimo.compiler.ast.item.ItemKind; +import arimo.compiler.ast.item.Item; +import arimo.compiler.ast.item.ClassDecl; +import arimo.compiler.ast.item.EnumDecl; +import arimo.compiler.ast.item.StructDecl; +import arimo.compiler.ast.item.ExceptionDecl; +import arimo.compiler.ast.item.ExternDecl; +import arimo.compiler.ast.item.ExtensionDecl; + +public class CodeGen { + private tc : TypeChecker; + private header : String; + private strConsts : String; + private rtFuncs : String; + private body : String; + private strIdx : Integer; + private counter : Integer; + private labelCnt : Integer; + private curFn : String; + private selfType : String; + private retTyStr : String; + private varNames : List; + private varRegs : List; + private varLLTys : List; + private varElemClass : List; + private scopeMarks : List; + private loopExits : List; + private loopConds : List; + private entryClass : String; + + public constructor(tc: TypeChecker) { + this.tc = tc; + this.header = ""; + this.strConsts = ""; + this.rtFuncs = ""; + this.body = ""; + this.strIdx = 0; + this.counter = 0; + this.labelCnt = 0; + this.curFn = ""; + this.selfType = ""; + this.retTyStr = "void"; + this.varNames = List(); + this.varRegs = List(); + this.varLLTys = List(); + this.varElemClass = List(); + this.scopeMarks = List(); + this.loopExits = List(); + this.loopConds = List(); + this.entryClass = ""; + } + + private nextReg() : String { + Integer n = this.counter; + this.counter = counter + 1; + return "%t${n}"; + } + + private nextLabel() : String { + Integer n = this.labelCnt; + this.labelCnt = labelCnt + 1; + return "L${n}"; + } + + private emit(s: String) : Void { + this.curFn = this.curFn.concat(" ").concat(s).concat("\n"); + } + + private emitBr(label: String) : Void { + this.curFn = this.curFn.concat(" br label %").concat(label).concat("\n"); + } + + private emitLabel(label: String) : Void { + this.curFn = this.curFn.concat(label).concat(":\n"); + } + + private flushFn(sig: String) : Void { + this.body = this.body.concat(sig).concat(" {\nentry:\n").concat(this.curFn).concat("}\n\n"); + this.curFn = ""; + this.counter = 0; + this.varNames = List(); + this.varRegs = List(); + this.varLLTys = List(); + this.varElemClass = List(); + this.scopeMarks = List(); + this.loopExits = List(); + this.loopConds = List(); + } + + private addHeader(s: String) : Void { + this.header = this.header.concat(s).concat("\n"); + } + + private addRuntime(s: String) : Void { + this.rtFuncs = this.rtFuncs.concat(s).concat("\n"); + } + + public llvmTy(ty: AstType) : String { + if (ty == null) { return "void"; } + Integer k = ty.kind; + if (k == TypeKind.INTEGER) { return "i64"; } + if (k == TypeKind.FLOAT) { return "double"; } + if (k == TypeKind.BOOLEAN) { return "i64"; } + if (k == TypeKind.STR) { return "i8*"; } + if (k == TypeKind.VOID) { return "void"; } + if (k == TypeKind.NORETURN) { return "void"; } + if (k == TypeKind.U8) { return "i8"; } + if (k == TypeKind.U16) { return "i16"; } + if (k == TypeKind.U32) { return "i32"; } + if (k == TypeKind.U64) { return "i64"; } + if (k == TypeKind.I8) { return "i8"; } + if (k == TypeKind.I16) { return "i16"; } + if (k == TypeKind.I32) { return "i32"; } + if (k == TypeKind.I64) { return "i64"; } + if (k == TypeKind.CHAR) { return "i64"; } + if (k == TypeKind.NAMED) { return "%${ty.name}*"; } + if (k == TypeKind.NULLABLE) { return this.llvmTy(ty.inner); } + if (k == TypeKind.LIST) { return "%__List*"; } + if (k == TypeKind.HASHMAP || k == TypeKind.MAP || k == TypeKind.TREEMAP) { return "%__HashMap*"; } + if (k == TypeKind.PAIR) { return "%__Pair*"; } + if (k == TypeKind.RAWPTR) { return "i8*"; } + if (k == TypeKind.FNPTR) { return "i8*"; } + if (k == TypeKind.ARRAY) { return "i8*"; } + if (k == TypeKind.SLICE) { return "i8*"; } + if (k == TypeKind.GENERIC) { return "%${ty.name}*"; } + return "i8*"; + } + + private isVoidTy(ty: AstType) : Boolean { + if (ty == null) { return true; } + return ty.kind == TypeKind.VOID || ty.kind == TypeKind.NORETURN; + } + + private escapeStr(s: String) : String { + String r = ""; + Integer i = 0; + while (i < s.length()) { + String c = s.substring(i, i + 1); + if (c == "\n") { r = r.concat("\\0A"); } + else if (c == "\t") { r = r.concat("\\09"); } + else if (c == "\r") { r = r.concat("\\0D"); } + else if (c == "\"") { r = r.concat("\\22"); } + else if (c == "\\") { r = r.concat("\\5C"); } + else { r = r.concat(c); } + i = i + 1; + } + return r; + } + + private emitStrConst(s: String) : String { + Integer idx = this.strIdx; + this.strIdx = strIdx + 1; + Integer len = s.length() + 1; + String esc = this.escapeStr(s); + String prefix = "@.str.${idx} = private constant [${len} x i8] c\""; + this.strConsts = this.strConsts.concat(prefix).concat(esc).concat("\\00\"\n"); + return "@.str.${idx}"; + } + + private strGEP(gname: String, len: Integer) : String { + String r = this.nextReg(); + this.emit("${r} = getelementptr [${len} x i8], [${len} x i8]* ${gname}, i32 0, i32 0"); + return r; + } + + private emitStrLit(s: String) : String { + String g = this.emitStrConst(s); + return this.strGEP(g, s.length() + 1); + } + + private pushScope() : Void { + this.scopeMarks.append(this.varNames.length()); + } + + private popScope() : Void { + Integer last = this.scopeMarks.length() - 1; + if (last < 0) { return; } + Integer mark = this.scopeMarks.get(last); + this.scopeMarks.removeAt(last); + while (this.varNames.length() > mark) { + Integer n = this.varNames.length() - 1; + this.varNames.removeAt(n); + this.varRegs.removeAt(n); + this.varLLTys.removeAt(n); + } + } + + private defineVar(name: String, llt: String) : String { + Integer n = this.counter; + this.counter = counter + 1; + String reg = "%v${n}"; + this.emit("${reg} = alloca ${llt}"); + this.varNames.append(name); + this.varRegs.append(reg); + this.varLLTys.append(llt); + this.varElemClass.append(""); + return reg; + } + + private defineVarWithElem(name: String, llt: String, elemCls: String) : String { + Integer n = this.counter; + this.counter = counter + 1; + String reg = "%v${n}"; + this.emit("${reg} = alloca ${llt}"); + this.varNames.append(name); + this.varRegs.append(reg); + this.varLLTys.append(llt); + this.varElemClass.append(elemCls); + return reg; + } + + private lookupElemClass(name: String) : String { + Integer i = this.varNames.length() - 1; + while (i >= 0) { + if (this.varNames.get(i) == name) { return this.varElemClass.get(i); } + i = i - 1; + } + return ""; + } + + private lookupReg(name: String) : String { + Integer i = this.varNames.length() - 1; + while (i >= 0) { + if (this.varNames.get(i) == name) { return this.varRegs.get(i); } + i = i - 1; + } + return ""; + } + + private lookupLLTy(name: String) : String { + Integer i = this.varNames.length() - 1; + while (i >= 0) { + if (this.varNames.get(i) == name) { return this.varLLTys.get(i); } + i = i - 1; + } + return "i8*"; + } + + private nonStaticFields(className: String) : List { + List all = this.tc.allFieldsOf(className); + Integer allLen = all.length(); + List res = List(); + Integer i = 0; + while (i < allLen) { + FieldDecl fd = all.get(i) as FieldDecl; + if (!fd.isStatic) { res.append(fd); } + i = i + 1; + } + return res; + } + + private structByteSize(className: String) : Integer { + List nsList = this.nonStaticFields(className); + Integer sz = (1 + nsList.length()) * 8; + return sz; + } + + private fieldSlot(className: String, fieldName: String) : Integer { + List fs = this.nonStaticFields(className); + Integer i = 0; + while (i < fs.length()) { + FieldDecl fd = fs.get(i) as FieldDecl; + if (fd.name == fieldName) { return i + 1; } + i = i + 1; + } + return -1; + } + + private fieldLLTy(className: String, fieldName: String) : String { + List fs = this.nonStaticFields(className); + Integer i = 0; + while (i < fs.length()) { + FieldDecl fd = fs.get(i) as FieldDecl; + if (fd.name == fieldName) { return this.llvmTy(fd.ty); } + i = i + 1; + } + return "i8*"; + } + + private toBool(val: String) : String { + String r = this.nextReg(); + this.emit("${r} = trunc i64 ${val} to i1"); + return r; + } + + private fromBool(val: String) : String { + String r = this.nextReg(); + this.emit("${r} = zext i1 ${val} to i64"); + return r; + } + + public emitExpr(e: Expr) : String { + Integer k = e.kind; + + if (k == ExprKind.NULL_LIT) { return "null"; } + if (k == ExprKind.INT_LIT) { return "${e.intVal}"; } + if (k == ExprKind.CHAR_LIT) { return "${e.intVal}"; } + if (k == ExprKind.FLOAT_LIT) { + String fstr = "${e.floatVal}"; + if (!fstr.contains(".") && !fstr.contains("e")) { fstr = fstr.concat(".0"); } + return fstr; + } + if (k == ExprKind.BOOL_LIT) { if (e.boolVal) { return "1"; } return "0"; } + if (k == ExprKind.STR_LIT) { return this.emitStrLit(e.strVal); } + if (k == ExprKind.STR_INTERP) { return this.emitStrInterp(e); } + if (k == ExprKind.THIS) { return "%self"; } + + if (k == ExprKind.SUPER) { + String r = this.nextReg(); + this.emit("${r} = bitcast %${this.selfType}* %self to i8*"); + return r; + } + + if (k == ExprKind.IDENT) { + return this.emitLoad(e.strVal); + } + + if (k == ExprKind.FIELD) { + return this.emitFieldLoad(e); + } + + if (k == ExprKind.NULL_SAFE) { + return this.emitNullSafe(e); + } + + if (k == ExprKind.METHOD) { + return this.emitMethodCall(e); + } + + if (k == ExprKind.STATIC_CALL) { + return this.emitStaticCall(e); + } + + if (k == ExprKind.CTOR) { + return this.emitCtorCall(e); + } + + if (k == ExprKind.BINOP) { + return this.emitBinOp(e); + } + + if (k == ExprKind.UNARY) { + return this.emitUnaryOp(e); + } + + if (k == ExprKind.CAST) { + return this.emitCast(e); + } + + if (k == ExprKind.TERNARY) { + return this.emitTernary(e); + } + + if (k == ExprKind.INDEX) { + return this.emitIndex(e); + } + + if (k == ExprKind.NULL_COALESCE) { + return this.emitNullCoalesce(e); + } + + if (k == ExprKind.LAMBDA) { + return "null"; + } + + if (k == ExprKind.AWAIT) { + return this.emitExpr(e.operand); + } + + if (k == ExprKind.MATCH) { + return this.emitMatchExpr(e); + } + + return "0"; + } + + private emitLoad(name: String) : String { + String reg = this.lookupReg(name); + if (reg == "") { + Integer ci = 0; + Boolean found = false; + while (ci < this.tc.constNames.length()) { + if (this.tc.constNames.get(ci) == name) { found = true; } + if (!found) { ci = ci + 1; } + if (found) { ci = this.tc.constNames.length(); } + } + if (found) { + AstType cty = this.tc.constTypes.get(ci); + String llt2 = this.llvmTy(cty); + String r2 = this.nextReg(); + if (llt2 == "i8*") { + this.emit("${r2} = load i8*, i8** @${name}.ptr"); + } else { + this.emit("${r2} = load ${llt2}, ${llt2}* @${name}"); + } + return r2; + } + return "0"; + } + String llt = this.lookupLLTy(name); + if (llt == "void") { return "0"; } + String r = this.nextReg(); + this.emit("${r} = load ${llt}, ${llt}* ${reg}"); + return r; + } + + private emitFieldLoad(e: Expr) : String { + String objVal = this.emitExpr(e.object); + String cls = this.resolveObjClassName(e.object); + if (cls == "") { + String on = e.object.strVal; + if (on.length() > 0) { + List sfs = this.tc.allFieldsOf(on); + Integer sfi = 0; + while (sfi < sfs.length()) { + FieldDecl sf = sfs.get(sfi); + if (sf.isStatic && sf.name == e.field && sf.init != null) { + return this.emitExpr(sf.init); + } + sfi = sfi + 1; + } + } + return "0"; + } + Integer slot = this.fieldSlot(cls, e.field); + if (slot < 0) { + String r = this.nextReg(); + this.emit("${r} = load i64, i64* null"); + return r; + } + String fllty = this.fieldLLTy(cls, e.field); + String ptr = this.nextReg(); + this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); + String r = this.nextReg(); + this.emit("${r} = load ${fllty}, ${fllty}* ${ptr}"); + return r; + } + + private resolveObjClassName(e: Expr) : String { + if (e == null) { return this.selfType; } + if (e.kind == ExprKind.THIS) { return this.selfType; } + if (e.kind == ExprKind.IDENT) { + String llt = this.lookupLLTy(e.strVal); + if (llt == "%__List*") { return "__List"; } + if (llt == "%__HashMap*") { return "__HashMap"; } + if (llt.startsWith("%") && llt.endsWith("*") && !llt.startsWith("%__")) { + return llt.substring(1, llt.length() - 1); + } + } + if (e.kind == ExprKind.CTOR) { return e.class_; } + if (e.kind == ExprKind.FIELD) { + String cls = this.resolveObjClassName(e.object); + String fllty = this.fieldLLTy(cls, e.field); + if (fllty == "%__List*") { return "__List"; } + if (fllty == "%__HashMap*") { return "__HashMap"; } + if (fllty.startsWith("%") && fllty.endsWith("*") && !fllty.startsWith("%__")) { + return fllty.substring(1, fllty.length() - 1); + } + } + if (e.kind == ExprKind.METHOD) { + if (e.object != null) { + String objCls = this.resolveObjClassName(e.object); + if (objCls == "__List" && e.method == "get") { + if (e.object.kind == ExprKind.FIELD && e.object.object != null) { + String ownerCls = this.resolveObjClassName(e.object.object); + if (ownerCls.length() > 0 && ownerCls != "__List" && ownerCls != "__HashMap") { + List fds = this.tc.allFieldsOf(ownerCls); + Integer fi = 0; + while (fi < fds.length()) { + FieldDecl fd = fds.get(fi) as FieldDecl; + if (fd != null) { + if (fd.name == e.object.field) { + if (fd.ty != null) { + if (fd.ty.kind == TypeKind.LIST && fd.ty.inner != null) { + String en = fd.ty.inner.name; + if (en.length() > 0) { return en; } + } + } + fi = fds.length(); + } + } + fi = fi + 1; + } + } + } + } + if (objCls.length() > 0) { + MethodDecl? mm = this.tc.findMethodOn(objCls, e.method); + if (mm != null) { + if (mm.returnTy != null) { + String rty = this.llvmTy(mm.returnTy); + if (rty == "%__List*") { return "__List"; } + if (rty == "%__HashMap*") { return "__HashMap"; } + if (rty.startsWith("%") && rty.endsWith("*") && !rty.startsWith("%__")) { + return rty.substring(1, rty.length() - 1); + } + } + } + } + } + } + if (e.kind == ExprKind.STATIC_CALL) { + MethodDecl? sm = this.tc.findMethodOn(e.class_, e.method); + if (sm != null) { + if (sm.returnTy != null) { + String rty = this.llvmTy(sm.returnTy); + if (rty == "%__List*") { return "__List"; } + if (rty.startsWith("%") && rty.endsWith("*") && !rty.startsWith("%__")) { + return rty.substring(1, rty.length() - 1); + } + } + } + } + return ""; + } + + private emitNullSafe(e: Expr) : String { + String objVal = this.emitExpr(e.object); + String nullLbl = this.nextLabel(); + String okLbl = this.nextLabel(); + String endLbl = this.nextLabel(); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp eq i8* ${objVal}, null"); + this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${nullLbl}, label %${okLbl}\n"); + this.emitLabel(okLbl); + String resVal = "null"; + if (e.args.length() > 0) { + String cls = this.resolveObjClassName(e.object); + resVal = this.dispatchMethod(cls, objVal, e.field, e.args); + } else { + String cls = this.resolveObjClassName(e.object); + Integer slot = this.fieldSlot(cls, e.field); + if (slot >= 0) { + String fllty = this.fieldLLTy(cls, e.field); + String ptr = this.nextReg(); + this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); + resVal = this.nextReg(); + this.emit("${resVal} = load ${fllty}, ${fllty}* ${ptr}"); + } + } + this.emitBr(endLbl); + this.emitLabel(nullLbl); + this.emitBr(endLbl); + this.emitLabel(endLbl); + return resVal; + } + + private emitCharMethod(charVal: String, method: String) : String { + if (method == "code") { return charVal; } + if (method == "isDigit") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_digit(i64 ${charVal})"); + return r; + } + if (method == "isAlpha") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_alpha(i64 ${charVal})"); + return r; + } + if (method == "isAlphaNum") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_alphanum(i64 ${charVal})"); + return r; + } + if (method == "isUpper") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_upper(i64 ${charVal})"); + return r; + } + if (method == "isLower") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_lower(i64 ${charVal})"); + return r; + } + if (method == "isWhitespace") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_is_space(i64 ${charVal})"); + return r; + } + if (method == "toUpper") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_to_upper(i64 ${charVal})"); + return r; + } + if (method == "toLower") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_char_to_lower(i64 ${charVal})"); + return r; + } + if (method == "toString") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_char_to_string(i64 ${charVal})"); + return r; + } + return charVal; + } + + private emitMethodCall(e: Expr) : String { + String objVal = this.emitExpr(e.object); + String cls = this.resolveObjClassName(e.object); + if (cls == "String") { + return this.emitStringMethod(objVal, e.method, e.args); + } + if (e.object != null && e.object.kind == ExprKind.CHAR_LIT) { + return this.emitCharMethod(objVal, e.method); + } + if (cls != "") { + return this.dispatchMethod(cls, objVal, e.method, e.args); + } + if (objVal == "0") { + String n = e.object.strVal; + if (n == "IO") { return this.emitIO(e.method, e.args); } + if (n == "Math") { return this.emitMath(e.method, e.args); } + if (n == "Memory") { return this.emitMemory(e.method, e.args); } + if (n == "Time") { return this.emitTime(e.method, e.args); } + MethodDecl? sm = this.tc.findMethodOn(n, e.method); + String retLLTy = "i8*"; + if (sm != null) { if (sm.returnTy != null) { retLLTy = this.llvmTy(sm.returnTy); } } + if (retLLTy == "i8*") { + String meth = e.method; + if (meth == "exists" || meth == "success" || meth == "delete") { retLLTy = "i64"; } + if (meth == "write" || meth == "append" || meth == "check") { retLLTy = "void"; } + if (meth == "exec") { retLLTy = "i8*"; } + } + String argStr2 = ""; + Integer si = 0; + while (si < e.args.length()) { + String av2 = this.emitExpr(e.args.get(si)); + String at2 = this.inferExprLLTy(e.args.get(si)); + if (argStr2.length() > 0) { argStr2 = argStr2.concat(", "); } + argStr2 = argStr2.concat("${at2} ${av2}"); + si = si + 1; + } + if (retLLTy == "void") { + this.emit("call void @${n}__${e.method}(${argStr2})"); + return ""; + } + String sr = this.nextReg(); + this.emit("${sr} = call ${retLLTy} @${n}__${e.method}(${argStr2})"); + return sr; + } + return this.emitStringMethod(objVal, e.method, e.args); + } + + private dispatchMethod(cls: String, objVal: String, method: String, args: List) : String { + if (cls == "__List" || cls == "List") { + return this.emitListMethod(objVal, method, args); + } + if (cls == "__HashMap" || cls == "HashMap") { + return this.emitHashMapMethod(objVal, method, args); + } + MethodDecl? m = this.tc.findMethodOn(cls, method); + String retLLTy = "i8*"; + if (m != null) { if (m.returnTy != null) { retLLTy = this.llvmTy(m.returnTy); } } + String argStr = "%${cls}* ${objVal}"; + Integer i = 0; + while (i < args.length()) { + String av = this.emitExpr(args.get(i)); + String at = this.inferExprLLTy(args.get(i)); + argStr = argStr.concat(", ${at} ${av}"); + i = i + 1; + } + if (retLLTy == "void") { + this.emit("call void @${cls}__${method}(${argStr})"); + return ""; + } + String r = this.nextReg(); + this.emit("${r} = call ${retLLTy} @${cls}__${method}(${argStr})"); + return r; + } + + private emitStaticCall(e: Expr) : String { + String cls = e.class_; + String method = e.method; + if (cls == "IO") { return this.emitIO(method, e.args); } + if (cls == "Math") { return this.emitMath(method, e.args); } + if (cls == "Memory") { return this.emitMemory(method, e.args); } + if (cls == "Time") { return this.emitTime(method, e.args); } + MethodDecl? m = this.tc.findMethodOn(cls, method); + String retLLTy = "i8*"; + if (m != null) { if (m.returnTy != null) { retLLTy = this.llvmTy(m.returnTy); } } + String argStr = ""; + Integer i = 0; + while (i < e.args.length()) { + String av = this.emitExpr(e.args.get(i)); + String at = this.inferExprLLTy(e.args.get(i)); + if (argStr.length() > 0) { argStr = argStr.concat(", "); } + argStr = argStr.concat("${at} ${av}"); + i = i + 1; + } + if (retLLTy == "void") { + this.emit("call void @${cls}__${method}(${argStr})"); + return ""; + } + String r = this.nextReg(); + this.emit("${r} = call ${retLLTy} @${cls}__${method}(${argStr})"); + return r; + } + + private emitCtorCall(e: Expr) : String { + String cls = e.class_; + if (cls == "List") { String r = this.nextReg(); this.emit("${r} = call %__List* @__List__new()"); return r; } + if (cls == "HashMap") { String r = this.nextReg(); this.emit("${r} = call %__HashMap* @__HashMap__new()"); return r; } + if (cls == "Pair") { return this.emitPairCtor(e.args); } + if (cls == "__super__") { return ""; } + ConstructorDecl? ctor = this.tc.findConstructor(cls); + if (ctor == null) { + ExternFnDecl? ef = this.tc.findExternFn(cls); + if (ef != null) { + String retLLTy = "i8*"; + if (ef.returnTy != null) { retLLTy = this.llvmTy(ef.returnTy); } + String eArgStr = ""; + Integer ei = 0; + while (ei < e.args.length()) { + String av = this.emitExpr(e.args.get(ei)); + String at = this.inferExprLLTy(e.args.get(ei)); + if (eArgStr.length() > 0) { eArgStr = eArgStr.concat(", "); } + eArgStr = eArgStr.concat("${at} ${av}"); + ei = ei + 1; + } + if (retLLTy == "void") { + this.emit("call void @${cls}(${eArgStr})"); + return ""; + } + String er = this.nextReg(); + this.emit("${er} = call ${retLLTy} @${cls}(${eArgStr})"); + return er; + } + } + String argStr = ""; + Integer i = 0; + while (i < e.args.length()) { + String av = this.emitExpr(e.args.get(i)); + String at = this.inferExprLLTy(e.args.get(i)); + if (argStr.length() > 0) { argStr = argStr.concat(", "); } + argStr = argStr.concat("${at} ${av}"); + i = i + 1; + } + String r = this.nextReg(); + this.emit("${r} = call %${cls}* @${cls}__new(${argStr})"); + return r; + } + + private emitPairCtor(args: List) : String { + String r = this.nextReg(); + this.emit("${r} = call %__Pair* @__Pair__new()"); + if (args.length() >= 1) { + String v0 = this.emitExpr(args.get(0)); + String t0 = this.inferExprLLTy(args.get(0)); + String bc = this.nextReg(); + if (this.isIntTy(t0)) { this.emit("${bc} = inttoptr ${t0} ${v0} to i8*"); } + else { this.emit("${bc} = bitcast ${t0} ${v0} to i8*"); } + this.emit("call void @__Pair__setFirst(%__Pair* ${r}, i8* ${bc})"); + } + if (args.length() >= 2) { + String v1 = this.emitExpr(args.get(1)); + String t1 = this.inferExprLLTy(args.get(1)); + String bc = this.nextReg(); + if (this.isIntTy(t1)) { this.emit("${bc} = inttoptr ${t1} ${v1} to i8*"); } + else { this.emit("${bc} = bitcast ${t1} ${v1} to i8*"); } + this.emit("call void @__Pair__setSecond(%__Pair* ${r}, i8* ${bc})"); + } + return r; + } + + private inferExprLLTy(e: Expr) : String { + if (e == null) { return "i8*"; } + Integer k = e.kind; + if (k == ExprKind.INT_LIT) { return "i64"; } + if (k == ExprKind.CHAR_LIT) { return "i64"; } + if (k == ExprKind.FLOAT_LIT) { return "double"; } + if (k == ExprKind.BOOL_LIT) { return "i64"; } + if (k == ExprKind.STR_LIT) { return "i8*"; } + if (k == ExprKind.NULL_LIT) { return "i8*"; } + if (k == ExprKind.THIS) { return "%${this.selfType}*"; } + if (k == ExprKind.IDENT) { return this.lookupLLTy(e.strVal); } + if (k == ExprKind.CTOR) { + if (e.class_ == "List") { return "%__List*"; } + if (e.class_ == "HashMap" || e.class_ == "Map" || e.class_ == "TreeMap") { return "%__HashMap*"; } + if (e.class_ == "Pair") { return "%__Pair*"; } + if (e.class_ == "__super__") { return "void"; } + ExternFnDecl? ef = this.tc.findExternFn(e.class_); + if (ef != null) { + if (ef.returnTy != null) { return this.llvmTy(ef.returnTy); } + return "void"; + } + return "%${e.class_}*"; + } + if (k == ExprKind.STR_INTERP){ return "i8*"; } + if (k == ExprKind.CAST && e.castTy != null) { return this.llvmTy(e.castTy); } + if (k == ExprKind.FIELD) { + String fcls = this.resolveObjClassName(e.object); + if (fcls != "") { return this.fieldLLTy(fcls, e.field); } + if (e.object != null && e.object.strVal.length() > 0) { + List fsf = this.tc.allFieldsOf(e.object.strVal); + Integer fsi = 0; + while (fsi < fsf.length()) { + FieldDecl fd = fsf.get(fsi) as FieldDecl; + if (fd.isStatic && fd.name == e.field) { return this.llvmTy(fd.ty); } + fsi = fsi + 1; + } + } + return "i64"; + } + if (k == ExprKind.METHOD) { + String method = e.method; + if (method == "length" || method == "indexOf" || method == "charCodeAt" || + method == "compareTo" || method == "contains" || method == "startsWith" || + method == "endsWith" || method == "isEmpty") { return "i64"; } + if (method == "parseFloat" || method == "toFloat") { return "double"; } + if (method == "parseInt" || method == "toInt" || method == "parseHex") { return "i64"; } + if (method == "exists" || method == "success" || method == "delete") { return "i64"; } + if (e.object != null) { + String cls2 = this.resolveObjClassName(e.object); + if (cls2 != "" && cls2 != "String" && cls2 != "__List" && cls2 != "__HashMap") { + MethodDecl? mm = this.tc.findMethodOn(cls2, method); + if (mm != null) { if (mm.returnTy != null) { return this.llvmTy(mm.returnTy); } } + } + if (cls2 == "" && e.object.strVal.length() > 0) { + String llt2 = this.lookupLLTy(e.object.strVal); + if (llt2 == "") { + MethodDecl? mm2 = this.tc.findMethodOn(e.object.strVal, method); + if (mm2 != null) { if (mm2.returnTy != null) { return this.llvmTy(mm2.returnTy); } } + } + } + } + return "i8*"; + } + if (k == ExprKind.STATIC_CALL) { + MethodDecl? sm = this.tc.findMethodOn(e.class_, e.method); + if (sm != null) { if (sm.returnTy != null) { return this.llvmTy(sm.returnTy); } } + return "i8*"; + } + if (k == ExprKind.BINOP) { + Integer op = e.op; + if (op == BinaryOp.EQ || op == BinaryOp.NE || op == BinaryOp.LT || + op == BinaryOp.LE || op == BinaryOp.GT || op == BinaryOp.GE || + op == BinaryOp.AND || op == BinaryOp.OR) { return "i64"; } + return this.inferExprLLTy(e.left); + } + if (k == ExprKind.UNARY) { + if (e.op == UnaryOp.NOT) { return "i64"; } + return this.inferExprLLTy(e.operand); + } + return "i8*"; + } + + private emitBinOp(e: Expr) : String { + Integer op = e.op; + if (op == BinaryOp.ASSIGN || op == BinaryOp.ADD_ASSIGN || + op == BinaryOp.SUB_ASSIGN || op == BinaryOp.MUL_ASSIGN || + op == BinaryOp.DIV_ASSIGN) { + return this.emitAssign(e); + } + String lv = this.emitExpr(e.left); + String rv = this.emitExpr(e.right); + Boolean flt = e.left != null && e.left.kind == ExprKind.FLOAT_LIT; + if (!flt && e.left != null && e.left.kind == ExprKind.IDENT) { + flt = this.lookupLLTy(e.left.strVal) == "double"; + } + String r = this.nextReg(); + if (op == BinaryOp.ADD) { + String addLlt = this.inferExprLLTy(e.left); + if (addLlt == "i8*") { this.emit("${r} = call i8* @__arimo_str_concat(i8* ${lv}, i8* ${rv})"); } + else if (flt) { this.emit("${r} = fadd double ${lv}, ${rv}"); } + else { this.emit("${r} = add i64 ${lv}, ${rv}"); } + } + else if (op == BinaryOp.SUB) { this.emit(flt ? "${r} = fsub double ${lv}, ${rv}" : "${r} = sub i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.MUL) { this.emit(flt ? "${r} = fmul double ${lv}, ${rv}" : "${r} = mul i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.DIV) { this.emit(flt ? "${r} = fdiv double ${lv}, ${rv}" : "${r} = sdiv i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.MOD) { this.emit("${r} = srem i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.BITAND) { this.emit("${r} = and i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.BITOR) { this.emit("${r} = or i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.XOR) { this.emit("${r} = xor i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.SHL) { this.emit("${r} = shl i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.SHR) { this.emit("${r} = ashr i64 ${lv}, ${rv}"); } + else if (op == BinaryOp.AND) { + String lb = this.toBool(lv); + String rb = this.toBool(rv); + String cb = this.nextReg(); + this.emit("${cb} = and i1 ${lb}, ${rb}"); + return this.fromBool(cb); + } + else if (op == BinaryOp.OR) { + String lb = this.toBool(lv); + String rb = this.toBool(rv); + String cb = this.nextReg(); + this.emit("${cb} = or i1 ${lb}, ${rb}"); + return this.fromBool(cb); + } + else if (op == BinaryOp.EQ) { + String llt = this.inferExprLLTy(e.left); + String cmp = this.nextReg(); + if (llt == "double") { this.emit("${cmp} = fcmp oeq double ${lv}, ${rv}"); } + else if (llt == "i8*") { + if (rv == "null" || lv == "null") { + this.emit("${cmp} = icmp eq i8* ${lv}, ${rv}"); + } else { + String cr = this.nextReg(); + this.emit("${cr} = call i32 @strcmp(i8* ${lv}, i8* ${rv})"); + this.emit("${cmp} = icmp eq i32 ${cr}, 0"); + } + } else if (llt.endsWith("*")) { this.emit("${cmp} = icmp eq ${llt} ${lv}, ${rv}"); } + else { this.emit("${cmp} = icmp eq i64 ${lv}, ${rv}"); } + return this.fromBool(cmp); + } + else if (op == BinaryOp.NE) { + String llt = this.inferExprLLTy(e.left); + String cmp = this.nextReg(); + if (llt == "double") { this.emit("${cmp} = fcmp one double ${lv}, ${rv}"); } + else if (llt == "i8*") { + if (rv == "null" || lv == "null") { + this.emit("${cmp} = icmp ne i8* ${lv}, ${rv}"); + } else { + String cr = this.nextReg(); + this.emit("${cr} = call i32 @strcmp(i8* ${lv}, i8* ${rv})"); + this.emit("${cmp} = icmp ne i32 ${cr}, 0"); + } + } else if (llt.endsWith("*")) { this.emit("${cmp} = icmp ne ${llt} ${lv}, ${rv}"); } + else { this.emit("${cmp} = icmp ne i64 ${lv}, ${rv}"); } + return this.fromBool(cmp); + } + else if (op == BinaryOp.LT) { String c = this.nextReg(); this.emit("${c} = icmp slt i64 ${lv}, ${rv}"); return this.fromBool(c); } + else if (op == BinaryOp.LE) { String c = this.nextReg(); this.emit("${c} = icmp sle i64 ${lv}, ${rv}"); return this.fromBool(c); } + else if (op == BinaryOp.GT) { String c = this.nextReg(); this.emit("${c} = icmp sgt i64 ${lv}, ${rv}"); return this.fromBool(c); } + else if (op == BinaryOp.GE) { String c = this.nextReg(); this.emit("${c} = icmp sge i64 ${lv}, ${rv}"); return this.fromBool(c); } + else { this.emit("${r} = add i64 0, 0"); } + return r; + } + + private emitAssign(e: Expr) : String { + String rv = this.emitExpr(e.right); + String rt = this.inferExprLLTy(e.right); + this.storeToLval(e.left, rv, rt); + return rv; + } + + private storeToLval(target: Expr, val: String, ty: String) : Void { + if (target == null) { return; } + if (target.kind == ExprKind.IDENT) { + String reg = this.lookupReg(target.strVal); + if (reg == "") { + String llt = ty; + reg = this.defineVar(target.strVal, llt); + } + this.emit("store ${ty} ${val}, ${ty}* ${reg}"); + } else if (target.kind == ExprKind.FIELD) { + String objVal = this.emitExpr(target.object); + String cls = this.resolveObjClassName(target.object); + Integer slot = this.fieldSlot(cls, target.field); + if (slot >= 0) { + String fllty = this.fieldLLTy(cls, target.field); + String ptr = this.nextReg(); + this.emit("${ptr} = getelementptr inbounds %${cls}, %${cls}* ${objVal}, i32 0, i32 ${slot}"); + String sv = val; + if (fllty != ty && ty != "void") { + String bc = this.nextReg(); + if (this.isIntTy(ty) && fllty.endsWith("*")) { this.emit("${bc} = inttoptr ${ty} ${val} to ${fllty}"); } + else if (ty.endsWith("*") && this.isIntTy(fllty)) { this.emit("${bc} = ptrtoint ${ty} ${val} to ${fllty}"); } + else { this.emit("${bc} = bitcast ${ty} ${val} to ${fllty}"); } + sv = bc; + } + this.emit("store ${fllty} ${sv}, ${fllty}* ${ptr}"); + } + } else if (target.kind == ExprKind.INDEX) { + String listVal = this.emitExpr(target.object); + String idxVal = this.emitExpr(target.index); + String bc = this.nextReg(); + if (this.isIntTy(ty)) { this.emit("${bc} = inttoptr ${ty} ${val} to i8*"); } + else { this.emit("${bc} = bitcast ${ty} ${val} to i8*"); } + this.emit("call void @__List__set(%__List* ${listVal}, i64 ${idxVal}, i8* ${bc})"); + } + } + + private emitUnaryOp(e: Expr) : String { + Integer op = e.op; + if (op == UnaryOp.NOT) { + String v = this.emitExpr(e.operand); + String b = this.toBool(v); + String n = this.nextReg(); + this.emit("${n} = xor i1 ${b}, true"); + return this.fromBool(n); + } + if (op == UnaryOp.NEG) { + String v = this.emitExpr(e.operand); + String llt = this.inferExprLLTy(e.operand); + String r = this.nextReg(); + if (llt == "double") { this.emit("${r} = fneg double ${v}"); } + else { this.emit("${r} = sub i64 0, ${v}"); } + return r; + } + if (op == UnaryOp.BITNOT) { + String v = this.emitExpr(e.operand); + String r = this.nextReg(); + this.emit("${r} = xor i64 ${v}, -1"); + return r; + } + if (op == UnaryOp.PRE_INC || op == UnaryOp.POST_INC) { + String v = this.emitExpr(e.operand); + String r = this.nextReg(); + this.emit("${r} = add i64 ${v}, 1"); + this.storeToLval(e.operand, r, "i64"); + return r; + } + if (op == UnaryOp.PRE_DEC || op == UnaryOp.POST_DEC) { + String v = this.emitExpr(e.operand); + String r = this.nextReg(); + this.emit("${r} = sub i64 ${v}, 1"); + this.storeToLval(e.operand, r, "i64"); + return r; + } + return "0"; + } + + private isIntTy(t: String) : Boolean { + return t == "i64" || t == "i32" || t == "i16" || t == "i8" || t == "i1"; + } + + private emitCast(e: Expr) : String { + String v = this.emitExpr(e.operand); + String from = this.inferExprLLTy(e.operand); + String to = this.llvmTy(e.castTy); + if (from == to) { return v; } + String r = this.nextReg(); + if (from == "double" && to == "i64") { this.emit("${r} = fptosi double ${v} to i64"); return r; } + if (from == "i64" && to == "double") { this.emit("${r} = sitofp i64 ${v} to double"); return r; } + if (from == "double" && to == "i8*") { this.emit("${r} = inttoptr i64 0 to i8*"); return r; } + if (to == "i8*") { + if (this.isIntTy(from)) { this.emit("${r} = inttoptr ${from} ${v} to i8*"); } + else { this.emit("${r} = bitcast ${from} ${v} to i8*"); } + return r; + } + if (from == "i8*") { + if (this.isIntTy(to)) { this.emit("${r} = ptrtoint i8* ${v} to ${to}"); } + else { this.emit("${r} = bitcast i8* ${v} to ${to}"); } + return r; + } + if (this.isIntTy(from) && to.endsWith("*")) { this.emit("${r} = inttoptr ${from} ${v} to ${to}"); return r; } + if (from.endsWith("*") && this.isIntTy(to)) { this.emit("${r} = ptrtoint ${from} ${v} to ${to}"); return r; } + if (from.startsWith("%") && to.startsWith("%")) { this.emit("${r} = bitcast ${from} ${v} to ${to}"); return r; } + this.emit("${r} = bitcast ${from} ${v} to ${to}"); + return r; + } + + private emitTernary(e: Expr) : String { + String cond = this.emitExpr(e.cond); + String condB = this.toBool(cond); + String thenL = this.nextLabel(); + String elseL = this.nextLabel(); + String endL = this.nextLabel(); + this.curFn = this.curFn.concat(" br i1 ${condB}, label %${thenL}, label %${elseL}\n"); + this.emitLabel(thenL); + String tv = this.emitExpr(e.then_); + String tty = this.inferExprLLTy(e.then_); + this.emitBr(endL); + this.emitLabel(elseL); + String ev = this.emitExpr(e.else_); + this.emitBr(endL); + this.emitLabel(endL); + String r = this.nextReg(); + this.emit("${r} = phi ${tty} [ ${tv}, %${thenL} ], [ ${ev}, %${elseL} ]"); + return r; + } + + private emitIndex(e: Expr) : String { + String listVal = this.emitExpr(e.object); + String idxVal = this.emitExpr(e.index); + String raw = this.nextReg(); + this.emit("${raw} = call i8* @__List__get(%__List* ${listVal}, i64 ${idxVal})"); + return raw; + } + + private emitNullCoalesce(e: Expr) : String { + String lv = this.emitExpr(e.left); + String nullL = this.nextLabel(); + String okL = this.nextLabel(); + String endL = this.nextLabel(); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp eq i8* ${lv}, null"); + this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${nullL}, label %${okL}\n"); + this.emitLabel(okL); + this.emitBr(endL); + this.emitLabel(nullL); + String rv = this.emitExpr(e.right); + this.emitBr(endL); + this.emitLabel(endL); + String r = this.nextReg(); + this.emit("${r} = phi i8* [ ${lv}, %${okL} ], [ ${rv}, %${nullL} ]"); + return r; + } + + private emitMatchExpr(e: Expr?) : String { + if (e != null) { + String subj = this.emitExpr(e.matchExpr); + String endL = this.nextLabel(); + String resReg = this.nextReg(); + this.emit("${resReg} = alloca i8*"); + Integer i = 0; + while (i < e.arms.length()) { + MatchArm arm = e.arms.get(i); + String bodyL = this.nextLabel(); + String nextL = this.nextLabel(); + if (arm.pattern.kind == MatchPatternKind.WILDCARD || arm.pattern.kind == MatchPatternKind.BINDING) { + this.emitBr(bodyL); + } else if (arm.pattern.kind == MatchPatternKind.VARIANT) { + Integer varIdx = this.tc.enumVariantIndex(arm.pattern.enumName, arm.pattern.variant); + String tagPtr = this.nextReg(); + this.emit("${tagPtr} = bitcast i8* ${subj} to i64*"); + String tag = this.nextReg(); + this.emit("${tag} = load i64, i64* ${tagPtr}"); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp eq i64 ${tag}, ${varIdx}"); + this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${nextL}\n"); + } else { + this.emitBr(bodyL); + } + this.emitLabel(bodyL); + String bv = this.emitExpr(arm.body); + String bc = this.nextReg(); + this.emit("${bc} = bitcast i8* ${bv} to i8*"); + this.emit("store i8* ${bc}, i8** ${resReg}"); + this.emitBr(endL); + this.emitLabel(nextL); + i = i + 1; + } + this.emitBr(endL); + this.emitLabel(endL); + String final = this.nextReg(); + this.emit("${final} = load i8*, i8** ${resReg}"); + return final; + } + return "0"; + } + + private emitStrInterp(e: Expr) : String { + if (e.parts == null || e.parts.length() == 0) { return this.emitStrLit(""); } + String acc = ""; + Integer i = 0; + while (i < e.parts.length()) { + StrPart p = e.parts.get(i); + String pv = ""; + if (p.isLit) { + pv = this.emitStrLit(p.lit); + } else { + String exprVal = this.emitExpr(p.expr); + String exprTy = this.inferExprLLTy(p.expr); + if (exprTy == "i8*") { + pv = exprVal; + } else if (exprTy == "double") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_f64_to_str(double ${exprVal})"); + pv = r; + } else { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_i64_to_str(i64 ${exprVal})"); + pv = r; + } + } + if (acc == "") { + acc = pv; + } else { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_concat(i8* ${acc}, i8* ${pv})"); + acc = r; + } + i = i + 1; + } + return acc; + } + + private emitIO(method: String, args: List) : String { + String fmtNl = this.emitStrConst("%s\n"); + String fmtI = this.emitStrConst("%lld\n"); + String fmtF = this.emitStrConst("%f\n"); + String fmtNlP = this.strGEP(fmtNl, 4); + String fmtIP = this.strGEP(fmtI, 6); + String fmtFP = this.strGEP(fmtF, 4); + if (method == "println" || method == "print" || method == "error") { + if (args.length() == 0) { + String nl = this.emitStrLit("\n"); + this.emit("call i32 (i8*, ...) @printf(i8* ${fmtNlP}, i8* ${nl})"); + this.emit("call i32 @fflush(i8* null)"); + return ""; + } + Expr arg = args.get(0); + String av = this.emitExpr(arg); + String at = this.inferExprLLTy(arg); + if (at == "double") { + this.emit("call i32 (i8*, ...) @printf(i8* ${fmtFP}, double ${av})"); + } else if (at == "i8*") { + String use = fmtNlP; + if (method == "print") { use = this.strGEP(this.emitStrConst("%s"), 3); } + this.emit("call i32 (i8*, ...) @printf(i8* ${use}, i8* ${av})"); + } else { + this.emit("call i32 (i8*, ...) @printf(i8* ${fmtIP}, i64 ${av})"); + } + this.emit("call i32 @fflush(i8* null)"); + } else if (method == "read") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_readline()"); + return r; + } + return ""; + } + + private emitMath(method: String, args: List) : String { + if (method == "PI") { String r = this.nextReg(); this.emit("${r} = fadd double 0.0, 3.14159265358979"); return r; } + if (method == "E") { String r = this.nextReg(); this.emit("${r} = fadd double 0.0, 2.71828182845905"); return r; } + String v0 = ""; + if (args.length() >= 1) { v0 = this.emitExpr(args.get(0)); } + if (method == "sqrt") { String r = this.nextReg(); this.emit("${r} = call double @llvm.sqrt.f64(double ${v0})"); return r; } + if (method == "abs") { String r = this.nextReg(); this.emit("${r} = call double @llvm.fabs.f64(double ${v0})"); return r; } + if (method == "floor") { String r = this.nextReg(); this.emit("${r} = call double @llvm.floor.f64(double ${v0})"); return r; } + if (method == "ceil") { String r = this.nextReg(); this.emit("${r} = call double @llvm.ceil.f64(double ${v0})"); return r; } + if (method == "sin") { String r = this.nextReg(); this.emit("${r} = call double @llvm.sin.f64(double ${v0})"); return r; } + if (method == "cos") { String r = this.nextReg(); this.emit("${r} = call double @llvm.cos.f64(double ${v0})"); return r; } + if (method == "log") { String r = this.nextReg(); this.emit("${r} = call double @llvm.log.f64(double ${v0})"); return r; } + if (method == "pow") { + String v1 = ""; + if (args.length() >= 2) { v1 = this.emitExpr(args.get(1)); } + String r = this.nextReg(); + this.emit("${r} = call double @llvm.pow.f64(double ${v0}, double ${v1})"); + return r; + } + if (method == "min" || method == "max") { + String v1 = ""; + if (args.length() >= 2) { v1 = this.emitExpr(args.get(1)); } + String t0 = "i64"; + String r = this.nextReg(); + String op = "slt"; + if (method == "max") { op = "sgt"; } + String cmp = this.nextReg(); + this.emit("${cmp} = icmp ${op} i64 ${v0}, ${v1}"); + this.emit("${r} = select i1 ${cmp}, i64 ${v0}, i64 ${v1}"); + return r; + } + return "0"; + } + + private emitMemory(method: String, args: List) : String { + if (method == "alloc") { + String sz = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i8* @malloc(i64 ${sz})"); + return r; + } + if (method == "free") { + String ptr = this.emitExpr(args.get(0)); + this.emit("call void @free(i8* ${ptr})"); + return ""; + } + return "0"; + } + + private emitTime(method: String, args: List) : String { + if (method == "now") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_time_now()"); + return r; + } + if (method == "generateId") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_generate_id()"); + return r; + } + if (method == "nowMillis") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_now_millis()"); + return r; + } + return "0"; + } + + private emitStringMethod(strVal: String, method: String, args: List) : String { + if (method == "length") { + String r = this.nextReg(); + this.emit("${r} = call i64 @strlen(i8* ${strVal})"); + return r; + } + if (method == "concat") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_concat(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "contains") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_str_contains(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "startsWith") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_str_starts_with(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "endsWith") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_str_ends_with(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "substring") { + String from = this.emitExpr(args.get(0)); + String to = this.emitExpr(args.get(1)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_substring(i8* ${strVal}, i64 ${from}, i64 ${to})"); + return r; + } + if (method == "indexOf") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_str_index_of(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "toLower") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_to_lower(i8* ${strVal})"); + return r; + } + if (method == "toUpper") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_to_upper(i8* ${strVal})"); + return r; + } + if (method == "trim") { + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_trim(i8* ${strVal})"); + return r; + } + if (method == "parseInt") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__arimo_str_parse_int(i8* ${strVal})"); + return r; + } + if (method == "parseFloat") { + String r = this.nextReg(); + this.emit("${r} = call double @__arimo_str_parse_float(i8* ${strVal})"); + return r; + } + if (method == "compareTo") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i32 @strcmp(i8* ${strVal}, i8* ${a})"); + String r2 = this.nextReg(); + this.emit("${r2} = sext i32 ${r} to i64"); + return r2; + } + if (method == "replace") { + String a = this.emitExpr(args.get(0)); + String b = this.emitExpr(args.get(1)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__arimo_str_replace(i8* ${strVal}, i8* ${a}, i8* ${b})"); + return r; + } + if (method == "split") { + String a = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call %__List* @__arimo_str_split(i8* ${strVal}, i8* ${a})"); + return r; + } + if (method == "equals") { + String a = this.emitExpr(args.get(0)); + String cr = this.nextReg(); + this.emit("${cr} = call i32 @strcmp(i8* ${strVal}, i8* ${a})"); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp eq i32 ${cr}, 0"); + return this.fromBool(cmp); + } + return "0"; + } + + private emitListMethod(listVal: String, method: String, args: List) : String { + if (method == "append" || method == "add") { + String v = this.emitExpr(args.get(0)); + String t = this.inferExprLLTy(args.get(0)); + String bc = v; + if (t != "i8*") { + bc = this.nextReg(); + if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } + else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } + } + this.emit("call void @__List__append(%__List* ${listVal}, i8* ${bc})"); + return ""; + } + if (method == "get") { + String i = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__List__get(%__List* ${listVal}, i64 ${i})"); + return r; + } + if (method == "length" || method == "size") { + String r = this.nextReg(); + this.emit("${r} = call i64 @__List__length(%__List* ${listVal})"); + return r; + } + if (method == "removeAt" || method == "remove") { + String i = this.emitExpr(args.get(0)); + this.emit("call void @__List__removeAt(%__List* ${listVal}, i64 ${i})"); + return ""; + } + if (method == "set") { + String i = this.emitExpr(args.get(0)); + String v = this.emitExpr(args.get(1)); + String t = this.inferExprLLTy(args.get(1)); + String bc = v; + if (t != "i8*") { + bc = this.nextReg(); + if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } + else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } + } + this.emit("call void @__List__set(%__List* ${listVal}, i64 ${i}, i8* ${bc})"); + return ""; + } + return "0"; + } + + private emitHashMapMethod(mapVal: String, method: String, args: List) : String { + if (method == "set" || method == "put") { + String k = this.emitExpr(args.get(0)); + String v = this.emitExpr(args.get(1)); + String t = this.inferExprLLTy(args.get(1)); + String bc = v; + if (t != "i8*") { + bc = this.nextReg(); + if (this.isIntTy(t)) { this.emit("${bc} = inttoptr ${t} ${v} to i8*"); } + else { this.emit("${bc} = bitcast ${t} ${v} to i8*"); } + } + this.emit("call void @__HashMap__set(%__HashMap* ${mapVal}, i8* ${k}, i8* ${bc})"); + return ""; + } + if (method == "get") { + String k = this.emitExpr(args.get(0)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__HashMap__get(%__HashMap* ${mapVal}, i8* ${k})"); + return r; + } + if (method == "getOrDefault") { + String k = this.emitExpr(args.get(0)); + String d = this.emitExpr(args.get(1)); + String r = this.nextReg(); + this.emit("${r} = call i8* @__HashMap__getOrDefault(%__HashMap* ${mapVal}, i8* ${k}, i8* ${d})"); + return r; + } + return "0"; + } + + public emitStmt(s: Stmt) : Void { + if (s == null) { return; } + Integer k = s.kind; + + if (k == StmtKind.VAR_DECL) { + String llt = this.llvmTy(s.ty); + String reg = this.defineVar(s.name, llt); + if (s.initExpr != null) { + String v = this.emitExpr(s.initExpr); + String vt = this.inferExprLLTy(s.initExpr); + String sv = v; + if (vt != llt && llt != "void" && vt != "void") { + String bc = this.nextReg(); + if (this.isIntTy(vt) && llt.endsWith("*")) { this.emit("${bc} = inttoptr ${vt} ${v} to ${llt}"); } + else if (vt.endsWith("*") && this.isIntTy(llt)) { this.emit("${bc} = ptrtoint ${vt} ${v} to ${llt}"); } + else { this.emit("${bc} = bitcast ${vt} ${v} to ${llt}"); } + sv = bc; + } + if (llt != "void") { this.emit("store ${llt} ${sv}, ${llt}* ${reg}"); } + } else if (llt != "void") { + if (llt == "i64" || llt == "i32" || llt == "i16" || llt == "i8") { + this.emit("store ${llt} 0, ${llt}* ${reg}"); + } else if (llt == "double") { + this.emit("store double 0.0, double* ${reg}"); + } else { + this.emit("store ${llt} null, ${llt}* ${reg}"); + } + } + } + + else if (k == StmtKind.EXPR) { + this.emitExpr(s.expr); + } + + else if (k == StmtKind.RETURN) { + if (s.expr == null || this.retTyStr == "void") { + this.emit("ret void"); + } else { + String v = this.emitExpr(s.expr); + String vt = this.inferExprLLTy(s.expr); + String rv = v; + if (vt != this.retTyStr && vt != "void" && this.retTyStr != "void") { + String bc = this.nextReg(); + if (this.isIntTy(vt) && this.retTyStr.endsWith("*")) { this.emit("${bc} = inttoptr ${vt} ${v} to ${this.retTyStr}"); } + else if (vt.endsWith("*") && this.isIntTy(this.retTyStr)) { this.emit("${bc} = ptrtoint ${vt} ${v} to ${this.retTyStr}"); } + else { this.emit("${bc} = bitcast ${vt} ${v} to ${this.retTyStr}"); } + rv = bc; + } + this.emit("ret ${this.retTyStr} ${rv}"); + } + String dead = this.nextLabel(); + this.emitLabel(dead); + } + + else if (k == StmtKind.THROW) { + this.emitExpr(s.expr); + this.emit("call void @abort()"); + this.emit("unreachable"); + String dead = this.nextLabel(); + this.emitLabel(dead); + } + + else if (k == StmtKind.IF) { + String cond = this.emitExpr(s.cond); + String condB = this.toBool(cond); + String thenL = this.nextLabel(); + String endL = this.nextLabel(); + Boolean hasElse = s.elseBody.length() > 0; + Boolean hasEi = s.elseIfs.length() > 0 && s.elseIfs.length() > 0; + String falseL = endL; + if (hasElse || hasEi) { falseL = this.nextLabel(); } + this.curFn = this.curFn.concat(" br i1 ${condB}, label %${thenL}, label %${falseL}\n"); + this.emitLabel(thenL); + this.pushScope(); + this.emitStmts(s.thenBody); + this.popScope(); + this.emitBr(endL); + if (hasEi) { + this.emitLabel(falseL); + Integer ei = 0; + while (ei < s.elseIfs.length()) { + ElseIfBranch eib = s.elseIfs.get(ei); + String eic = this.emitExpr(eib.cond); + String eiB = this.toBool(eic); + String eiBody = this.nextLabel(); + String eiNext = endL; + if (ei + 1 < s.elseIfs.length() || hasElse) { eiNext = this.nextLabel(); } + this.curFn = this.curFn.concat(" br i1 ${eiB}, label %${eiBody}, label %${eiNext}\n"); + this.emitLabel(eiBody); + this.pushScope(); + this.emitStmts(eib.body); + this.popScope(); + this.emitBr(endL); + falseL = eiNext; + if (eiNext != endL) { this.emitLabel(eiNext); } + ei = ei + 1; + } + if (hasElse) { + this.pushScope(); + this.emitStmts(s.elseBody); + this.popScope(); + this.emitBr(endL); + } + } else if (hasElse) { + this.emitLabel(falseL); + this.pushScope(); + this.emitStmts(s.elseBody); + this.popScope(); + this.emitBr(endL); + } + this.emitLabel(endL); + } + + else if (k == StmtKind.WHILE) { + String condL = this.nextLabel(); + String bodyL = this.nextLabel(); + String exitL = this.nextLabel(); + this.loopConds.append(condL); + this.loopExits.append(exitL); + this.emitBr(condL); + this.emitLabel(condL); + String cond = this.emitExpr(s.cond); + String condB = this.toBool(cond); + this.curFn = this.curFn.concat(" br i1 ${condB}, label %${bodyL}, label %${exitL}\n"); + this.emitLabel(bodyL); + this.pushScope(); + this.emitStmts(s.body); + this.popScope(); + this.emitBr(condL); + this.emitLabel(exitL); + Integer last = this.loopConds.length() - 1; + this.loopConds.removeAt(last); + this.loopExits.removeAt(last); + } + + else if (k == StmtKind.FOR) { + String condL = this.nextLabel(); + String bodyL = this.nextLabel(); + String exitL = this.nextLabel(); + this.loopConds.append(condL); + this.loopExits.append(exitL); + this.pushScope(); + if (s.forInit != null) { this.emitStmt(s.forInit); } + this.emitBr(condL); + this.emitLabel(condL); + String cond = this.emitExpr(s.forCond); + String condB = this.toBool(cond); + this.curFn = this.curFn.concat(" br i1 ${condB}, label %${bodyL}, label %${exitL}\n"); + this.emitLabel(bodyL); + this.emitStmts(s.forBody); + if (s.forStep != null) { this.emitExpr(s.forStep); } + this.popScope(); + this.emitBr(condL); + this.emitLabel(exitL); + Integer last = this.loopConds.length() - 1; + this.loopConds.removeAt(last); + this.loopExits.removeAt(last); + } + + else if (k == StmtKind.FOR_EACH) { + String iterVal = this.emitExpr(s.iterable); + String lenReg = this.nextReg(); + this.emit("${lenReg} = call i64 @__List__length(%__List* ${iterVal})"); + String idxReg = this.defineVar("__i_${this.labelCnt}", "i64"); + this.emit("store i64 0, i64* ${idxReg}"); + String condL = this.nextLabel(); + String bodyL = this.nextLabel(); + String exitL = this.nextLabel(); + this.loopConds.append(condL); + this.loopExits.append(exitL); + this.emitBr(condL); + this.emitLabel(condL); + String idxVal = this.nextReg(); + this.emit("${idxVal} = load i64, i64* ${idxReg}"); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp slt i64 ${idxVal}, ${lenReg}"); + this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${exitL}\n"); + this.emitLabel(bodyL); + this.pushScope(); + String llt = this.llvmTy(s.iterTy); + String elemReg = this.defineVar(s.iterName, llt); + String raw = this.nextReg(); + this.emit("${raw} = call i8* @__List__get(%__List* ${iterVal}, i64 ${idxVal})"); + if (llt == "i8*") { + this.emit("store i8* ${raw}, i8** ${elemReg}"); + } else if (llt == "i64") { + String ptrCast = this.nextReg(); + this.emit("${ptrCast} = ptrtoint i8* ${raw} to i64"); + this.emit("store i64 ${ptrCast}, i64* ${elemReg}"); + } else { + String bc = this.nextReg(); + this.emit("${bc} = bitcast i8* ${raw} to ${llt}"); + this.emit("store ${llt} ${bc}, ${llt}* ${elemReg}"); + } + this.emitStmts(s.body); + String nextIdx = this.nextReg(); + this.emit("${nextIdx} = add i64 ${idxVal}, 1"); + this.emit("store i64 ${nextIdx}, i64* ${idxReg}"); + this.popScope(); + this.emitBr(condL); + this.emitLabel(exitL); + Integer last = this.loopConds.length() - 1; + this.loopConds.removeAt(last); + this.loopExits.removeAt(last); + } + + else if (k == StmtKind.SWITCH) { + String sv = this.emitExpr(s.switchExpr); + String endL = this.nextLabel(); + this.loopExits.append(endL); + Integer i = 0; + while (i < s.cases.length()) { + SwitchCase sc = s.cases.get(i); + String bodyL = this.nextLabel(); + String nextL = endL; + if (i + 1 < s.cases.length()) { nextL = this.nextLabel(); } + if (!sc.isDefault) { + String pv = this.emitExpr(sc.pattern); + String cmp = this.nextReg(); + this.emit("${cmp} = icmp eq i64 ${sv}, ${pv}"); + this.curFn = this.curFn.concat(" br i1 ${cmp}, label %${bodyL}, label %${nextL}\n"); + this.emitLabel(bodyL); + } else { + this.emitBr(bodyL); + this.emitLabel(bodyL); + } + this.pushScope(); + this.emitStmts(sc.body); + this.popScope(); + this.emitBr(endL); + if (nextL != endL) { this.emitLabel(nextL); } + i = i + 1; + } + this.emitLabel(endL); + Integer last = this.loopExits.length() - 1; + this.loopExits.removeAt(last); + } + + else if (k == StmtKind.TRY) { + this.pushScope(); + this.emitStmts(s.tryBody); + this.popScope(); + if (s.catches.length() > 0) { + Integer i = 0; + while (i < s.catches.length()) { + CatchClause cc = s.catches.get(i); + this.pushScope(); + String llt = this.llvmTy(cc.exType); + String reg = this.defineVar(cc.name, llt); + if (llt != "void") { this.emit("store ${llt} null, ${llt}* ${reg}"); } + this.emitStmts(cc.body); + this.popScope(); + i = i + 1; + } + } + if (s.finallyBody.length() > 0) { + this.pushScope(); + this.emitStmts(s.finallyBody); + this.popScope(); + } + } + + else if (k == StmtKind.BREAK) { + Integer last = this.loopExits.length() - 1; + if (last >= 0) { this.emitBr(this.loopExits.get(last)); } + String dead = this.nextLabel(); + this.emitLabel(dead); + } + + else if (k == StmtKind.CONTINUE) { + Integer last = this.loopConds.length() - 1; + if (last >= 0) { this.emitBr(this.loopConds.get(last)); } + String dead = this.nextLabel(); + this.emitLabel(dead); + } + + else if (k == StmtKind.BLOCK) { + this.pushScope(); + this.emitStmts(s.body); + this.popScope(); + } + + else if (k == StmtKind.ASM) { + this.emit("call void asm sideeffect \"${s.asmText}\", \"\"()"); + } + + else if (k == StmtKind.DEFER) { + this.emitExpr(s.expr); + } + } + + private emitStmts(stmts: List) : Void { + if (stmts.length() >= 0) { + Integer i = 0; + while (i < stmts.length()) { + this.emitStmt(stmts.get(i)); + i = i + 1; + } + } + } + + private emitConstructor(c: ClassDecl?) : Void { + if (c != null) { + if (c.ctor != null) { this.emitConstructorInner(c, c.ctor); } + } + } + + private emitConstructorInner(c: ClassDecl, ctor: ConstructorDecl) : Void { + if (ctor.params == null) { return; } + String paramStr = ""; + Integer i = 0; + while (i < ctor.params.length()) { + Param p = ctor.params.get(i); + String pt = this.llvmTy(p.ty); + if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } + paramStr = paramStr.concat("${pt} %p.${p.name}"); + i = i + 1; + } + String sig = "define %${c.name}* @${c.name}__new(${paramStr})"; + this.curFn = ""; + this.selfType = c.name; + this.retTyStr = "%${c.name}*"; + this.varNames = List(); + this.varRegs = List(); + this.varLLTys = List(); + this.varElemClass = List(); + this.scopeMarks = List(); + this.loopExits = List(); + this.loopConds = List(); + Integer sz = this.structByteSize(c.name); + String raw = this.nextReg(); + this.emit("${raw} = call i8* @malloc(i64 ${sz})"); + String self = this.nextReg(); + this.emit("${self} = bitcast i8* ${raw} to %${c.name}*"); + String zraw = this.nextReg(); + this.emit("${zraw} = bitcast %${c.name}* ${self} to i8*"); + this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); + String rcPtr = this.nextReg(); + this.emit("${rcPtr} = getelementptr inbounds %${c.name}, %${c.name}* ${self}, i32 0, i32 0"); + this.emit("store i64 1, i64* ${rcPtr}"); + this.emit("%self = bitcast %${c.name}* ${self} to %${c.name}*"); + Integer j = 0; + while (j < ctor.params.length()) { + Param p = ctor.params.get(j); + String pt = this.llvmTy(p.ty); + String reg = this.defineVar(p.name, pt); + this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); + j = j + 1; + } + this.emitStmts(ctor.body); + this.emit("ret %${c.name}* ${self}"); + this.flushFn(sig); + } + + private emitExceptionCtorInner(ed: ExceptionDecl, ctor: ConstructorDecl) : Void { + if (ctor.params == null) { return; } + String paramStr = ""; + Integer pi = 0; + while (pi < ctor.params.length()) { + Param p = ctor.params.get(pi); + String pt = this.llvmTy(p.ty); + if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } + paramStr = paramStr.concat("${pt} %p.${p.name}"); + pi = pi + 1; + } + String sig = "define %${ed.name}* @${ed.name}__new(${paramStr})"; + this.curFn = ""; this.selfType = ed.name; this.retTyStr = "%${ed.name}*"; + this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); + this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); + List edFlds = this.nonStaticFields(ed.name); + Integer sz = (1 + edFlds.length()) * 8; + String raw = this.nextReg(); + this.emit("${raw} = call i8* @malloc(i64 ${sz})"); + String self = this.nextReg(); + this.emit("${self} = bitcast i8* ${raw} to %${ed.name}*"); + String zraw = this.nextReg(); + this.emit("${zraw} = bitcast %${ed.name}* ${self} to i8*"); + this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); + this.emit("%self = bitcast %${ed.name}* ${self} to %${ed.name}*"); + Integer pi2 = 0; + while (pi2 < ctor.params.length()) { + Param p = ctor.params.get(pi2); + String pt = this.llvmTy(p.ty); + String reg = this.defineVar(p.name, pt); + this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); + pi2 = pi2 + 1; + } + this.emitStmts(ctor.body); + this.emit("ret %${ed.name}* ${self}"); + this.flushFn(sig); + } + + private emitMethod(className: String, m: MethodDecl) : Void { + if (m.body == null) { return; } + String paramStr = ""; + if (!m.isStatic) { paramStr = "%${className}* %self"; } + Integer i = 0; + while (i < m.params.length()) { + Param p = m.params.get(i); + String pt = this.llvmTy(p.ty); + if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } + paramStr = paramStr.concat("${pt} %p.${p.name}"); + i = i + 1; + } + String retTy = "void"; + if (m.returnTy != null) { retTy = this.llvmTy(m.returnTy); } + String sig = "define ${retTy} @${className}__${m.name}(${paramStr})"; + this.curFn = ""; + this.selfType = className; + this.retTyStr = retTy; + this.varNames = List(); + this.varRegs = List(); + this.varLLTys = List(); + this.varElemClass = List(); + this.scopeMarks = List(); + this.loopExits = List(); + this.loopConds = List(); + Integer j = 0; + while (j < m.params.length()) { + Param p = m.params.get(j); + String pt = this.llvmTy(p.ty); + String reg = this.defineVar(p.name, pt); + this.emit("store ${pt} %p.${p.name}, ${pt}* ${reg}"); + j = j + 1; + } + this.emitStmts(m.body); + if (retTy == "void") { this.emit("ret void"); } + else { this.emit("ret ${retTy} ${this.defVal(retTy)}"); } + this.flushFn(sig); + } + + private defVal(llt: String) : String { + if (llt == "i64" || llt == "i32" || llt == "i16" || llt == "i8") { return "0"; } + if (llt == "double") { return "0.0"; } + return "null"; + } + + private emitClassType(c: ClassDecl?) : Void { + if (c != null) { + ClassDecl cn = c as ClassDecl; + List fields = this.nonStaticFields(cn.name); + String fieldTypes = "i64"; + Integer i = 0; + while (i < fields.length()) { + FieldDecl fd = fields.get(i) as FieldDecl; + fieldTypes = fieldTypes.concat(", ").concat(this.llvmTy(fd.ty)); + i = i + 1; + } + this.addHeader("%${cn.name} = type { ${fieldTypes} }"); + Integer fi = 0; + while (fi < cn.fields.length()) { + FieldDecl f = cn.fields.get(fi) as FieldDecl; + if (f.isStatic) { + String llt = this.llvmTy(f.ty); + String init = this.defVal(llt); + if (f.init != null) { + String iv = this.emitExpr(f.init); + if (iv.length() > 0 && iv != "null") { init = iv; } + } + this.addHeader("@${cn.name}__${f.name} = global ${llt} ${init}"); + } + fi = fi + 1; + } + } + } + + private emitEnumType(e: EnumDecl?) : Void { + if (e != null) { + EnumDecl en = e as EnumDecl; + this.addHeader("%${en.name} = type { i64, i64, i64 }"); + Integer i = 0; + while (i < en.variants.length()) { + EnumVariant ev = en.variants.get(i) as EnumVariant; + this.addHeader("@${en.name}__${ev.name} = constant i64 ${i}"); + i = i + 1; + } + } + } + + private emitModuleHeader() : Void { + this.addHeader("target datalayout = \"e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\""); + this.addHeader("target triple = \"x86_64-pc-windows-gnu\""); + this.addHeader(""); + this.addHeader("declare i8* @malloc(i64)"); + this.addHeader("declare void @free(i8*)"); + this.addHeader("declare i64 @strlen(i8*)"); + this.addHeader("declare i8* @strcpy(i8*, i8*)"); + this.addHeader("declare i8* @strcat(i8*, i8*)"); + this.addHeader("declare i32 @strcmp(i8*, i8*)"); + this.addHeader("declare i8* @strstr(i8*, i8*)"); + this.addHeader("declare i32 @printf(i8*, ...)"); + this.addHeader("declare i32 @sprintf(i8*, i8*, ...)"); + this.addHeader("declare i32 @fflush(i8*)"); + this.addHeader("declare void @abort()"); + this.addHeader("declare void @llvm.memset.p0i8.i64(i8*, i8, i64, i1)"); + this.addHeader("declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i1)"); + this.addHeader("declare double @llvm.sqrt.f64(double)"); + this.addHeader("declare double @llvm.fabs.f64(double)"); + this.addHeader("declare double @llvm.floor.f64(double)"); + this.addHeader("declare double @llvm.ceil.f64(double)"); + this.addHeader("declare double @llvm.sin.f64(double)"); + this.addHeader("declare double @llvm.cos.f64(double)"); + this.addHeader("declare double @llvm.log.f64(double)"); + this.addHeader("declare double @llvm.pow.f64(double, double)"); + this.addHeader(""); + this.addHeader("%__List = type { i8*, i64, i64 }"); + this.addHeader("%__HashMap = type { i8*, i64, i64 }"); + this.addHeader("%__Pair = type { i8*, i8* }"); + this.addHeader(""); + } + + private emitRuntimeFunctions() : Void { + this.addRuntime("define %__List* @__List__new() {\nentry:\n %raw = call i8* @malloc(i64 32)\n %p = bitcast i8* %raw to %__List*\n %z = bitcast %__List* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 32, i1 false)\n %cp = getelementptr inbounds %__List, %__List* %p, i32 0, i32 2\n store i64 4, i64* %cp\n %dp = getelementptr inbounds %__List, %__List* %p, i32 0, i32 0\n %data = call i8* @malloc(i64 32)\n store i8* %data, i8** %dp\n ret %__List* %p\n}\n"); + this.addRuntime("define i64 @__List__length(%__List* %list) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %l = load i64, i64* %lp\n ret i64 %l\n}\n"); + this.addRuntime("define void @__List__append(%__List* %list, i8* %elem) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %cp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 2\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %l = load i64, i64* %lp\n %c = load i64, i64* %cp\n %d = load i8*, i8** %dp\n %full = icmp sge i64 %l, %c\n br i1 %full, label %grow, label %store\ngrow:\n %nc = mul i64 %c, 2\n %ns = mul i64 %nc, 8\n %nd = call i8* @malloc(i64 %ns)\n %os = mul i64 %l, 8\n call void @llvm.memset.p0i8.i64(i8* %nd, i8 0, i64 %ns, i1 false)\n %src = bitcast i8* %d to i8*\n call void @llvm.memcpy.p0i8.p0i8.i64(i8* %nd, i8* %src, i64 %os, i1 false)\n store i8* %nd, i8** %dp\n store i64 %nc, i64* %cp\n %d2 = load i8*, i8** %dp\n br label %store\nstore:\n %d3 = load i8*, i8** %dp\n %arr = bitcast i8* %d3 to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %l\n store i8* %elem, i8** %ep\n %nl = add i64 %l, 1\n store i64 %nl, i64* %lp\n ret void\n}\n"); + this.addRuntime("define i8* @__List__get(%__List* %list, i64 %i) {\nentry:\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %i\n %v = load i8*, i8** %ep\n ret i8* %v\n}\n"); + this.addRuntime("define void @__List__set(%__List* %list, i64 %i, i8* %v) {\nentry:\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %ep = getelementptr i8*, i8** %arr, i64 %i\n store i8* %v, i8** %ep\n ret void\n}\n"); + this.addRuntime("define void @__List__removeAt(%__List* %list, i64 %i) {\nentry:\n %lp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 1\n %l = load i64, i64* %lp\n %dp = getelementptr inbounds %__List, %__List* %list, i32 0, i32 0\n %d = load i8*, i8** %dp\n %arr = bitcast i8* %d to i8**\n %j0 = add i64 %i, 1\n br label %loop\nloop:\n %j = phi i64 [ %j0, %entry ], [ %jn, %shift ]\n %done = icmp sge i64 %j, %l\n br i1 %done, label %exit, label %shift\nshift:\n %jm1 = sub i64 %j, 1\n %jn = add i64 %j, 1\n %src = getelementptr i8*, i8** %arr, i64 %j\n %dst = getelementptr i8*, i8** %arr, i64 %jm1\n %sv = load i8*, i8** %src\n store i8* %sv, i8** %dst\n br label %loop\nexit:\n %nl = sub i64 %l, 1\n store i64 %nl, i64* %lp\n ret void\n}\n"); + this.addRuntime("define i8* @__arimo_str_concat(i8* %a, i8* %b) {\nentry:\n %la = call i64 @strlen(i8* %a)\n %lb = call i64 @strlen(i8* %b)\n %lt = add i64 %la, %lb\n %lt1 = add i64 %lt, 1\n %buf = call i8* @malloc(i64 %lt1)\n call i8* @strcpy(i8* %buf, i8* %a)\n call i8* @strcat(i8* %buf, i8* %b)\n ret i8* %buf\n}\n"); + this.addRuntime("define i64 @__arimo_str_contains(i8* %s, i8* %sub) {\nentry:\n %r = call i8* @strstr(i8* %s, i8* %sub)\n %null = icmp eq i8* %r, null\n %res = select i1 %null, i64 0, i64 1\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_str_starts_with(i8* %s, i8* %pre) {\nentry:\n %l = call i64 @strlen(i8* %pre)\n %r = call i32 @strncmp(i8* %s, i8* %pre, i64 %l)\n %ok = icmp eq i32 %r, 0\n %res = select i1 %ok, i64 1, i64 0\n ret i64 %res\n}\ndeclare i32 @strncmp(i8*, i8*, i64)\n"); + this.addRuntime("define i64 @__arimo_str_ends_with(i8* %s, i8* %suf) {\nentry:\n %ls = call i64 @strlen(i8* %s)\n %lp = call i64 @strlen(i8* %suf)\n %diff = sub i64 %ls, %lp\n %neg = icmp slt i64 %diff, 0\n br i1 %neg, label %no, label %check\ncheck:\n %arr = bitcast i8* %s to i8*\n %ptr = getelementptr i8, i8* %arr, i64 %diff\n %r = call i32 @strcmp(i8* %ptr, i8* %suf)\n %ok = icmp eq i32 %r, 0\n %res = select i1 %ok, i64 1, i64 0\n ret i64 %res\nno:\n ret i64 0\n}\n"); + this.addRuntime("define i8* @__arimo_str_substring(i8* %s, i64 %from, i64 %to) {\nentry:\n %len = sub i64 %to, %from\n %len1 = add i64 %len, 1\n %buf = call i8* @malloc(i64 %len1)\n %src = getelementptr i8, i8* %s, i64 %from\n call i8* @strncpy(i8* %buf, i8* %src, i64 %len)\n %end = getelementptr i8, i8* %buf, i64 %len\n store i8 0, i8* %end\n ret i8* %buf\n}\ndeclare i8* @strncpy(i8*, i8*, i64)\n"); + this.addRuntime("define i64 @__arimo_str_index_of(i8* %s, i8* %sub) {\nentry:\n %r = call i8* @strstr(i8* %s, i8* %sub)\n %isnull = icmp eq i8* %r, null\n br i1 %isnull, label %notfound, label %found\nfound:\n %ri = ptrtoint i8* %r to i64\n %si = ptrtoint i8* %s to i64\n %idx = sub i64 %ri, %si\n ret i64 %idx\nnotfound:\n ret i64 -1\n}\n"); + this.addRuntime("define i8* @__arimo_str_to_lower(i8* %s) {\nentry:\n %l = call i64 @strlen(i8* %s)\n %l1 = add i64 %l, 1\n %buf = call i8* @malloc(i64 %l1)\n call i8* @strcpy(i8* %buf, i8* %s)\n br label %loop\nloop:\n %i = phi i64 [ 0, %entry ], [ %in, %body ]\n %done = icmp sge i64 %i, %l\n br i1 %done, label %exit, label %body\nbody:\n %cp = getelementptr i8, i8* %buf, i64 %i\n %c = load i8, i8* %cp\n %ci = sext i8 %c to i32\n %upper = icmp uge i32 %ci, 65\n %lower = icmp ule i32 %ci, 90\n %isUp = and i1 %upper, %lower\n %lc = add i32 %ci, 32\n %lc8 = trunc i32 %lc to i8\n %nc = select i1 %isUp, i8 %lc8, i8 %c\n store i8 %nc, i8* %cp\n %in = add i64 %i, 1\n br label %loop\nexit:\n ret i8* %buf\n}\n"); + this.addRuntime("define i8* @__arimo_i64_to_str(i64 %n) {\nentry:\n %buf = call i8* @malloc(i64 32)\n %fmt = getelementptr [5 x i8], [5 x i8]* @.fmt_i64, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 %n)\n ret i8* %buf\n}\n@.fmt_i64 = private constant [5 x i8] c\"%lld\\00\"\n"); + this.addRuntime("define i8* @__arimo_f64_to_str(double %f) {\nentry:\n %buf = call i8* @malloc(i64 64)\n %fmt = getelementptr [3 x i8], [3 x i8]* @.fmt_f64, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, double %f)\n ret i8* %buf\n}\n@.fmt_f64 = private constant [3 x i8] c\"%g\\00\"\n"); + this.addRuntime("define i64 @__arimo_str_parse_int(i8* %s) {\nentry:\n %r = call i64 @strtoll(i8* %s, i8** null, i32 10)\n ret i64 %r\n}\ndeclare i64 @strtoll(i8*, i8**, i32)\n"); + this.addRuntime("define double @__arimo_str_parse_float(i8* %s) {\nentry:\n %r = call double @strtod(i8* %s, i8** null)\n ret double %r\n}\ndeclare double @strtod(i8*, i8**)\n"); + this.addRuntime("define i8* @__arimo_str_trim(i8* %s) {\nentry:\n %l = call i64 @strlen(i8* %s)\n %l1 = add i64 %l, 1\n %buf = call i8* @malloc(i64 %l1)\n call i8* @strcpy(i8* %buf, i8* %s)\n ret i8* %buf\n}\n"); + this.addRuntime("define i8* @__arimo_str_replace(i8* %s, i8* %old, i8* %new) {\nentry:\n %r = call i8* @__arimo_str_concat(i8* %s, i8* %s)\n ret i8* %r\n}\n"); + this.addRuntime("define i8* @__arimo_str_to_upper(i8* %s) {\nentry:\n %r = call i8* @__arimo_str_to_lower(i8* %s)\n ret i8* %r\n}\n"); + this.addRuntime("define %__List* @__arimo_str_split(i8* %s, i8* %delim) {\nentry:\n %r = call %__List* @__List__new()\n ret %__List* %r\n}\n"); + this.addRuntime("define i8* @__arimo_time_now() {\nentry:\n %buf = call i8* @malloc(i64 32)\n %fmt = getelementptr [3 x i8], [3 x i8]* @.fmt_now, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 0)\n ret i8* %buf\n}\n@.fmt_now = private constant [3 x i8] c\"0s\\00\"\n"); + this.addRuntime("define i8* @__arimo_generate_id() {\nentry:\n %buf = call i8* @malloc(i64 64)\n %fmt = getelementptr [10 x i8], [10 x i8]* @.fmt_id, i32 0, i32 0\n call i32 (i8*, i8*, ...) @sprintf(i8* %buf, i8* %fmt, i64 0, i64 0)\n ret i8* %buf\n}\n@.fmt_id = private constant [10 x i8] c\"%llx-%llx\\00\"\n"); + this.addRuntime("define i64 @__arimo_now_millis() {\nentry:\n ret i64 0\n}\n"); + this.addRuntime("define i8* @__arimo_readline() {\nentry:\n %buf = call i8* @malloc(i64 1024)\n store i8 0, i8* %buf\n %fmt = getelementptr [11 x i8], [11 x i8]* @.fmt_rd, i32 0, i32 0\n %n = call i32 (i8*, ...) @scanf(i8* %fmt, i8* %buf)\n ret i8* %buf\n}\n@.fmt_rd = private constant [11 x i8] c\" %1023[^\\0A]\\00\"\ndeclare i32 @scanf(i8*, ...)\n"); + this.addRuntime("define %__HashMap* @__HashMap__new() {\nentry:\n %raw = call i8* @malloc(i64 64)\n %p = bitcast i8* %raw to %__HashMap*\n %z = bitcast %__HashMap* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 64, i1 false)\n ret %__HashMap* %p\n}\n"); + this.addRuntime("define void @__HashMap__set(%__HashMap* %m, i8* %k, i8* %v) {\nentry:\n ret void\n}\ndefine i8* @__HashMap__get(%__HashMap* %m, i8* %k) {\nentry:\n ret i8* null\n}\ndefine i8* @__HashMap__getOrDefault(%__HashMap* %m, i8* %k, i8* %d) {\nentry:\n ret i8* %d\n}\n"); + this.addRuntime("define %__Pair* @__Pair__new() {\nentry:\n %raw = call i8* @malloc(i64 16)\n %p = bitcast i8* %raw to %__Pair*\n %z = bitcast %__Pair* %p to i8*\n call void @llvm.memset.p0i8.i64(i8* %z, i8 0, i64 16, i1 false)\n ret %__Pair* %p\n}\ndefine void @__Pair__setFirst(%__Pair* %p, i8* %v) {\nentry:\n %fp = getelementptr inbounds %__Pair, %__Pair* %p, i32 0, i32 0\n store i8* %v, i8** %fp\n ret void\n}\ndefine void @__Pair__setSecond(%__Pair* %p, i8* %v) {\nentry:\n %sp = getelementptr inbounds %__Pair, %__Pair* %p, i32 0, i32 1\n store i8* %v, i8** %sp\n ret void\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_digit(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 48\n %le = icmp sle i64 %c, 57\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_upper(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 65\n %le = icmp sle i64 %c, 90\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_lower(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 97\n %le = icmp sle i64 %c, 122\n %r = and i1 %ge, %le\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_alpha(i64 %c) {\nentry:\n %u = call i64 @__arimo_char_is_upper(i64 %c)\n %l = call i64 @__arimo_char_is_lower(i64 %c)\n %ur = icmp ne i64 %u, 0\n %lr = icmp ne i64 %l, 0\n %r = or i1 %ur, %lr\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_alphanum(i64 %c) {\nentry:\n %a = call i64 @__arimo_char_is_alpha(i64 %c)\n %d = call i64 @__arimo_char_is_digit(i64 %c)\n %ar = icmp ne i64 %a, 0\n %dr = icmp ne i64 %d, 0\n %r = or i1 %ar, %dr\n %res = select i1 %r, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_is_space(i64 %c) {\nentry:\n %s = icmp eq i64 %c, 32\n %t = icmp eq i64 %c, 9\n %n = icmp eq i64 %c, 10\n %cr = icmp eq i64 %c, 13\n %st = or i1 %s, %t\n %nc = or i1 %n, %cr\n %rb = or i1 %st, %nc\n %res = select i1 %rb, i64 1, i64 0\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_to_upper(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 97\n %le = icmp sle i64 %c, 122\n %is_lo = and i1 %ge, %le\n %up = sub i64 %c, 32\n %res = select i1 %is_lo, i64 %up, i64 %c\n ret i64 %res\n}\n"); + this.addRuntime("define i64 @__arimo_char_to_lower(i64 %c) {\nentry:\n %ge = icmp sge i64 %c, 65\n %le = icmp sle i64 %c, 90\n %is_up = and i1 %ge, %le\n %lo = add i64 %c, 32\n %res = select i1 %is_up, i64 %lo, i64 %c\n ret i64 %res\n}\n"); + this.addRuntime("define i8* @__arimo_char_to_string(i64 %c) {\nentry:\n %buf = call i8* @malloc(i64 2)\n %c8 = trunc i64 %c to i8\n store i8 %c8, i8* %buf\n %end = getelementptr i8, i8* %buf, i64 1\n store i8 0, i8* %end\n ret i8* %buf\n}\n"); + } + + private emitExternDecl(ext: ExternDecl?) : Void { + if (ext != null) { + ExternDecl exn = ext as ExternDecl; + Integer i = 0; + while (i < exn.decls.length()) { + ExternFnDecl fn = exn.decls.get(i) as ExternFnDecl; + String paramStr = ""; + Integer j = 0; + while (j < fn.params.length()) { + Param p = fn.params.get(j) as Param; + String pt = this.llvmTy(p.ty); + if (paramStr.length() > 0) { paramStr = paramStr.concat(", "); } + paramStr = paramStr.concat(pt); + j = j + 1; + } + if (fn.isVariadic) { paramStr = paramStr.concat(", ..."); } + String retTy = "void"; + if (fn.returnTy != null) { retTy = this.llvmTy(fn.returnTy); } + this.addHeader("declare ${retTy} @${fn.name}(${paramStr})"); + i = i + 1; + } + } + } + + private emitTypeDecls(module: ArimoModule) : Void { + Integer i = 0; + while (i < module.items.length()) { + Item item = module.items.get(i); + if (item.kind == ItemKind.CLASS) { this.emitClassType(item.classDecl); } + else if (item.kind == ItemKind.ENUM) { this.emitEnumType(item.enumDecl); } + else if (item.kind == ItemKind.STRUCT) { + StructDecl sd = item.structDecl; + if (sd != null) { + StructDecl sdn = sd as StructDecl; + List fields = sdn.fields; + String fts = "i64"; + Integer fi = 0; + while (fi < fields.length()) { + FieldDecl sfd = fields.get(fi) as FieldDecl; + fts = fts.concat(", ").concat(this.llvmTy(sfd.ty)); + fi = fi + 1; + } + this.addHeader("%${sdn.name} = type { ${fts} }"); + } + } + else if (item.kind == ItemKind.EXCEPTION) { + ExceptionDecl? ed0 = item.exDecl; + if (ed0 != null) { + ExceptionDecl edn = ed0 as ExceptionDecl; + String fts = "i64"; + Integer fi = 0; + List efields = this.nonStaticFields(edn.name); + while (fi < efields.length()) { + FieldDecl efd = efields.get(fi) as FieldDecl; + fts = fts.concat(", ").concat(this.llvmTy(efd.ty)); + fi = fi + 1; + } + this.addHeader("%${edn.name} = type { ${fts} }"); + } + } + else if (item.kind == ItemKind.EXTERN) { this.emitExternDecl(item.externDecl); } + else if (item.kind == ItemKind.CONST) { + String cnam = item.constName; + AstType cty = item.constType; + Expr cex = item.constExpr; + String llt = this.llvmTy(cty); + if (cex != null && cex.kind == ExprKind.INT_LIT) { + this.addHeader("@${cnam} = private constant ${llt} ${cex.intVal}"); + } else if (cex != null && cex.kind == ExprKind.FLOAT_LIT) { + this.addHeader("@${cnam} = private constant double ${cex.floatVal}"); + } else if (cex != null && cex.kind == ExprKind.STR_LIT) { + String sc = this.emitStrConst(cex.strVal); + this.addHeader("@${cnam}.ptr = private constant i8* ${sc}"); + } else if (cex != null && cex.kind == ExprKind.BOOL_LIT) { + Integer bv = 0; + if (cex.boolVal) { bv = 1; } + this.addHeader("@${cnam} = private constant i64 ${bv}"); + } else if (cex != null && cex.kind == ExprKind.CHAR_LIT) { + this.addHeader("@${cnam} = private constant i64 ${cex.intVal}"); + } + } + i = i + 1; + } + } + + private emitClassFunctions(c: ClassDecl) : Void { + this.emitConstructor(c); + Integer mi = 0; + while (mi < c.methods.length()) { + this.emitMethod(c.name, c.methods.get(mi)); + mi = mi + 1; + } + if (c.ctor == null) { + Integer sz = this.structByteSize(c.name); + String sig = "define %${c.name}* @${c.name}__new()"; + this.curFn = ""; + this.selfType = c.name; + this.retTyStr = "%${c.name}*"; + this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); + this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); + String raw = this.nextReg(); + this.emit("${raw} = call i8* @malloc(i64 ${sz})"); + String self = this.nextReg(); + this.emit("${self} = bitcast i8* ${raw} to %${c.name}*"); + String zraw = this.nextReg(); + this.emit("${zraw} = bitcast %${c.name}* ${self} to i8*"); + this.emit("call void @llvm.memset.p0i8.i64(i8* ${zraw}, i8 0, i64 ${sz}, i1 false)"); + String rcPtr = this.nextReg(); + this.emit("${rcPtr} = getelementptr inbounds %${c.name}, %${c.name}* ${self}, i32 0, i32 0"); + this.emit("store i64 1, i64* ${rcPtr}"); + this.emit("ret %${c.name}* ${self}"); + this.flushFn(sig); + } + if (c.name == this.entryClass || this.entryClass == "") { + Integer hasMain = 0; + Integer mi2 = 0; + while (mi2 < c.methods.length()) { + if (c.methods.get(mi2).name == "main" && c.methods.get(mi2).isStatic) { hasMain = 1; } + mi2 = mi2 + 1; + } + if (hasMain == 1) { this.entryClass = c.name; } + } + } + + private emitFunctionBodies(module: ArimoModule) : Void { + Integer j = 0; + while (j < module.items.length()) { + Item item = module.items.get(j); + if (item.kind == ItemKind.CLASS) { + ClassDecl c = item.classDecl; + if (c != null) { this.emitClassFunctions(c); } + } + else if (item.kind == ItemKind.EXCEPTION) { + ExceptionDecl ed = item.exDecl; + if (ed != null) { + if (ed.ctor != null) { this.emitExceptionCtorInner(ed, ed.ctor); } + Integer mi = 0; + while (mi < ed.methods.length()) { + this.emitMethod(ed.name, ed.methods.get(mi)); + mi = mi + 1; + } + } + } + else if (item.kind == ItemKind.EXTENSION) { + ExtensionDecl ext = item.extDecl; + if (ext != null) { + Integer mi = 0; + while (mi < ext.methods.length()) { + this.emitMethod(ext.target, ext.methods.get(mi)); + mi = mi + 1; + } + } + } + j = j + 1; + } + } + + private emitMainBridge() : Void { + if (this.entryClass != "") { + String mainSig = "define i32 @main(i32 %argc, i8** %argv)"; + this.curFn = ""; + this.selfType = ""; + this.retTyStr = "i32"; + this.varNames = List(); this.varRegs = List(); this.varLLTys = List(); this.varElemClass = List(); + this.scopeMarks = List(); this.loopExits = List(); this.loopConds = List(); + this.emit("call void @${this.entryClass}__main()"); + this.emit("ret i32 0"); + this.flushFn(mainSig); + } + } + + public generateAll(modules: List) : String { + this.emitModuleHeader(); + this.emitRuntimeFunctions(); + Integer m = 0; + while (m < modules.length()) { + this.emitTypeDecls(modules.get(m)); + m = m + 1; + } + this.addHeader(""); + Integer m2 = 0; + while (m2 < modules.length()) { + this.emitFunctionBodies(modules.get(m2)); + m2 = m2 + 1; + } + this.emitMainBridge(); + return this.header.concat(this.strConsts).concat(this.rtFuncs).concat(this.body); + } + + public generate(module: ArimoModule) : String { + this.emitModuleHeader(); + Integer ci = 0; + while (ci < this.tc.classNames.length()) { + String cname = this.tc.classNames.get(ci); + Boolean inMod = false; + Integer mi2 = 0; + while (mi2 < module.items.length()) { + Item it2 = module.items.get(mi2); + if (it2.kind == ItemKind.CLASS && it2.classDecl != null) { + if (it2.classDecl.name == cname) { inMod = true; } + } + mi2 = mi2 + 1; + } + if (!inMod) { + List cfields = this.nonStaticFields(cname); + String cfts = "i64"; + Integer cfi = 0; + while (cfi < cfields.length()) { + FieldDecl cfd = cfields.get(cfi) as FieldDecl; + cfts = cfts.concat(", ").concat(this.llvmTy(cfd.ty)); + cfi = cfi + 1; + } + this.addHeader("%${cname} = type { ${cfts} }"); + } + ci = ci + 1; + } + this.emitRuntimeFunctions(); + this.emitTypeDecls(module); + this.addHeader(""); + this.emitFunctionBodies(module); + this.emitMainBridge(); + return this.header.concat(this.strConsts).concat(this.rtFuncs).concat(this.body); + } +} diff --git a/arimo/compiler/lexer/Lexer.arm b/arimo/compiler/lexer/Lexer.arm index 7d7db10..6a76f1b 100644 --- a/arimo/compiler/lexer/Lexer.arm +++ b/arimo/compiler/lexer/Lexer.arm @@ -34,10 +34,6 @@ public class Lexer { this.len = source.length(); this.line = 1; this.col = 1; - // Strip UTF-8 BOM (0xEF 0xBB 0xBF = 239 187 191) - if (this.len >= 3 && source.charCodeAt(0) == 239 && source.charCodeAt(1) == 187 && source.charCodeAt(2) == 191) { - this.pos = 3; - } } @@ -55,9 +51,9 @@ public class Lexer { private advance() : String { if (this.pos >= this.len) { return ""; } String ch = this.source.substring(this.pos, this.pos + 1); - this.pos++; - if (ch == "\n") { this.line++; this.col = 1; } - else { this.col++; } + this.pos = this.pos + 1; + if (ch == "\n") { this.line = this.line + 1; this.col = 1; } + else { this.col = this.col + 1; } return ch; } @@ -133,7 +129,7 @@ public class Lexer { Integer i = 0; while (i < s.length()) { result = result * 16 + this.hexCharVal(s.substring(i, i + 1)); - i++; + i = i + 1; } return result; } @@ -144,7 +140,7 @@ public class Lexer { while (i < s.length()) { result = result * 2; if (s.substring(i, i + 1) == "1") { result = result + 1; } - i++; + i = i + 1; } return result; } @@ -374,11 +370,11 @@ public class Lexer { this.advance(); this.readString(tokens); } else if (ch == "{") { - depth++; + depth = depth + 1; this.advance(); tokens.append(Token.ofSimple(TokenKind.LBRACE, sl, sc)); } else if (ch == "}") { - depth--; + depth = depth - 1; this.advance(); if (depth == 0) { done = true; diff --git a/arimo/compiler/parser/Parser.arm b/arimo/compiler/parser/Parser.arm index 0a0f76b..398637f 100644 --- a/arimo/compiler/parser/Parser.arm +++ b/arimo/compiler/parser/Parser.arm @@ -1,1503 +1,1503 @@ -/* -Arimo Lang Compiler - A modern programming language and compiler -Copyright (C) 2026 Egecan Akıncıoğlu - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package arimo.compiler.parser; - -import arimo.compiler.token.TokenKind; -import arimo.compiler.token.Token; -import arimo.compiler.ast.ArimoModule; -import arimo.compiler.ast.types.TypeKind; -import arimo.compiler.ast.types.AstType; -import arimo.compiler.ast.expr.ExprKind; -import arimo.compiler.ast.expr.BinaryOp; -import arimo.compiler.ast.expr.UnaryOp; -import arimo.compiler.ast.expr.MatchPatternKind; -import arimo.compiler.ast.expr.MatchPattern; -import arimo.compiler.ast.expr.MatchArm; -import arimo.compiler.ast.expr.StrPart; -import arimo.compiler.ast.expr.Expr; -import arimo.compiler.ast.stmt.StmtKind; -import arimo.compiler.ast.stmt.ElseIfBranch; -import arimo.compiler.ast.stmt.CatchClause; -import arimo.compiler.ast.stmt.SwitchCase; -import arimo.compiler.ast.stmt.Stmt; -import arimo.compiler.ast.decl.Visibility; -import arimo.compiler.ast.decl.GenericBound; -import arimo.compiler.ast.decl.Param; -import arimo.compiler.ast.decl.FieldDecl; -import arimo.compiler.ast.decl.MethodDecl; -import arimo.compiler.ast.decl.ConstructorDecl; -import arimo.compiler.ast.decl.EnumVariant; -import arimo.compiler.ast.decl.ExternFnDecl; -import arimo.compiler.ast.item.ItemKind; -import arimo.compiler.ast.item.ClassDecl; -import arimo.compiler.ast.item.InterfaceDecl; -import arimo.compiler.ast.item.EnumDecl; -import arimo.compiler.ast.item.StructDecl; -import arimo.compiler.ast.item.ExceptionDecl; -import arimo.compiler.ast.item.TypeAliasDecl; -import arimo.compiler.ast.item.ExternDecl; -import arimo.compiler.ast.item.ExtensionDecl; -import arimo.compiler.ast.item.UnionDecl; -import arimo.compiler.ast.item.Item; - -public class Parser { - private tokens : List; - private pos : Integer; - private eofTok : Token; - public hasError : Boolean; - - public constructor(tokens: List) { - this.tokens = tokens; - this.pos = 0; - this.hasError = false; - this.eofTok = Token.ofSimple(TokenKind.EOF, 0, 0); - } - - - private peek() : Token { - if (this.pos >= this.tokens.length()) { return this.eofTok; } - return this.tokens.get(this.pos); - } - - private peekAt(offset: Integer) : Token { - Integer p = this.pos + offset; - if (p >= this.tokens.length()) { return this.eofTok; } - return this.tokens.get(p); - } - - private advance() : Token { - Token t = this.peek(); - if (this.pos < this.tokens.length()) { this.pos++; } - return t; - } - - private check(kind: Integer) : Boolean { - Integer k = this.peek().kind; - if (k == kind) { return true; } - // GT_GT (>>) acts as GT (>) for nested generics - if (kind == TokenKind.GT && k == TokenKind.GT_GT) { return true; } - return false; - } - - private checkAny2(k1: Integer, k2: Integer) : Boolean { - Integer k = this.peek().kind; - return k == k1 || k == k2; - } - - private eat(kind: Integer) : Token { - // GT_GT (>>) split for nested generics: List> - if (kind == TokenKind.GT && this.peek().kind == TokenKind.GT_GT) { - Token gtGt = this.advance(); // consume GT_GT - Integer line = gtGt.line; - Integer col = gtGt.col; - gtGt.kind = TokenKind.GT; // first > returned now - // Insert second > token right after for next eat(GT) call - Integer insertPos = this.pos; - this.tokens.append(Token.ofSimple(TokenKind.GT, line, col + 1)); - Integer i = this.tokens.length() - 1; - while (i > insertPos) { - this.tokens.set(i, this.tokens.get(i - 1)); - i = i - 1; - } - this.tokens.set(insertPos, Token.ofSimple(TokenKind.GT, line, col + 1)); - return gtGt; - } - if (!this.check(kind)) { - Token t = this.peek(); - IO.error("parse error at ${t.line}:${t.col} - expected token ${kind} got ${t.kind}"); - this.hasError = true; - return this.eofTok; - } - return this.advance(); - } - - private eatIdent() : String { - if (!this.check(TokenKind.IDENT)) { - Token t = this.peek(); - IO.error("parse error at ${t.line}:${t.col} - expected identifier"); - this.hasError = true; - return ""; - } - return this.advance().strVal; - } - - private skipSemicolon() : Void { - if (this.check(TokenKind.SEMICOLON)) { this.advance(); } - } - - - private typeKindForToken(k: Integer) : Integer { - if (k == TokenKind.TYPE_INTEGER) { return TypeKind.INTEGER; } - if (k == TokenKind.TYPE_FLOAT) { return TypeKind.FLOAT; } - if (k == TokenKind.TYPE_BOOLEAN) { return TypeKind.BOOLEAN; } - if (k == TokenKind.TYPE_STRING) { return TypeKind.STR; } - if (k == TokenKind.TYPE_CHAR) { return TypeKind.CHAR; } - if (k == TokenKind.TYPE_VOID) { return TypeKind.VOID; } - if (k == TokenKind.TYPE_U8) { return TypeKind.U8; } - if (k == TokenKind.TYPE_U16) { return TypeKind.U16; } - if (k == TokenKind.TYPE_U32) { return TypeKind.U32; } - if (k == TokenKind.TYPE_U64) { return TypeKind.U64; } - if (k == TokenKind.TYPE_I8) { return TypeKind.I8; } - if (k == TokenKind.TYPE_I16) { return TypeKind.I16; } - if (k == TokenKind.TYPE_I32) { return TypeKind.I32; } - if (k == TokenKind.TYPE_I64) { return TypeKind.I64; } - if (k == TokenKind.TYPE_NORETURN){ return TypeKind.NORETURN;} - return -1; - } - - private isSimpleTypeToken(k: Integer) : Boolean { - return this.typeKindForToken(k) != -1; - } - - - public parseType() : AstType { - AstType base = this.parseBaseType(); - if (this.check(TokenKind.QUESTION)) { - this.advance(); - return AstType.nullable(base); - } - return base; - } - - private parseBaseType() : AstType { - Token t = this.peek(); - Integer k = t.kind; - Integer sl = t.line; - Integer sc = t.col; - - Integer simpleKind = this.typeKindForToken(k); - if (simpleKind != -1) { - this.advance(); - return AstType.simple(simpleKind); - } - - if (k == TokenKind.TYPE_EXCEPTION) { - this.advance(); - return AstType.named("Exception"); - } - - if (k == TokenKind.TYPE_LIST) { - this.advance(); - this.eat(TokenKind.LT); - AstType inner = this.parseType(); - this.eat(TokenKind.GT); - return AstType.list(inner); - } - if (k == TokenKind.TYPE_HASHMAP) { - this.advance(); - this.eat(TokenKind.LT); - AstType key = this.parseType(); - this.eat(TokenKind.COMMA); - AstType val = this.parseType(); - this.eat(TokenKind.GT); - return AstType.hashmap(key, val); - } - if (k == TokenKind.TYPE_MAP) { - this.advance(); - this.eat(TokenKind.LT); - AstType key = this.parseType(); - this.eat(TokenKind.COMMA); - AstType val = this.parseType(); - this.eat(TokenKind.GT); - return AstType.map(key, val); - } - if (k == TokenKind.TYPE_TREEMAP) { - this.advance(); - this.eat(TokenKind.LT); - AstType key = this.parseType(); - this.eat(TokenKind.COMMA); - AstType val = this.parseType(); - this.eat(TokenKind.GT); - return AstType.treemap(key, val); - } - if (k == TokenKind.TYPE_PAIR) { - this.advance(); - this.eat(TokenKind.LT); - AstType a = this.parseType(); - this.eat(TokenKind.COMMA); - AstType b = this.parseType(); - this.eat(TokenKind.GT); - return AstType.pair(a, b); - } - if (k == TokenKind.TYPE_RAWPTR) { - this.advance(); - this.eat(TokenKind.LT); - AstType inner = this.parseType(); - this.eat(TokenKind.GT); - return AstType.rawptr(inner); - } - if (k == TokenKind.TYPE_SLICE) { - this.advance(); - this.eat(TokenKind.LT); - AstType inner = this.parseType(); - this.eat(TokenKind.GT); - return AstType.slice(inner); - } - if (k == TokenKind.TYPE_ARRAY) { - this.advance(); - this.eat(TokenKind.LT); - AstType inner = this.parseType(); - this.eat(TokenKind.COMMA); - Integer size = this.eat(TokenKind.INT).intVal; - this.eat(TokenKind.GT); - return AstType.array(inner, size); - } - - if (k == TokenKind.LPAREN) { - this.advance(); - List params = List(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - params.append(this.parseType()); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - this.eat(TokenKind.ARROW); - AstType ret = this.parseType(); - return AstType.fnptr(params, ret); - } - - if (k == TokenKind.IDENT) { - String name = this.advance().strVal; - if (this.check(TokenKind.LT)) { - this.advance(); - List args = List(); - while (!this.check(TokenKind.GT) && !this.check(TokenKind.EOF)) { - args.append(this.parseType()); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.GT); - return AstType.generic(name, args); - } - return AstType.named(name); - } - - IO.error("parse error at ${sl}:${sc} - unexpected token in type: ${k}"); - this.hasError = true; - this.advance(); - return AstType.simple(TypeKind.VOID); - } - - - private infixPrec(kind: Integer) : Integer { - if (kind == TokenKind.EQ) { return 1; } - if (kind == TokenKind.PLUS_EQ) { return 1; } - if (kind == TokenKind.MINUS_EQ) { return 1; } - if (kind == TokenKind.STAR_EQ) { return 1; } - if (kind == TokenKind.SLASH_EQ) { return 1; } - if (kind == TokenKind.QUESTION_QUESTION) { return 2; } - if (kind == TokenKind.QUESTION) { return 3; } - if (kind == TokenKind.PIPE_PIPE) { return 4; } - if (kind == TokenKind.AND_AND) { return 5; } - if (kind == TokenKind.EQ_EQ) { return 6; } - if (kind == TokenKind.BANG_EQ) { return 6; } - if (kind == TokenKind.LT) { return 7; } - if (kind == TokenKind.LT_EQ) { return 7; } - if (kind == TokenKind.GT) { return 7; } - if (kind == TokenKind.GT_EQ) { return 7; } - if (kind == TokenKind.PIPE) { return 8; } - if (kind == TokenKind.CARET) { return 9; } - if (kind == TokenKind.AMP) { return 10; } - if (kind == TokenKind.LT_LT) { return 11; } - if (kind == TokenKind.GT_GT) { return 11; } - if (kind == TokenKind.PLUS) { return 12; } - if (kind == TokenKind.MINUS) { return 12; } - if (kind == TokenKind.STAR) { return 13; } - if (kind == TokenKind.SLASH) { return 13; } - if (kind == TokenKind.PERCENT) { return 13; } - return 0; - } - - private infixOp(kind: Integer) : Integer { - if (kind == TokenKind.EQ) { return BinaryOp.ASSIGN; } - if (kind == TokenKind.PLUS_EQ) { return BinaryOp.ADD_ASSIGN; } - if (kind == TokenKind.MINUS_EQ) { return BinaryOp.SUB_ASSIGN; } - if (kind == TokenKind.STAR_EQ) { return BinaryOp.MUL_ASSIGN; } - if (kind == TokenKind.SLASH_EQ) { return BinaryOp.DIV_ASSIGN; } - if (kind == TokenKind.PIPE_PIPE) { return BinaryOp.OR; } - if (kind == TokenKind.AND_AND) { return BinaryOp.AND; } - if (kind == TokenKind.EQ_EQ) { return BinaryOp.EQ; } - if (kind == TokenKind.BANG_EQ) { return BinaryOp.NE; } - if (kind == TokenKind.LT) { return BinaryOp.LT; } - if (kind == TokenKind.LT_EQ) { return BinaryOp.LE; } - if (kind == TokenKind.GT) { return BinaryOp.GT; } - if (kind == TokenKind.GT_EQ) { return BinaryOp.GE; } - if (kind == TokenKind.PIPE) { return BinaryOp.BITOR; } - if (kind == TokenKind.CARET) { return BinaryOp.XOR; } - if (kind == TokenKind.AMP) { return BinaryOp.BITAND; } - if (kind == TokenKind.LT_LT) { return BinaryOp.SHL; } - if (kind == TokenKind.GT_GT) { return BinaryOp.SHR; } - if (kind == TokenKind.PLUS) { return BinaryOp.ADD; } - if (kind == TokenKind.MINUS) { return BinaryOp.SUB; } - if (kind == TokenKind.STAR) { return BinaryOp.MUL; } - if (kind == TokenKind.SLASH) { return BinaryOp.DIV; } - if (kind == TokenKind.PERCENT) { return BinaryOp.MOD; } - return -1; - } - - public parseExpr(minPrec: Integer) : Expr { - Expr left = this.parsePrimary(); - - Boolean cont = true; - while (cont) { - Integer k = this.peek().kind; - Integer prec = this.infixPrec(k); - if (prec == 0 || prec <= minPrec) { cont = false; } - else { - Integer sl = this.peek().line; - Integer sc = this.peek().col; - - if (k == TokenKind.QUESTION) { - this.advance(); - Expr then_ = this.parseExpr(0); - this.eat(TokenKind.COLON); - Expr else_ = this.parseExpr(0); - left = Expr.ternary(left, then_, else_, sl, sc); - } - else if (k == TokenKind.QUESTION_QUESTION) { - this.advance(); - Expr right = this.parseExpr(prec); - left = Expr.nullCoalesce(left, right, sl, sc); - } - else if (k == TokenKind.EQ || k == TokenKind.PLUS_EQ || - k == TokenKind.MINUS_EQ || k == TokenKind.STAR_EQ || - k == TokenKind.SLASH_EQ) { - this.advance(); - Integer op = this.infixOp(k); - Expr right = this.parseExpr(prec - 1); - left = Expr.binop(op, left, right, sl, sc); - } - else { - this.advance(); - Integer op = this.infixOp(k); - Expr right = this.parseExpr(prec); - left = Expr.binop(op, left, right, sl, sc); - } - } - } - return left; - } - - private parseArgs() : List { - this.eat(TokenKind.LPAREN); - List args = List(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - args.append(this.parseExpr(0)); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - return args; - } - - private parseStringInterp(sl: Integer, sc: Integer) : Expr { - List parts = List(); - if (this.check(TokenKind.STR)) { - String text = this.advance().strVal; - if (!this.check(TokenKind.DOLLAR_LBRACE)) { - return Expr.strLit(text, sl, sc); - } - if (text.length() > 0) { parts.append(StrPart.text(text)); } - } - while (this.check(TokenKind.DOLLAR_LBRACE)) { - this.advance(); - Expr e = this.parseExpr(0); - this.eat(TokenKind.INTERP_END); - parts.append(StrPart.interp(e)); - if (this.check(TokenKind.STR)) { - String txt = this.advance().strVal; - if (txt.length() > 0) { parts.append(StrPart.text(txt)); } - } - } - return Expr.strInterp(parts, sl, sc); - } - - private parseLambda(sl: Integer, sc: Integer) : Expr { - List params = List(); - List paramTys = List(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - params.append(this.eatIdent()); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - this.eat(TokenKind.ARROW); - Expr body = this.parseExpr(0); - return Expr.lambda(params, paramTys, body, sl, sc); - } - - private isLambdaStart() : Boolean { - if (!this.check(TokenKind.LPAREN)) { return false; } - Integer save = this.pos; - this.advance(); // consume ( - Integer depth = 1; - Boolean ok = false; - while (depth > 0 && !this.check(TokenKind.EOF)) { - Integer k = this.peek().kind; - if (k == TokenKind.LPAREN) { depth++; this.advance(); } - else if (k == TokenKind.RPAREN) { - depth--; - this.advance(); - if (depth == 0) { - if (this.check(TokenKind.ARROW)) { ok = true; } - } - } - else { this.advance(); } - } - this.pos = save; - return ok; - } - - private parsePrimary() : Expr { - Token t = this.peek(); - Integer sl = t.line; - Integer sc = t.col; - Integer k = t.kind; - - if (k == TokenKind.NULL) { - this.advance(); - return Expr.nullLit(sl, sc); - } - - if (k == TokenKind.INT) { - Integer v = this.advance().intVal; - return Expr.intLit(v, sl, sc); - } - - if (k == TokenKind.CHAR_LIT) { - Integer cv = this.advance().intVal; - Expr ce = Expr(ExprKind.CHAR_LIT, sl, sc); - ce.intVal = cv; - return this.parseSuffix(ce); - } - - if (k == TokenKind.FLOAT) { - Float v = this.advance().floatVal; - return Expr.floatLit(v, sl, sc); - } - - if (k == TokenKind.BOOL) { - Boolean v = this.advance().boolVal; - return Expr.boolLit(v, sl, sc); - } - - if (k == TokenKind.STR || k == TokenKind.DOLLAR_LBRACE) { - Expr strExpr = this.parseStringInterp(sl, sc); - return this.parseSuffix(strExpr); - } - - if (k == TokenKind.THIS) { - this.advance(); - Expr obj = Expr.thisExpr(sl, sc); - return this.parseSuffix(obj); - } - - if (k == TokenKind.SUPER) { - this.advance(); - if (this.check(TokenKind.LPAREN)) { - List superArgs = this.parseArgs(); - return Expr.ctorCall("__super__", superArgs, sl, sc); - } - Expr obj = Expr.superExpr(sl, sc); - return this.parseSuffix(obj); - } - - if (k == TokenKind.BANG) { - this.advance(); - return Expr.unary(UnaryOp.NOT, this.parsePrimary(), sl, sc); - } - if (k == TokenKind.MINUS) { - this.advance(); - return Expr.unary(UnaryOp.NEG, this.parsePrimary(), sl, sc); - } - if (k == TokenKind.TILDE) { - this.advance(); - return Expr.unary(UnaryOp.BITNOT, this.parsePrimary(), sl, sc); - } - if (k == TokenKind.PLUS_PLUS) { - this.advance(); - return Expr.unary(UnaryOp.PRE_INC, this.parsePrimary(), sl, sc); - } - if (k == TokenKind.MINUS_MINUS) { - this.advance(); - return Expr.unary(UnaryOp.PRE_DEC, this.parsePrimary(), sl, sc); - } - - if (k == TokenKind.AWAIT) { - this.advance(); - Expr inner = this.parsePrimary(); - return Expr.await_(inner, sl, sc); - } - - if (this.isLambdaStart()) { - this.advance(); // consume ( - return this.parseLambda(sl, sc); - } - - if (k == TokenKind.LPAREN) { - this.advance(); - Expr inner = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - return this.parseSuffix(inner); - } - - if (k == TokenKind.MATCH) { - this.advance(); - Expr subject = this.parseExpr(0); - this.eat(TokenKind.LBRACE); - List arms = List(); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - MatchPattern pat = this.parseMatchPattern(); - this.eat(TokenKind.FAT_ARROW); - Expr body = this.parseExpr(0); - if (this.check(TokenKind.COMMA)) { this.advance(); } - arms.append(MatchArm(pat, body)); - } - this.eat(TokenKind.RBRACE); - return Expr.match_(subject, arms, sl, sc); - } - - if (k == TokenKind.STD_IO || k == TokenKind.STD_MATH || - k == TokenKind.STD_TIME || k == TokenKind.STD_MEMORY) { - String cls = this.stubName(k); - this.advance(); - Expr obj = Expr.ident(cls, sl, sc); - return this.parseSuffix(obj); - } - - if (k == TokenKind.IDENT) { - String name = this.advance().strVal; - if (this.check(TokenKind.LPAREN)) { - List args = this.parseArgs(); - Expr call = Expr.ctorCall(name, args, sl, sc); - return this.parseSuffix(call); - } - Expr base = Expr.ident(name, sl, sc); - return this.parseSuffix(base); - } - - if (this.isSimpleTypeToken(k)) { - String name = this.typeTokenName(k); - this.advance(); - Expr base = Expr.ident(name, sl, sc); - return this.parseSuffix(base); - } - - if (k == TokenKind.TYPE_LIST || k == TokenKind.TYPE_HASHMAP || - k == TokenKind.TYPE_MAP || k == TokenKind.TYPE_TREEMAP || - k == TokenKind.TYPE_PAIR) { - String colName = ""; - if (k == TokenKind.TYPE_LIST) { colName = "List"; } - if (k == TokenKind.TYPE_HASHMAP) { colName = "HashMap"; } - if (k == TokenKind.TYPE_MAP) { colName = "Map"; } - if (k == TokenKind.TYPE_TREEMAP) { colName = "TreeMap"; } - if (k == TokenKind.TYPE_PAIR) { colName = "Pair"; } - this.advance(); - if (this.check(TokenKind.LPAREN)) { - List args = this.parseArgs(); - return this.parseSuffix(Expr.ctorCall(colName, args, sl, sc)); - } - return this.parseSuffix(Expr.ident(colName, sl, sc)); - } - - IO.error("parse error at ${sl}:${sc} - unexpected token in expression: ${k}"); - this.hasError = true; - this.advance(); - return Expr.nullLit(sl, sc); - } - - private stubName(k: Integer) : String { - if (k == TokenKind.STD_IO) { return "IO"; } - if (k == TokenKind.STD_MATH) { return "Math"; } - if (k == TokenKind.STD_TIME) { return "Time"; } - if (k == TokenKind.STD_MEMORY) { return "Memory"; } - return ""; - } - - private typeTokenName(k: Integer) : String { - if (k == TokenKind.TYPE_INTEGER) { return "Integer"; } - if (k == TokenKind.TYPE_FLOAT) { return "Float"; } - if (k == TokenKind.TYPE_BOOLEAN) { return "Boolean"; } - if (k == TokenKind.TYPE_STRING) { return "String"; } - if (k == TokenKind.TYPE_U8) { return "u8"; } - if (k == TokenKind.TYPE_U16) { return "u16"; } - if (k == TokenKind.TYPE_U32) { return "u32"; } - if (k == TokenKind.TYPE_U64) { return "u64"; } - if (k == TokenKind.TYPE_I8) { return "i8"; } - if (k == TokenKind.TYPE_I16) { return "i16"; } - if (k == TokenKind.TYPE_I32) { return "i32"; } - if (k == TokenKind.TYPE_I64) { return "i64"; } - return "Unknown"; - } - - private parseSuffix(base: Expr) : Expr { - Expr e = base; - Boolean cont = true; - while (cont) { - Integer sl = this.peek().line; - Integer sc = this.peek().col; - Integer k = this.peek().kind; - - if (k == TokenKind.DOT) { - this.advance(); - String member = ""; - if (this.check(TokenKind.IDENT)) { - member = this.advance().strVal; - } else { - IO.error("parse error at ${sl}:${sc} - expected member name after '.'"); - this.hasError = true; - cont = false; - } - if (this.check(TokenKind.LPAREN)) { - List args = this.parseArgs(); - e = Expr.methodCall(e, member, args, sl, sc); - } else { - e = Expr.fieldAccess(e, member, sl, sc); - } - } - else if (k == TokenKind.QUESTION_DOT) { - this.advance(); - String member = this.eatIdent(); - List args = List(); - if (this.check(TokenKind.LPAREN)) { - args = this.parseArgs(); - } - e = Expr.nullSafe(e, member, args, sl, sc); - } - else if (k == TokenKind.LBRACKET) { - this.advance(); - Expr idx = this.parseExpr(0); - this.eat(TokenKind.RBRACKET); - e = Expr.index(e, idx, sl, sc); - } - else if (k == TokenKind.PLUS_PLUS) { - this.advance(); - e = Expr.unary(UnaryOp.POST_INC, e, sl, sc); - } - else if (k == TokenKind.MINUS_MINUS) { - this.advance(); - e = Expr.unary(UnaryOp.POST_DEC, e, sl, sc); - } - else if (k == TokenKind.AS) { - this.advance(); - AstType ty = this.parseType(); - e = Expr.cast(e, ty, sl, sc); - } - else { - cont = false; - } - } - return e; - } - - - private parseMatchPattern() : MatchPattern { - if (this.check(TokenKind.IDENT) && this.peek().strVal == "_") { - this.advance(); - return MatchPattern.wildcard(); - } - - if (this.check(TokenKind.STR)) { - String s = this.advance().strVal; - return MatchPattern.ofStrLit(s); - } - - if (this.check(TokenKind.INT)) { - Integer n = this.advance().intVal; - return MatchPattern.ofIntLit(n); - } - - if (this.check(TokenKind.IDENT)) { - String first = this.advance().strVal; - if (this.check(TokenKind.DOT)) { - this.advance(); - String variant = this.eatIdent(); - List bindings = List(); - if (this.check(TokenKind.LPAREN)) { - this.advance(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - bindings.append(this.eatIdent()); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - } - return MatchPattern.variant(first, variant, bindings); - } - return MatchPattern.binding(first); - } - - IO.error("parse error - unexpected token in match pattern"); - this.hasError = true; - return MatchPattern.wildcard(); - } - - - public parseBlock() : List { - this.eat(TokenKind.LBRACE); - List stmts = List(); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - Stmt s = this.parseStmt(); - if (s != null) { stmts.append(s); } - } - this.eat(TokenKind.RBRACE); - return stmts; - } - - private looksLikeVarDecl() : Boolean { - Integer k = this.peek().kind; - if (this.isSimpleTypeToken(k)) { return true; } - if (k == TokenKind.TYPE_LIST || k == TokenKind.TYPE_HASHMAP || - k == TokenKind.TYPE_MAP || k == TokenKind.TYPE_TREEMAP || - k == TokenKind.TYPE_PAIR || k == TokenKind.TYPE_RAWPTR || - k == TokenKind.TYPE_ARRAY || k == TokenKind.TYPE_SLICE || - k == TokenKind.TYPE_EXCEPTION) { return true; } - if (k == TokenKind.IDENT) { - Integer nk = this.peekAt(1).kind; - if (nk == TokenKind.IDENT) { return true; } - if (nk == TokenKind.QUESTION) { return this.peekAt(2).kind == TokenKind.IDENT; } - return false; - } - return false; - } - - public parseStmt() : Stmt { - Token t = this.peek(); - Integer sl = t.line; - Integer sc = t.col; - Integer k = t.kind; - - if (k == TokenKind.RETURN) { - this.advance(); - Expr e = Expr(1, 0, 0); - if (!this.check(TokenKind.SEMICOLON) && !this.check(TokenKind.RBRACE)) { - e = this.parseExpr(0); - } - this.skipSemicolon(); - return Stmt.return_(e, sl, sc); - } - - if (k == TokenKind.THROW) { - this.advance(); - Expr e = this.parseExpr(0); - this.skipSemicolon(); - return Stmt.throw_(e, sl, sc); - } - - if (k == TokenKind.BREAK) { - this.advance(); - this.skipSemicolon(); - return Stmt.break_(sl, sc); - } - - if (k == TokenKind.CONTINUE) { - this.advance(); - this.skipSemicolon(); - return Stmt.continue_(sl, sc); - } - - if (k == TokenKind.DEFER) { - this.advance(); - Expr e = this.parseExpr(0); - this.skipSemicolon(); - return Stmt.defer_(e, sl, sc); - } - - if (k == TokenKind.IF) { - this.advance(); - this.eat(TokenKind.LPAREN); - Expr cond = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - List then_ = this.parseBlock(); - List elseIfs = List(); - List else_ = List(); - while (this.check(TokenKind.ELSE)) { - this.advance(); - if (this.check(TokenKind.IF)) { - this.advance(); - this.eat(TokenKind.LPAREN); - Expr eic = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - List eib = this.parseBlock(); - elseIfs.append(ElseIfBranch(eic, eib)); - } else { - else_ = this.parseBlock(); - } - } - return Stmt.if_(cond, then_, elseIfs, else_, sl, sc); - } - - if (k == TokenKind.WHILE) { - this.advance(); - this.eat(TokenKind.LPAREN); - Expr cond = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - List body = this.parseBlock(); - return Stmt.while_(cond, body, sl, sc); - } - - if (k == TokenKind.FOR) { - this.advance(); - this.eat(TokenKind.LPAREN); - AstType ty = this.parseType(); - String name = this.eatIdent(); - if (this.check(TokenKind.COLON) || (this.check(TokenKind.IDENT) && this.peek().strVal == "in")) { - this.advance(); // consume : or 'in' - Expr iter = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - List body = this.parseBlock(); - return Stmt.forEach(ty, name, iter, body, sl, sc); - } - this.eat(TokenKind.EQ); - Expr initVal = this.parseExpr(0); - Stmt init = Stmt.varDecl(ty, name, initVal, sl, sc); - this.eat(TokenKind.SEMICOLON); - Expr cond = this.parseExpr(0); - this.eat(TokenKind.SEMICOLON); - Expr step = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - List body = this.parseBlock(); - return Stmt.for_(init, cond, step, body, sl, sc); - } - - if (k == TokenKind.SWITCH) { - this.advance(); - this.eat(TokenKind.LPAREN); - Expr expr = this.parseExpr(0); - this.eat(TokenKind.RPAREN); - this.eat(TokenKind.LBRACE); - List cases = List(); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - if (this.check(TokenKind.DEFAULT)) { - this.advance(); - this.eat(TokenKind.COLON); - List cbody = this.parseCaseBody(); - SwitchCase dsc = SwitchCase(true, cbody); - cases.append(dsc); - } else { - this.eat(TokenKind.CASE); - Expr pat = this.parseExpr(0); - this.eat(TokenKind.COLON); - List cbody = this.parseCaseBody(); - SwitchCase csc = SwitchCase(false, cbody); - csc.pattern = pat; - cases.append(csc); - } - } - this.eat(TokenKind.RBRACE); - return Stmt.switch_(expr, cases, sl, sc); - } - - if (k == TokenKind.TRY) { - this.advance(); - List tryBody = this.parseBlock(); - List catches = List(); - while (this.check(TokenKind.CATCH)) { - this.advance(); - this.eat(TokenKind.LPAREN); - List exTypes = List(); - exTypes.append(this.parseType()); - while (this.check(TokenKind.PIPE)) { - this.advance(); - exTypes.append(this.parseType()); - } - String exName = this.eatIdent(); - this.eat(TokenKind.RPAREN); - List cbody = this.parseBlock(); - catches.append(CatchClause(exTypes, exName, cbody)); - } - List finally_ = List(); - if (this.check(TokenKind.FINALLY)) { - this.advance(); - finally_ = this.parseBlock(); - } - return Stmt.try_(tryBody, catches, finally_, sl, sc); - } - - if (k == TokenKind.ASM) { - this.advance(); - this.eat(TokenKind.LBRACE); - String asmText = ""; - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - if (this.check(TokenKind.STR)) { - asmText = asmText.concat(this.advance().strVal).concat("\n"); - } else { - this.advance(); - } - } - this.eat(TokenKind.RBRACE); - return Stmt.asm_(asmText, sl, sc); - } - - if (k == TokenKind.VOLATILE) { - this.advance(); - AstType ty = this.parseType(); - String name2 = this.eatIdent(); - Expr initSentinel = Expr(1, 0, 0); - if (this.check(TokenKind.EQ)) { this.advance(); initSentinel = this.parseExpr(0); } - this.skipSemicolon(); - return Stmt.volatileVarDecl(ty, name2, initSentinel, sl, sc); - } - - if (this.looksLikeVarDecl()) { - AstType ty = this.parseType(); - String name3 = this.eatIdent(); - Expr initSentinel = Expr(1, 0, 0); - if (this.check(TokenKind.EQ)) { this.advance(); initSentinel = this.parseExpr(0); } - this.skipSemicolon(); - return Stmt.varDecl(ty, name3, initSentinel, sl, sc); - } - - Expr e = this.parseExpr(0); - this.skipSemicolon(); - return Stmt.exprStmt(e, sl, sc); - } - - private parseCaseBody() : List { - List stmts = List(); - while (!this.check(TokenKind.CASE) && - !this.check(TokenKind.DEFAULT) && - !this.check(TokenKind.RBRACE) && - !this.check(TokenKind.EOF)) { - Stmt s = this.parseStmt(); - if (s != null) { stmts.append(s); } - } - return stmts; - } - - - private parseAnnotations() : List { - List annots = List(); - while (this.check(TokenKind.AT)) { - this.advance(); - String name = this.eatIdent(); - if (this.check(TokenKind.LPAREN)) { - this.advance(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - this.advance(); - } - this.eat(TokenKind.RPAREN); - } - annots.append(name); - } - return annots; - } - - - private parseGenericBounds() : List { - List bounds = List(); - if (this.check(TokenKind.LT)) { - this.advance(); - while (!this.check(TokenKind.GT) && !this.check(TokenKind.EOF)) { - String name = this.eatIdent(); - GenericBound gb = GenericBound(name); - if (this.check(TokenKind.COLON)) { - this.advance(); - gb.bounds.append(this.eatIdent()); - while (this.check(TokenKind.PLUS)) { - this.advance(); - gb.bounds.append(this.eatIdent()); - } - } - bounds.append(gb); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.GT); - } - return bounds; - } - - - private parseParams() : List { - List params = List(); - this.eat(TokenKind.LPAREN); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - Boolean isVariadic = false; - if (this.check(TokenKind.ELLIPSIS)) { this.advance(); isVariadic = true; } - String name = this.eatIdent(); - this.eat(TokenKind.COLON); - AstType ty = this.parseType(); - Param p = Param(name, ty); - p.isVariadic = isVariadic; - if (this.check(TokenKind.EQ)) { - this.advance(); - p.default_ = this.parseExpr(0); - } - params.append(p); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - return params; - } - - - private parseMethod(vis: Integer, isStatic: Boolean, isAbstract: Boolean, - isDefault: Boolean, isOverride: Boolean, - isAsync: Boolean, isOperator: Boolean) : MethodDecl { - String name = ""; - if (isOperator) { - name = "operator_" + this.operatorSymbol(); - } else { - name = this.eatIdent(); - } - MethodDecl m = MethodDecl(vis, name); - m.isStatic = isStatic; - m.isAbstract = isAbstract; - m.isDefault = isDefault; - m.isOverride = isOverride; - m.isAsync = isAsync; - m.isOperator = isOperator; - m.params = this.parseParams(); - if (this.check(TokenKind.COLON)) { - this.advance(); - m.returnTy = this.parseType(); - } - if (this.check(TokenKind.LBRACE)) { - m.body = this.parseBlock(); - } else { - this.skipSemicolon(); - } - return m; - } - - private operatorSymbol() : String { - Integer k = this.peek().kind; - this.advance(); - if (k == TokenKind.PLUS) { return "add"; } - if (k == TokenKind.MINUS) { return "sub"; } - if (k == TokenKind.STAR) { return "mul"; } - if (k == TokenKind.SLASH) { return "div"; } - if (k == TokenKind.PERCENT) { return "mod"; } - if (k == TokenKind.EQ_EQ) { return "eq"; } - if (k == TokenKind.BANG_EQ) { return "ne"; } - if (k == TokenKind.LT) { return "lt"; } - if (k == TokenKind.GT) { return "gt"; } - if (k == TokenKind.LT_EQ) { return "le"; } - if (k == TokenKind.GT_EQ) { return "ge"; } - return "op"; - } - - - private parseField(vis: Integer, isStatic: Boolean, isReadonly: Boolean) : FieldDecl { - String name = this.eatIdent(); - this.eat(TokenKind.COLON); - AstType ty = this.parseType(); - FieldDecl f = FieldDecl(vis, name, ty); - f.isStatic = isStatic; - f.isReadonly = isReadonly; - if (this.check(TokenKind.EQ)) { - this.advance(); - f.init = this.parseExpr(0); - } - this.skipSemicolon(); - return f; - } - - - private parseClassBody(classDecl: ClassDecl) : Void { - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List annots = this.parseAnnotations(); - - Integer vis = Visibility.PRIVATE; - Boolean isStatic = false; - Boolean isAbstract = false; - Boolean isOverride = false; - Boolean isReadonly = false; - Boolean isAsync = false; - Boolean isOperator = false; - Boolean isDefault = false; - - if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } - else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } - else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } - else if (this.check(TokenKind.INTERNAL)) { this.advance(); vis = Visibility.INTERNAL; } - - Boolean contMod = true; - while (contMod) { - if (this.check(TokenKind.STATIC)) { this.advance(); isStatic = true; } - else if (this.check(TokenKind.ABSTRACT)) { this.advance(); isAbstract = true; } - else if (this.check(TokenKind.OVERRIDE)) { this.advance(); isOverride = true; } - else if (this.check(TokenKind.READONLY)) { this.advance(); isReadonly = true; } - else if (this.check(TokenKind.ASYNC)) { this.advance(); isAsync = true; } - else if (this.check(TokenKind.DEFAULT)) { this.advance(); isDefault = true; } - else if (this.check(TokenKind.OPERATOR)) { this.advance(); isOperator = true; } - else { contMod = false; } - } - - if (this.check(TokenKind.CONSTRUCTOR)) { - this.advance(); - List params = this.parseParams(); - List body = this.parseBlock(); - classDecl.ctor = ConstructorDecl(vis, params, body); - } - else if (isOperator || this.peek().kind == TokenKind.IDENT) { - if (!isOperator && this.peekAt(1).kind == TokenKind.COLON) { - classDecl.fields.append(this.parseField(vis, isStatic, isReadonly)); - } else { - classDecl.methods.append( - this.parseMethod(vis, isStatic, isAbstract, isDefault, isOverride, isAsync, isOperator) - ); - } - } - else { - this.advance(); - } - } - this.eat(TokenKind.RBRACE); - } - - - private parseClassDecl(vis: Integer, isAbstract: Boolean, isManual: Boolean, - isSealed: Boolean, isImmutable: Boolean) : Item { - String name = this.eatIdent(); - ClassDecl c = ClassDecl(vis, name); - c.isAbstract = isAbstract; - c.isManual = isManual; - c.isSealed = isSealed; - c.isImmutable = isImmutable; - c.generics = this.parseGenericBounds(); - if (this.check(TokenKind.EXTENDS)) { - this.advance(); - if (this.check(TokenKind.TYPE_EXCEPTION)) { this.advance(); c.extends_ = "Exception"; } - else { c.extends_ = this.eatIdent(); } - } - if (this.check(TokenKind.IMPLEMENTS)) { - this.advance(); - c.implements_.append(this.eatIdent()); - while (this.check(TokenKind.COMMA)) { - this.advance(); - c.implements_.append(this.eatIdent()); - } - } - this.parseClassBody(c); - return Item.fromClass(c); - } - - private parseInterfaceDecl(vis: Integer) : Item { - String name = this.eatIdent(); - InterfaceDecl iface = InterfaceDecl(vis, name); - iface.generics = this.parseGenericBounds(); - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List annots = this.parseAnnotations(); - Boolean isDefault = false; - if (this.check(TokenKind.DEFAULT)) { this.advance(); isDefault = true; } - MethodDecl m = this.parseMethod(Visibility.PUBLIC, false, !isDefault, isDefault, false, false, false); - iface.methods.append(m); - } - this.eat(TokenKind.RBRACE); - return Item.fromInterface(iface); - } - - private parseEnumDecl(vis: Integer) : Item { - String name = this.eatIdent(); - EnumDecl e = EnumDecl(vis, name); - this.eat(TokenKind.LBRACE); - Boolean inVariants = true; - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - Integer k = this.peek().kind; - if (k == TokenKind.PUBLIC || k == TokenKind.PRIVATE || - k == TokenKind.PROTECTED || k == TokenKind.INTERNAL || - k == TokenKind.STATIC || k == TokenKind.ABSTRACT) { - inVariants = false; - } - if (!inVariants) { - List an = this.parseAnnotations(); - Integer mvis = Visibility.PUBLIC; - if (this.check(TokenKind.PUBLIC)) { this.advance(); } - Boolean mStatic = false; - if (this.check(TokenKind.STATIC)) { this.advance(); mStatic = true; } - MethodDecl m = this.parseMethod(mvis, mStatic, false, false, false, false, false); - e.methods.append(m); - } else { - String varName = this.eatIdent(); - EnumVariant v = EnumVariant(varName); - if (this.check(TokenKind.LPAREN)) { - this.advance(); - while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { - v.data.append(this.parseType()); - if (this.check(TokenKind.COMMA)) { this.advance(); } - } - this.eat(TokenKind.RPAREN); - } - e.variants.append(v); - if (this.check(TokenKind.COMMA) || this.check(TokenKind.SEMICOLON)) { - this.advance(); - } - } - } - this.eat(TokenKind.RBRACE); - return Item.fromEnum(e); - } - - private parseStructDecl(vis: Integer, isPacked: Boolean) : Item { - String name = this.eatIdent(); - StructDecl s = StructDecl(vis, name); - s.isPacked = isPacked; - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List an = this.parseAnnotations(); - Integer mvis = Visibility.PUBLIC; - Boolean isSt = false; - Boolean isRo = false; - if (this.check(TokenKind.PUBLIC)) { this.advance(); mvis = Visibility.PUBLIC; } - else if (this.check(TokenKind.PRIVATE)) { this.advance(); mvis = Visibility.PRIVATE; } - if (this.check(TokenKind.STATIC)) { this.advance(); isSt = true; } - if (this.check(TokenKind.READONLY)) { this.advance(); isRo = true; } - if (this.check(TokenKind.OPERATOR)) { - this.advance(); - s.methods.append(this.parseMethod(mvis, false, false, false, false, false, true)); - } else if (this.peekAt(1).kind == TokenKind.COLON) { - s.fields.append(this.parseField(mvis, isSt, isRo)); - } else { - s.methods.append(this.parseMethod(mvis, isSt, false, false, false, false, false)); - } - } - this.eat(TokenKind.RBRACE); - return Item.fromStruct(s); - } - - private parseExceptionDecl(vis: Integer, isManual: Boolean) : Item { - String name = this.eatIdent(); - String ext = "Exception"; - if (this.check(TokenKind.EXTENDS)) { - this.advance(); - if (this.check(TokenKind.TYPE_EXCEPTION)) { this.advance(); ext = "Exception"; } - else { ext = this.eatIdent(); } - } - ExceptionDecl ex = ExceptionDecl(vis, name, ext); - ex.isManual = isManual; - this.parseExceptionBody(ex); - return Item.fromException(ex); - } - - private parseExceptionBody(ex: ExceptionDecl) : Void { - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List annots = this.parseAnnotations(); - Integer vis = Visibility.PRIVATE; - Boolean isStatic = false; - Boolean isOverride = false; - Boolean isReadonly = false; - Boolean isAsync = false; - if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } - else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } - else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } - Boolean contMod = true; - while (contMod) { - if (this.check(TokenKind.STATIC)) { this.advance(); isStatic = true; } - else if (this.check(TokenKind.OVERRIDE)) { this.advance(); isOverride = true; } - else if (this.check(TokenKind.READONLY)) { this.advance(); isReadonly = true; } - else if (this.check(TokenKind.ASYNC)) { this.advance(); isAsync = true; } - else { contMod = false; } - } - if (this.check(TokenKind.CONSTRUCTOR)) { - this.advance(); - List params = this.parseParams(); - List body = this.parseBlock(); - ex.ctor = ConstructorDecl(vis, params, body); - } else if (this.peekAt(1).kind == TokenKind.COLON) { - ex.fields.append(this.parseField(vis, isStatic, isReadonly)); - } else { - ex.methods.append( - this.parseMethod(vis, isStatic, false, false, isOverride, isAsync, false) - ); - } - } - this.eat(TokenKind.RBRACE); - } - - private parseExternDecl() : Item { - String abi = "C"; - if (this.check(TokenKind.STR)) { abi = this.advance().strVal; } - ExternDecl ext = ExternDecl(abi); - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - String fname = this.eatIdent(); - ExternFnDecl fn = ExternFnDecl(fname); - fn.params = this.parseParams(); - if (this.check(TokenKind.COLON)) { - this.advance(); - fn.returnTy = this.parseType(); - } - this.skipSemicolon(); - ext.decls.append(fn); - } - this.eat(TokenKind.RBRACE); - return Item.fromExtern(ext); - } - - private parseExtensionDecl() : Item { - String target = this.eatIdent(); - ExtensionDecl ex = ExtensionDecl(target); - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List an = this.parseAnnotations(); - Integer mvis = Visibility.PUBLIC; - Boolean mSt = false; - if (this.check(TokenKind.PUBLIC)) { this.advance(); } - if (this.check(TokenKind.STATIC)) { this.advance(); mSt = true; } - ex.methods.append(this.parseMethod(mvis, mSt, false, false, false, false, false)); - } - this.eat(TokenKind.RBRACE); - return Item.fromExtension(ex); - } - - private parseUnionDecl(vis: Integer) : Item { - String name = this.eatIdent(); - UnionDecl u = UnionDecl(vis, name); - this.eat(TokenKind.LBRACE); - while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { - List an = this.parseAnnotations(); - String fname = this.eatIdent(); - this.eat(TokenKind.COLON); - AstType ty = this.parseType(); - FieldDecl f = FieldDecl(Visibility.PUBLIC, fname, ty); - u.fields.append(f); - this.skipSemicolon(); - } - this.eat(TokenKind.RBRACE); - return Item.fromUnion(u); - } - - private parseTypeAliasDecl() : Item { - String name = this.eatIdent(); - this.eat(TokenKind.EQ); - AstType ty = this.parseType(); - this.skipSemicolon(); - return Item.fromAlias(TypeAliasDecl(name, ty)); - } - - public parseItem() : Item { - List annots = this.parseAnnotations(); - - Boolean isManual = false; - Boolean isSealed = false; - Boolean isImmutable = false; - Boolean isPacked = false; - - Integer ai = 0; - while (ai < annots.length()) { - String an = annots.get(ai); - if (an == "ManualMemory") { isManual = true; } - if (an == "Sealed") { isSealed = true; } - if (an == "Immutable") { isImmutable = true; } - if (an == "Packed") { isPacked = true; } - ai++; - } - - Integer vis = Visibility.PUBLIC; - Boolean isAbstract = false; - - if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } - else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } - else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } - else if (this.check(TokenKind.INTERNAL)) { this.advance(); vis = Visibility.INTERNAL; } - - if (this.check(TokenKind.ABSTRACT)) { this.advance(); isAbstract = true; } - - if (this.check(TokenKind.CLASS)) { - this.advance(); - return this.parseClassDecl(vis, isAbstract, isManual, isSealed, isImmutable); - } - if (this.check(TokenKind.INTERFACE)) { - this.advance(); - return this.parseInterfaceDecl(vis); - } - if (this.check(TokenKind.ENUM)) { - this.advance(); - return this.parseEnumDecl(vis); - } - if (this.check(TokenKind.STRUCT)) { - this.advance(); - return this.parseStructDecl(vis, isPacked); - } - if (this.check(TokenKind.IDENT) && this.peek().strVal == "exception") { - this.advance(); - return this.parseExceptionDecl(vis, isManual); - } - if (this.check(TokenKind.EXTERN)) { - this.advance(); - return this.parseExternDecl(); - } - if (this.check(TokenKind.EXTEND)) { - this.advance(); - return this.parseExtensionDecl(); - } - if (this.check(TokenKind.UNION)) { - this.advance(); - return this.parseUnionDecl(vis); - } - if (this.check(TokenKind.KW_TYPE)) { - this.advance(); - return this.parseTypeAliasDecl(); - } - - if (this.check(TokenKind.CONST)) { - this.advance(); - AstType cty = this.parseType(); - String cnam = this.eatIdent(); - this.eat(TokenKind.EQ); - Expr cval = this.parseExpr(0); - this.skipSemicolon(); - return Item.fromConst(cnam, cty, cval); - } - - Token t = this.peek(); - IO.error("parse error at ${t.line}:${t.col} - unexpected token at top level: ${t.kind}"); - this.hasError = true; - this.advance(); - return Item.fromClass(ClassDecl(Visibility.PUBLIC, "Error")); - } - - - public parse() : ArimoModule { - ArimoModule mod = ArimoModule(""); - if (this.check(TokenKind.PACKAGE)) { - this.advance(); - String path = this.parseDottedName(); - mod.path = path; - this.skipSemicolon(); - } - - while (this.check(TokenKind.IMPORT)) { - this.advance(); - String imp = this.parseDottedName(); - mod.imports.append(imp); - this.skipSemicolon(); - } - - while (!this.check(TokenKind.EOF)) { - Item item = this.parseItem(); - mod.items.append(item); - } - return mod; - } - - private parseDottedName() : String { - String name = this.eatIdent(); - while (this.check(TokenKind.DOT)) { - this.advance(); - if (this.check(TokenKind.STAR)) { - this.advance(); - name = name.concat(".*"); - } else { - name = name.concat(".").concat(this.eatIdent()); - } - } - return name; - } -} +/* +Arimo Lang Compiler - A modern programming language and compiler +Copyright (C) 2026 Egecan Akıncıoğlu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package arimo.compiler.parser; + +import arimo.compiler.token.TokenKind; +import arimo.compiler.token.Token; +import arimo.compiler.ast.ArimoModule; +import arimo.compiler.ast.types.TypeKind; +import arimo.compiler.ast.types.AstType; +import arimo.compiler.ast.expr.ExprKind; +import arimo.compiler.ast.expr.BinaryOp; +import arimo.compiler.ast.expr.UnaryOp; +import arimo.compiler.ast.expr.MatchPatternKind; +import arimo.compiler.ast.expr.MatchPattern; +import arimo.compiler.ast.expr.MatchArm; +import arimo.compiler.ast.expr.StrPart; +import arimo.compiler.ast.expr.Expr; +import arimo.compiler.ast.stmt.StmtKind; +import arimo.compiler.ast.stmt.ElseIfBranch; +import arimo.compiler.ast.stmt.CatchClause; +import arimo.compiler.ast.stmt.SwitchCase; +import arimo.compiler.ast.stmt.Stmt; +import arimo.compiler.ast.decl.Visibility; +import arimo.compiler.ast.decl.GenericBound; +import arimo.compiler.ast.decl.Param; +import arimo.compiler.ast.decl.FieldDecl; +import arimo.compiler.ast.decl.MethodDecl; +import arimo.compiler.ast.decl.ConstructorDecl; +import arimo.compiler.ast.decl.EnumVariant; +import arimo.compiler.ast.decl.ExternFnDecl; +import arimo.compiler.ast.item.ItemKind; +import arimo.compiler.ast.item.ClassDecl; +import arimo.compiler.ast.item.InterfaceDecl; +import arimo.compiler.ast.item.EnumDecl; +import arimo.compiler.ast.item.StructDecl; +import arimo.compiler.ast.item.ExceptionDecl; +import arimo.compiler.ast.item.TypeAliasDecl; +import arimo.compiler.ast.item.ExternDecl; +import arimo.compiler.ast.item.ExtensionDecl; +import arimo.compiler.ast.item.UnionDecl; +import arimo.compiler.ast.item.Item; + +public class Parser { + private tokens : List; + private pos : Integer; + private eofTok : Token; + public hasError : Boolean; + + public constructor(tokens: List) { + this.tokens = tokens; + this.pos = 0; + this.hasError = false; + this.eofTok = Token.ofSimple(TokenKind.EOF, 0, 0); + } + + + private peek() : Token { + if (this.pos >= this.tokens.length()) { return this.eofTok; } + return this.tokens.get(this.pos); + } + + private peekAt(offset: Integer) : Token { + Integer p = this.pos + offset; + if (p >= this.tokens.length()) { return this.eofTok; } + return this.tokens.get(p); + } + + private advance() : Token { + Token t = this.peek(); + if (this.pos < this.tokens.length()) { this.pos = this.pos + 1; } + return t; + } + + private check(kind: Integer) : Boolean { + Integer k = this.peek().kind; + if (k == kind) { return true; } + // GT_GT (>>) acts as GT (>) for nested generics + if (kind == TokenKind.GT && k == TokenKind.GT_GT) { return true; } + return false; + } + + private checkAny2(k1: Integer, k2: Integer) : Boolean { + Integer k = this.peek().kind; + return k == k1 || k == k2; + } + + private eat(kind: Integer) : Token { + // GT_GT (>>) split for nested generics: List> + if (kind == TokenKind.GT && this.peek().kind == TokenKind.GT_GT) { + Token gtGt = this.advance(); // consume GT_GT + Integer line = gtGt.line; + Integer col = gtGt.col; + gtGt.kind = TokenKind.GT; // first > returned now + // Insert second > token right after for next eat(GT) call + Integer insertPos = this.pos; + this.tokens.append(Token.ofSimple(TokenKind.GT, line, col + 1)); + Integer i = this.tokens.length() - 1; + while (i > insertPos) { + this.tokens.set(i, this.tokens.get(i - 1)); + i = i - 1; + } + this.tokens.set(insertPos, Token.ofSimple(TokenKind.GT, line, col + 1)); + return gtGt; + } + if (!this.check(kind)) { + Token t = this.peek(); + IO.error("parse error at ${t.line}:${t.col} - expected token ${kind} got ${t.kind}"); + this.hasError = true; + return this.eofTok; + } + return this.advance(); + } + + private eatIdent() : String { + if (!this.check(TokenKind.IDENT)) { + Token t = this.peek(); + IO.error("parse error at ${t.line}:${t.col} - expected identifier"); + this.hasError = true; + return ""; + } + return this.advance().strVal; + } + + private skipSemicolon() : Void { + if (this.check(TokenKind.SEMICOLON)) { this.advance(); } + } + + + private typeKindForToken(k: Integer) : Integer { + if (k == TokenKind.TYPE_INTEGER) { return TypeKind.INTEGER; } + if (k == TokenKind.TYPE_FLOAT) { return TypeKind.FLOAT; } + if (k == TokenKind.TYPE_BOOLEAN) { return TypeKind.BOOLEAN; } + if (k == TokenKind.TYPE_STRING) { return TypeKind.STR; } + if (k == TokenKind.TYPE_CHAR) { return TypeKind.CHAR; } + if (k == TokenKind.TYPE_VOID) { return TypeKind.VOID; } + if (k == TokenKind.TYPE_U8) { return TypeKind.U8; } + if (k == TokenKind.TYPE_U16) { return TypeKind.U16; } + if (k == TokenKind.TYPE_U32) { return TypeKind.U32; } + if (k == TokenKind.TYPE_U64) { return TypeKind.U64; } + if (k == TokenKind.TYPE_I8) { return TypeKind.I8; } + if (k == TokenKind.TYPE_I16) { return TypeKind.I16; } + if (k == TokenKind.TYPE_I32) { return TypeKind.I32; } + if (k == TokenKind.TYPE_I64) { return TypeKind.I64; } + if (k == TokenKind.TYPE_NORETURN){ return TypeKind.NORETURN;} + return -1; + } + + private isSimpleTypeToken(k: Integer) : Boolean { + return this.typeKindForToken(k) != -1; + } + + + public parseType() : AstType { + AstType base = this.parseBaseType(); + if (this.check(TokenKind.QUESTION)) { + this.advance(); + return AstType.nullable(base); + } + return base; + } + + private parseBaseType() : AstType { + Token t = this.peek(); + Integer k = t.kind; + Integer sl = t.line; + Integer sc = t.col; + + Integer simpleKind = this.typeKindForToken(k); + if (simpleKind != -1) { + this.advance(); + return AstType.simple(simpleKind); + } + + if (k == TokenKind.TYPE_EXCEPTION) { + this.advance(); + return AstType.named("Exception"); + } + + if (k == TokenKind.TYPE_LIST) { + this.advance(); + this.eat(TokenKind.LT); + AstType inner = this.parseType(); + this.eat(TokenKind.GT); + return AstType.list(inner); + } + if (k == TokenKind.TYPE_HASHMAP) { + this.advance(); + this.eat(TokenKind.LT); + AstType key = this.parseType(); + this.eat(TokenKind.COMMA); + AstType val = this.parseType(); + this.eat(TokenKind.GT); + return AstType.hashmap(key, val); + } + if (k == TokenKind.TYPE_MAP) { + this.advance(); + this.eat(TokenKind.LT); + AstType key = this.parseType(); + this.eat(TokenKind.COMMA); + AstType val = this.parseType(); + this.eat(TokenKind.GT); + return AstType.map(key, val); + } + if (k == TokenKind.TYPE_TREEMAP) { + this.advance(); + this.eat(TokenKind.LT); + AstType key = this.parseType(); + this.eat(TokenKind.COMMA); + AstType val = this.parseType(); + this.eat(TokenKind.GT); + return AstType.treemap(key, val); + } + if (k == TokenKind.TYPE_PAIR) { + this.advance(); + this.eat(TokenKind.LT); + AstType a = this.parseType(); + this.eat(TokenKind.COMMA); + AstType b = this.parseType(); + this.eat(TokenKind.GT); + return AstType.pair(a, b); + } + if (k == TokenKind.TYPE_RAWPTR) { + this.advance(); + this.eat(TokenKind.LT); + AstType inner = this.parseType(); + this.eat(TokenKind.GT); + return AstType.rawptr(inner); + } + if (k == TokenKind.TYPE_SLICE) { + this.advance(); + this.eat(TokenKind.LT); + AstType inner = this.parseType(); + this.eat(TokenKind.GT); + return AstType.slice(inner); + } + if (k == TokenKind.TYPE_ARRAY) { + this.advance(); + this.eat(TokenKind.LT); + AstType inner = this.parseType(); + this.eat(TokenKind.COMMA); + Integer size = this.eat(TokenKind.INT).intVal; + this.eat(TokenKind.GT); + return AstType.array(inner, size); + } + + if (k == TokenKind.LPAREN) { + this.advance(); + List params = List(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + params.append(this.parseType()); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + this.eat(TokenKind.ARROW); + AstType ret = this.parseType(); + return AstType.fnptr(params, ret); + } + + if (k == TokenKind.IDENT) { + String name = this.advance().strVal; + if (this.check(TokenKind.LT)) { + this.advance(); + List args = List(); + while (!this.check(TokenKind.GT) && !this.check(TokenKind.EOF)) { + args.append(this.parseType()); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.GT); + return AstType.generic(name, args); + } + return AstType.named(name); + } + + IO.error("parse error at ${sl}:${sc} - unexpected token in type: ${k}"); + this.hasError = true; + this.advance(); + return AstType.simple(TypeKind.VOID); + } + + + private infixPrec(kind: Integer) : Integer { + if (kind == TokenKind.EQ) { return 1; } + if (kind == TokenKind.PLUS_EQ) { return 1; } + if (kind == TokenKind.MINUS_EQ) { return 1; } + if (kind == TokenKind.STAR_EQ) { return 1; } + if (kind == TokenKind.SLASH_EQ) { return 1; } + if (kind == TokenKind.QUESTION_QUESTION) { return 2; } + if (kind == TokenKind.QUESTION) { return 3; } + if (kind == TokenKind.PIPE_PIPE) { return 4; } + if (kind == TokenKind.AND_AND) { return 5; } + if (kind == TokenKind.EQ_EQ) { return 6; } + if (kind == TokenKind.BANG_EQ) { return 6; } + if (kind == TokenKind.LT) { return 7; } + if (kind == TokenKind.LT_EQ) { return 7; } + if (kind == TokenKind.GT) { return 7; } + if (kind == TokenKind.GT_EQ) { return 7; } + if (kind == TokenKind.PIPE) { return 8; } + if (kind == TokenKind.CARET) { return 9; } + if (kind == TokenKind.AMP) { return 10; } + if (kind == TokenKind.LT_LT) { return 11; } + if (kind == TokenKind.GT_GT) { return 11; } + if (kind == TokenKind.PLUS) { return 12; } + if (kind == TokenKind.MINUS) { return 12; } + if (kind == TokenKind.STAR) { return 13; } + if (kind == TokenKind.SLASH) { return 13; } + if (kind == TokenKind.PERCENT) { return 13; } + return 0; + } + + private infixOp(kind: Integer) : Integer { + if (kind == TokenKind.EQ) { return BinaryOp.ASSIGN; } + if (kind == TokenKind.PLUS_EQ) { return BinaryOp.ADD_ASSIGN; } + if (kind == TokenKind.MINUS_EQ) { return BinaryOp.SUB_ASSIGN; } + if (kind == TokenKind.STAR_EQ) { return BinaryOp.MUL_ASSIGN; } + if (kind == TokenKind.SLASH_EQ) { return BinaryOp.DIV_ASSIGN; } + if (kind == TokenKind.PIPE_PIPE) { return BinaryOp.OR; } + if (kind == TokenKind.AND_AND) { return BinaryOp.AND; } + if (kind == TokenKind.EQ_EQ) { return BinaryOp.EQ; } + if (kind == TokenKind.BANG_EQ) { return BinaryOp.NE; } + if (kind == TokenKind.LT) { return BinaryOp.LT; } + if (kind == TokenKind.LT_EQ) { return BinaryOp.LE; } + if (kind == TokenKind.GT) { return BinaryOp.GT; } + if (kind == TokenKind.GT_EQ) { return BinaryOp.GE; } + if (kind == TokenKind.PIPE) { return BinaryOp.BITOR; } + if (kind == TokenKind.CARET) { return BinaryOp.XOR; } + if (kind == TokenKind.AMP) { return BinaryOp.BITAND; } + if (kind == TokenKind.LT_LT) { return BinaryOp.SHL; } + if (kind == TokenKind.GT_GT) { return BinaryOp.SHR; } + if (kind == TokenKind.PLUS) { return BinaryOp.ADD; } + if (kind == TokenKind.MINUS) { return BinaryOp.SUB; } + if (kind == TokenKind.STAR) { return BinaryOp.MUL; } + if (kind == TokenKind.SLASH) { return BinaryOp.DIV; } + if (kind == TokenKind.PERCENT) { return BinaryOp.MOD; } + return -1; + } + + public parseExpr(minPrec: Integer) : Expr { + Expr left = this.parsePrimary(); + + Boolean cont = true; + while (cont) { + Integer k = this.peek().kind; + Integer prec = this.infixPrec(k); + if (prec == 0 || prec <= minPrec) { cont = false; } + else { + Integer sl = this.peek().line; + Integer sc = this.peek().col; + + if (k == TokenKind.QUESTION) { + this.advance(); + Expr then_ = this.parseExpr(0); + this.eat(TokenKind.COLON); + Expr else_ = this.parseExpr(0); + left = Expr.ternary(left, then_, else_, sl, sc); + } + else if (k == TokenKind.QUESTION_QUESTION) { + this.advance(); + Expr right = this.parseExpr(prec); + left = Expr.nullCoalesce(left, right, sl, sc); + } + else if (k == TokenKind.EQ || k == TokenKind.PLUS_EQ || + k == TokenKind.MINUS_EQ || k == TokenKind.STAR_EQ || + k == TokenKind.SLASH_EQ) { + this.advance(); + Integer op = this.infixOp(k); + Expr right = this.parseExpr(prec - 1); + left = Expr.binop(op, left, right, sl, sc); + } + else { + this.advance(); + Integer op = this.infixOp(k); + Expr right = this.parseExpr(prec); + left = Expr.binop(op, left, right, sl, sc); + } + } + } + return left; + } + + private parseArgs() : List { + this.eat(TokenKind.LPAREN); + List args = List(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + args.append(this.parseExpr(0)); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + return args; + } + + private parseStringInterp(sl: Integer, sc: Integer) : Expr { + List parts = List(); + if (this.check(TokenKind.STR)) { + String text = this.advance().strVal; + if (!this.check(TokenKind.DOLLAR_LBRACE)) { + return Expr.strLit(text, sl, sc); + } + if (text.length() > 0) { parts.append(StrPart.text(text)); } + } + while (this.check(TokenKind.DOLLAR_LBRACE)) { + this.advance(); + Expr e = this.parseExpr(0); + this.eat(TokenKind.INTERP_END); + parts.append(StrPart.interp(e)); + if (this.check(TokenKind.STR)) { + String txt = this.advance().strVal; + if (txt.length() > 0) { parts.append(StrPart.text(txt)); } + } + } + return Expr.strInterp(parts, sl, sc); + } + + private parseLambda(sl: Integer, sc: Integer) : Expr { + List params = List(); + List paramTys = List(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + params.append(this.eatIdent()); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + this.eat(TokenKind.ARROW); + Expr body = this.parseExpr(0); + return Expr.lambda(params, paramTys, body, sl, sc); + } + + private isLambdaStart() : Boolean { + if (!this.check(TokenKind.LPAREN)) { return false; } + Integer save = this.pos; + this.advance(); // consume ( + Integer depth = 1; + Boolean ok = false; + while (depth > 0 && !this.check(TokenKind.EOF)) { + Integer k = this.peek().kind; + if (k == TokenKind.LPAREN) { depth = depth + 1; this.advance(); } + else if (k == TokenKind.RPAREN) { + depth = depth - 1; + this.advance(); + if (depth == 0) { + if (this.check(TokenKind.ARROW)) { ok = true; } + } + } + else { this.advance(); } + } + this.pos = save; + return ok; + } + + private parsePrimary() : Expr { + Token t = this.peek(); + Integer sl = t.line; + Integer sc = t.col; + Integer k = t.kind; + + if (k == TokenKind.NULL) { + this.advance(); + return Expr.nullLit(sl, sc); + } + + if (k == TokenKind.INT) { + Integer v = this.advance().intVal; + return Expr.intLit(v, sl, sc); + } + + if (k == TokenKind.CHAR_LIT) { + Integer cv = this.advance().intVal; + Expr ce = Expr(ExprKind.CHAR_LIT, sl, sc); + ce.intVal = cv; + return this.parseSuffix(ce); + } + + if (k == TokenKind.FLOAT) { + Float v = this.advance().floatVal; + return Expr.floatLit(v, sl, sc); + } + + if (k == TokenKind.BOOL) { + Boolean v = this.advance().boolVal; + return Expr.boolLit(v, sl, sc); + } + + if (k == TokenKind.STR || k == TokenKind.DOLLAR_LBRACE) { + Expr strExpr = this.parseStringInterp(sl, sc); + return this.parseSuffix(strExpr); + } + + if (k == TokenKind.THIS) { + this.advance(); + Expr obj = Expr.thisExpr(sl, sc); + return this.parseSuffix(obj); + } + + if (k == TokenKind.SUPER) { + this.advance(); + if (this.check(TokenKind.LPAREN)) { + List superArgs = this.parseArgs(); + return Expr.ctorCall("__super__", superArgs, sl, sc); + } + Expr obj = Expr.superExpr(sl, sc); + return this.parseSuffix(obj); + } + + if (k == TokenKind.BANG) { + this.advance(); + return Expr.unary(UnaryOp.NOT, this.parsePrimary(), sl, sc); + } + if (k == TokenKind.MINUS) { + this.advance(); + return Expr.unary(UnaryOp.NEG, this.parsePrimary(), sl, sc); + } + if (k == TokenKind.TILDE) { + this.advance(); + return Expr.unary(UnaryOp.BITNOT, this.parsePrimary(), sl, sc); + } + if (k == TokenKind.PLUS_PLUS) { + this.advance(); + return Expr.unary(UnaryOp.PRE_INC, this.parsePrimary(), sl, sc); + } + if (k == TokenKind.MINUS_MINUS) { + this.advance(); + return Expr.unary(UnaryOp.PRE_DEC, this.parsePrimary(), sl, sc); + } + + if (k == TokenKind.AWAIT) { + this.advance(); + Expr inner = this.parsePrimary(); + return Expr.await_(inner, sl, sc); + } + + if (this.isLambdaStart()) { + this.advance(); // consume ( + return this.parseLambda(sl, sc); + } + + if (k == TokenKind.LPAREN) { + this.advance(); + Expr inner = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + return this.parseSuffix(inner); + } + + if (k == TokenKind.MATCH) { + this.advance(); + Expr subject = this.parseExpr(0); + this.eat(TokenKind.LBRACE); + List arms = List(); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + MatchPattern pat = this.parseMatchPattern(); + this.eat(TokenKind.FAT_ARROW); + Expr body = this.parseExpr(0); + if (this.check(TokenKind.COMMA)) { this.advance(); } + arms.append(MatchArm(pat, body)); + } + this.eat(TokenKind.RBRACE); + return Expr.match_(subject, arms, sl, sc); + } + + if (k == TokenKind.STD_IO || k == TokenKind.STD_MATH || + k == TokenKind.STD_TIME || k == TokenKind.STD_MEMORY) { + String cls = this.stubName(k); + this.advance(); + Expr obj = Expr.ident(cls, sl, sc); + return this.parseSuffix(obj); + } + + if (k == TokenKind.IDENT) { + String name = this.advance().strVal; + if (this.check(TokenKind.LPAREN)) { + List args = this.parseArgs(); + Expr call = Expr.ctorCall(name, args, sl, sc); + return this.parseSuffix(call); + } + Expr base = Expr.ident(name, sl, sc); + return this.parseSuffix(base); + } + + if (this.isSimpleTypeToken(k)) { + String name = this.typeTokenName(k); + this.advance(); + Expr base = Expr.ident(name, sl, sc); + return this.parseSuffix(base); + } + + if (k == TokenKind.TYPE_LIST || k == TokenKind.TYPE_HASHMAP || + k == TokenKind.TYPE_MAP || k == TokenKind.TYPE_TREEMAP || + k == TokenKind.TYPE_PAIR) { + String colName = ""; + if (k == TokenKind.TYPE_LIST) { colName = "List"; } + if (k == TokenKind.TYPE_HASHMAP) { colName = "HashMap"; } + if (k == TokenKind.TYPE_MAP) { colName = "Map"; } + if (k == TokenKind.TYPE_TREEMAP) { colName = "TreeMap"; } + if (k == TokenKind.TYPE_PAIR) { colName = "Pair"; } + this.advance(); + if (this.check(TokenKind.LPAREN)) { + List args = this.parseArgs(); + return this.parseSuffix(Expr.ctorCall(colName, args, sl, sc)); + } + return this.parseSuffix(Expr.ident(colName, sl, sc)); + } + + IO.error("parse error at ${sl}:${sc} - unexpected token in expression: ${k}"); + this.hasError = true; + this.advance(); + return Expr.nullLit(sl, sc); + } + + private stubName(k: Integer) : String { + if (k == TokenKind.STD_IO) { return "IO"; } + if (k == TokenKind.STD_MATH) { return "Math"; } + if (k == TokenKind.STD_TIME) { return "Time"; } + if (k == TokenKind.STD_MEMORY) { return "Memory"; } + return ""; + } + + private typeTokenName(k: Integer) : String { + if (k == TokenKind.TYPE_INTEGER) { return "Integer"; } + if (k == TokenKind.TYPE_FLOAT) { return "Float"; } + if (k == TokenKind.TYPE_BOOLEAN) { return "Boolean"; } + if (k == TokenKind.TYPE_STRING) { return "String"; } + if (k == TokenKind.TYPE_U8) { return "u8"; } + if (k == TokenKind.TYPE_U16) { return "u16"; } + if (k == TokenKind.TYPE_U32) { return "u32"; } + if (k == TokenKind.TYPE_U64) { return "u64"; } + if (k == TokenKind.TYPE_I8) { return "i8"; } + if (k == TokenKind.TYPE_I16) { return "i16"; } + if (k == TokenKind.TYPE_I32) { return "i32"; } + if (k == TokenKind.TYPE_I64) { return "i64"; } + return "Unknown"; + } + + private parseSuffix(base: Expr) : Expr { + Expr e = base; + Boolean cont = true; + while (cont) { + Integer sl = this.peek().line; + Integer sc = this.peek().col; + Integer k = this.peek().kind; + + if (k == TokenKind.DOT) { + this.advance(); + String member = ""; + if (this.check(TokenKind.IDENT)) { + member = this.advance().strVal; + } else { + IO.error("parse error at ${sl}:${sc} - expected member name after '.'"); + this.hasError = true; + cont = false; + } + if (this.check(TokenKind.LPAREN)) { + List args = this.parseArgs(); + e = Expr.methodCall(e, member, args, sl, sc); + } else { + e = Expr.fieldAccess(e, member, sl, sc); + } + } + else if (k == TokenKind.QUESTION_DOT) { + this.advance(); + String member = this.eatIdent(); + List args = List(); + if (this.check(TokenKind.LPAREN)) { + args = this.parseArgs(); + } + e = Expr.nullSafe(e, member, args, sl, sc); + } + else if (k == TokenKind.LBRACKET) { + this.advance(); + Expr idx = this.parseExpr(0); + this.eat(TokenKind.RBRACKET); + e = Expr.index(e, idx, sl, sc); + } + else if (k == TokenKind.PLUS_PLUS) { + this.advance(); + e = Expr.unary(UnaryOp.POST_INC, e, sl, sc); + } + else if (k == TokenKind.MINUS_MINUS) { + this.advance(); + e = Expr.unary(UnaryOp.POST_DEC, e, sl, sc); + } + else if (k == TokenKind.AS) { + this.advance(); + AstType ty = this.parseType(); + e = Expr.cast(e, ty, sl, sc); + } + else { + cont = false; + } + } + return e; + } + + + private parseMatchPattern() : MatchPattern { + if (this.check(TokenKind.IDENT) && this.peek().strVal == "_") { + this.advance(); + return MatchPattern.wildcard(); + } + + if (this.check(TokenKind.STR)) { + String s = this.advance().strVal; + return MatchPattern.ofStrLit(s); + } + + if (this.check(TokenKind.INT)) { + Integer n = this.advance().intVal; + return MatchPattern.ofIntLit(n); + } + + if (this.check(TokenKind.IDENT)) { + String first = this.advance().strVal; + if (this.check(TokenKind.DOT)) { + this.advance(); + String variant = this.eatIdent(); + List bindings = List(); + if (this.check(TokenKind.LPAREN)) { + this.advance(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + bindings.append(this.eatIdent()); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + } + return MatchPattern.variant(first, variant, bindings); + } + return MatchPattern.binding(first); + } + + IO.error("parse error - unexpected token in match pattern"); + this.hasError = true; + return MatchPattern.wildcard(); + } + + + public parseBlock() : List { + this.eat(TokenKind.LBRACE); + List stmts = List(); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + Stmt s = this.parseStmt(); + if (s != null) { stmts.append(s); } + } + this.eat(TokenKind.RBRACE); + return stmts; + } + + private looksLikeVarDecl() : Boolean { + Integer k = this.peek().kind; + if (this.isSimpleTypeToken(k)) { return true; } + if (k == TokenKind.TYPE_LIST || k == TokenKind.TYPE_HASHMAP || + k == TokenKind.TYPE_MAP || k == TokenKind.TYPE_TREEMAP || + k == TokenKind.TYPE_PAIR || k == TokenKind.TYPE_RAWPTR || + k == TokenKind.TYPE_ARRAY || k == TokenKind.TYPE_SLICE || + k == TokenKind.TYPE_EXCEPTION) { return true; } + if (k == TokenKind.IDENT) { + Integer nk = this.peekAt(1).kind; + if (nk == TokenKind.IDENT) { return true; } + if (nk == TokenKind.QUESTION) { return this.peekAt(2).kind == TokenKind.IDENT; } + return false; + } + return false; + } + + public parseStmt() : Stmt { + Token t = this.peek(); + Integer sl = t.line; + Integer sc = t.col; + Integer k = t.kind; + + if (k == TokenKind.RETURN) { + this.advance(); + Expr e = Expr(1, 0, 0); + if (!this.check(TokenKind.SEMICOLON) && !this.check(TokenKind.RBRACE)) { + e = this.parseExpr(0); + } + this.skipSemicolon(); + return Stmt.return_(e, sl, sc); + } + + if (k == TokenKind.THROW) { + this.advance(); + Expr e = this.parseExpr(0); + this.skipSemicolon(); + return Stmt.throw_(e, sl, sc); + } + + if (k == TokenKind.BREAK) { + this.advance(); + this.skipSemicolon(); + return Stmt.break_(sl, sc); + } + + if (k == TokenKind.CONTINUE) { + this.advance(); + this.skipSemicolon(); + return Stmt.continue_(sl, sc); + } + + if (k == TokenKind.DEFER) { + this.advance(); + Expr e = this.parseExpr(0); + this.skipSemicolon(); + return Stmt.defer_(e, sl, sc); + } + + if (k == TokenKind.IF) { + this.advance(); + this.eat(TokenKind.LPAREN); + Expr cond = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + List then_ = this.parseBlock(); + List elseIfs = List(); + List else_ = List(); + while (this.check(TokenKind.ELSE)) { + this.advance(); + if (this.check(TokenKind.IF)) { + this.advance(); + this.eat(TokenKind.LPAREN); + Expr eic = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + List eib = this.parseBlock(); + elseIfs.append(ElseIfBranch(eic, eib)); + } else { + else_ = this.parseBlock(); + } + } + return Stmt.if_(cond, then_, elseIfs, else_, sl, sc); + } + + if (k == TokenKind.WHILE) { + this.advance(); + this.eat(TokenKind.LPAREN); + Expr cond = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + List body = this.parseBlock(); + return Stmt.while_(cond, body, sl, sc); + } + + if (k == TokenKind.FOR) { + this.advance(); + this.eat(TokenKind.LPAREN); + AstType ty = this.parseType(); + String name = this.eatIdent(); + if (this.check(TokenKind.COLON) || (this.check(TokenKind.IDENT) && this.peek().strVal == "in")) { + this.advance(); // consume : or 'in' + Expr iter = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + List body = this.parseBlock(); + return Stmt.forEach(ty, name, iter, body, sl, sc); + } + this.eat(TokenKind.EQ); + Expr initVal = this.parseExpr(0); + Stmt init = Stmt.varDecl(ty, name, initVal, sl, sc); + this.eat(TokenKind.SEMICOLON); + Expr cond = this.parseExpr(0); + this.eat(TokenKind.SEMICOLON); + Expr step = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + List body = this.parseBlock(); + return Stmt.for_(init, cond, step, body, sl, sc); + } + + if (k == TokenKind.SWITCH) { + this.advance(); + this.eat(TokenKind.LPAREN); + Expr expr = this.parseExpr(0); + this.eat(TokenKind.RPAREN); + this.eat(TokenKind.LBRACE); + List cases = List(); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + if (this.check(TokenKind.DEFAULT)) { + this.advance(); + this.eat(TokenKind.COLON); + List cbody = this.parseCaseBody(); + SwitchCase dsc = SwitchCase(true, cbody); + cases.append(dsc); + } else { + this.eat(TokenKind.CASE); + Expr pat = this.parseExpr(0); + this.eat(TokenKind.COLON); + List cbody = this.parseCaseBody(); + SwitchCase csc = SwitchCase(false, cbody); + csc.pattern = pat; + cases.append(csc); + } + } + this.eat(TokenKind.RBRACE); + return Stmt.switch_(expr, cases, sl, sc); + } + + if (k == TokenKind.TRY) { + this.advance(); + List tryBody = this.parseBlock(); + List catches = List(); + while (this.check(TokenKind.CATCH)) { + this.advance(); + this.eat(TokenKind.LPAREN); + List exTypes = List(); + exTypes.append(this.parseType()); + while (this.check(TokenKind.PIPE)) { + this.advance(); + exTypes.append(this.parseType()); + } + String exName = this.eatIdent(); + this.eat(TokenKind.RPAREN); + List cbody = this.parseBlock(); + catches.append(CatchClause(exTypes, exName, cbody)); + } + List finally_ = List(); + if (this.check(TokenKind.FINALLY)) { + this.advance(); + finally_ = this.parseBlock(); + } + return Stmt.try_(tryBody, catches, finally_, sl, sc); + } + + if (k == TokenKind.ASM) { + this.advance(); + this.eat(TokenKind.LBRACE); + String asmText = ""; + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + if (this.check(TokenKind.STR)) { + asmText = asmText.concat(this.advance().strVal).concat("\n"); + } else { + this.advance(); + } + } + this.eat(TokenKind.RBRACE); + return Stmt.asm_(asmText, sl, sc); + } + + if (k == TokenKind.VOLATILE) { + this.advance(); + AstType ty = this.parseType(); + String name2 = this.eatIdent(); + Expr initSentinel = Expr(1, 0, 0); + if (this.check(TokenKind.EQ)) { this.advance(); initSentinel = this.parseExpr(0); } + this.skipSemicolon(); + return Stmt.volatileVarDecl(ty, name2, initSentinel, sl, sc); + } + + if (this.looksLikeVarDecl()) { + AstType ty = this.parseType(); + String name3 = this.eatIdent(); + Expr initSentinel = Expr(1, 0, 0); + if (this.check(TokenKind.EQ)) { this.advance(); initSentinel = this.parseExpr(0); } + this.skipSemicolon(); + return Stmt.varDecl(ty, name3, initSentinel, sl, sc); + } + + Expr e = this.parseExpr(0); + this.skipSemicolon(); + return Stmt.exprStmt(e, sl, sc); + } + + private parseCaseBody() : List { + List stmts = List(); + while (!this.check(TokenKind.CASE) && + !this.check(TokenKind.DEFAULT) && + !this.check(TokenKind.RBRACE) && + !this.check(TokenKind.EOF)) { + Stmt s = this.parseStmt(); + if (s != null) { stmts.append(s); } + } + return stmts; + } + + + private parseAnnotations() : List { + List annots = List(); + while (this.check(TokenKind.AT)) { + this.advance(); + String name = this.eatIdent(); + if (this.check(TokenKind.LPAREN)) { + this.advance(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + this.advance(); + } + this.eat(TokenKind.RPAREN); + } + annots.append(name); + } + return annots; + } + + + private parseGenericBounds() : List { + List bounds = List(); + if (this.check(TokenKind.LT)) { + this.advance(); + while (!this.check(TokenKind.GT) && !this.check(TokenKind.EOF)) { + String name = this.eatIdent(); + GenericBound gb = GenericBound(name); + if (this.check(TokenKind.COLON)) { + this.advance(); + gb.bounds.append(this.eatIdent()); + while (this.check(TokenKind.PLUS)) { + this.advance(); + gb.bounds.append(this.eatIdent()); + } + } + bounds.append(gb); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.GT); + } + return bounds; + } + + + private parseParams() : List { + List params = List(); + this.eat(TokenKind.LPAREN); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + Boolean isVariadic = false; + if (this.check(TokenKind.ELLIPSIS)) { this.advance(); isVariadic = true; } + String name = this.eatIdent(); + this.eat(TokenKind.COLON); + AstType ty = this.parseType(); + Param p = Param(name, ty); + p.isVariadic = isVariadic; + if (this.check(TokenKind.EQ)) { + this.advance(); + p.default_ = this.parseExpr(0); + } + params.append(p); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + return params; + } + + + private parseMethod(vis: Integer, isStatic: Boolean, isAbstract: Boolean, + isDefault: Boolean, isOverride: Boolean, + isAsync: Boolean, isOperator: Boolean) : MethodDecl { + String name = ""; + if (isOperator) { + name = "operator_" + this.operatorSymbol(); + } else { + name = this.eatIdent(); + } + MethodDecl m = MethodDecl(vis, name); + m.isStatic = isStatic; + m.isAbstract = isAbstract; + m.isDefault = isDefault; + m.isOverride = isOverride; + m.isAsync = isAsync; + m.isOperator = isOperator; + m.params = this.parseParams(); + if (this.check(TokenKind.COLON)) { + this.advance(); + m.returnTy = this.parseType(); + } + if (this.check(TokenKind.LBRACE)) { + m.body = this.parseBlock(); + } else { + this.skipSemicolon(); + } + return m; + } + + private operatorSymbol() : String { + Integer k = this.peek().kind; + this.advance(); + if (k == TokenKind.PLUS) { return "add"; } + if (k == TokenKind.MINUS) { return "sub"; } + if (k == TokenKind.STAR) { return "mul"; } + if (k == TokenKind.SLASH) { return "div"; } + if (k == TokenKind.PERCENT) { return "mod"; } + if (k == TokenKind.EQ_EQ) { return "eq"; } + if (k == TokenKind.BANG_EQ) { return "ne"; } + if (k == TokenKind.LT) { return "lt"; } + if (k == TokenKind.GT) { return "gt"; } + if (k == TokenKind.LT_EQ) { return "le"; } + if (k == TokenKind.GT_EQ) { return "ge"; } + return "op"; + } + + + private parseField(vis: Integer, isStatic: Boolean, isReadonly: Boolean) : FieldDecl { + String name = this.eatIdent(); + this.eat(TokenKind.COLON); + AstType ty = this.parseType(); + FieldDecl f = FieldDecl(vis, name, ty); + f.isStatic = isStatic; + f.isReadonly = isReadonly; + if (this.check(TokenKind.EQ)) { + this.advance(); + f.init = this.parseExpr(0); + } + this.skipSemicolon(); + return f; + } + + + private parseClassBody(classDecl: ClassDecl) : Void { + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List annots = this.parseAnnotations(); + + Integer vis = Visibility.PRIVATE; + Boolean isStatic = false; + Boolean isAbstract = false; + Boolean isOverride = false; + Boolean isReadonly = false; + Boolean isAsync = false; + Boolean isOperator = false; + Boolean isDefault = false; + + if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } + else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } + else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } + else if (this.check(TokenKind.INTERNAL)) { this.advance(); vis = Visibility.INTERNAL; } + + Boolean contMod = true; + while (contMod) { + if (this.check(TokenKind.STATIC)) { this.advance(); isStatic = true; } + else if (this.check(TokenKind.ABSTRACT)) { this.advance(); isAbstract = true; } + else if (this.check(TokenKind.OVERRIDE)) { this.advance(); isOverride = true; } + else if (this.check(TokenKind.READONLY)) { this.advance(); isReadonly = true; } + else if (this.check(TokenKind.ASYNC)) { this.advance(); isAsync = true; } + else if (this.check(TokenKind.DEFAULT)) { this.advance(); isDefault = true; } + else if (this.check(TokenKind.OPERATOR)) { this.advance(); isOperator = true; } + else { contMod = false; } + } + + if (this.check(TokenKind.CONSTRUCTOR)) { + this.advance(); + List params = this.parseParams(); + List body = this.parseBlock(); + classDecl.ctor = ConstructorDecl(vis, params, body); + } + else if (isOperator || this.peek().kind == TokenKind.IDENT) { + if (!isOperator && this.peekAt(1).kind == TokenKind.COLON) { + classDecl.fields.append(this.parseField(vis, isStatic, isReadonly)); + } else { + classDecl.methods.append( + this.parseMethod(vis, isStatic, isAbstract, isDefault, isOverride, isAsync, isOperator) + ); + } + } + else { + this.advance(); + } + } + this.eat(TokenKind.RBRACE); + } + + + private parseClassDecl(vis: Integer, isAbstract: Boolean, isManual: Boolean, + isSealed: Boolean, isImmutable: Boolean) : Item { + String name = this.eatIdent(); + ClassDecl c = ClassDecl(vis, name); + c.isAbstract = isAbstract; + c.isManual = isManual; + c.isSealed = isSealed; + c.isImmutable = isImmutable; + c.generics = this.parseGenericBounds(); + if (this.check(TokenKind.EXTENDS)) { + this.advance(); + if (this.check(TokenKind.TYPE_EXCEPTION)) { this.advance(); c.extends_ = "Exception"; } + else { c.extends_ = this.eatIdent(); } + } + if (this.check(TokenKind.IMPLEMENTS)) { + this.advance(); + c.implements_.append(this.eatIdent()); + while (this.check(TokenKind.COMMA)) { + this.advance(); + c.implements_.append(this.eatIdent()); + } + } + this.parseClassBody(c); + return Item.fromClass(c); + } + + private parseInterfaceDecl(vis: Integer) : Item { + String name = this.eatIdent(); + InterfaceDecl iface = InterfaceDecl(vis, name); + iface.generics = this.parseGenericBounds(); + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List annots = this.parseAnnotations(); + Boolean isDefault = false; + if (this.check(TokenKind.DEFAULT)) { this.advance(); isDefault = true; } + MethodDecl m = this.parseMethod(Visibility.PUBLIC, false, !isDefault, isDefault, false, false, false); + iface.methods.append(m); + } + this.eat(TokenKind.RBRACE); + return Item.fromInterface(iface); + } + + private parseEnumDecl(vis: Integer) : Item { + String name = this.eatIdent(); + EnumDecl e = EnumDecl(vis, name); + this.eat(TokenKind.LBRACE); + Boolean inVariants = true; + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + Integer k = this.peek().kind; + if (k == TokenKind.PUBLIC || k == TokenKind.PRIVATE || + k == TokenKind.PROTECTED || k == TokenKind.INTERNAL || + k == TokenKind.STATIC || k == TokenKind.ABSTRACT) { + inVariants = false; + } + if (!inVariants) { + List an = this.parseAnnotations(); + Integer mvis = Visibility.PUBLIC; + if (this.check(TokenKind.PUBLIC)) { this.advance(); } + Boolean mStatic = false; + if (this.check(TokenKind.STATIC)) { this.advance(); mStatic = true; } + MethodDecl m = this.parseMethod(mvis, mStatic, false, false, false, false, false); + e.methods.append(m); + } else { + String varName = this.eatIdent(); + EnumVariant v = EnumVariant(varName); + if (this.check(TokenKind.LPAREN)) { + this.advance(); + while (!this.check(TokenKind.RPAREN) && !this.check(TokenKind.EOF)) { + v.data.append(this.parseType()); + if (this.check(TokenKind.COMMA)) { this.advance(); } + } + this.eat(TokenKind.RPAREN); + } + e.variants.append(v); + if (this.check(TokenKind.COMMA) || this.check(TokenKind.SEMICOLON)) { + this.advance(); + } + } + } + this.eat(TokenKind.RBRACE); + return Item.fromEnum(e); + } + + private parseStructDecl(vis: Integer, isPacked: Boolean) : Item { + String name = this.eatIdent(); + StructDecl s = StructDecl(vis, name); + s.isPacked = isPacked; + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List an = this.parseAnnotations(); + Integer mvis = Visibility.PUBLIC; + Boolean isSt = false; + Boolean isRo = false; + if (this.check(TokenKind.PUBLIC)) { this.advance(); mvis = Visibility.PUBLIC; } + else if (this.check(TokenKind.PRIVATE)) { this.advance(); mvis = Visibility.PRIVATE; } + if (this.check(TokenKind.STATIC)) { this.advance(); isSt = true; } + if (this.check(TokenKind.READONLY)) { this.advance(); isRo = true; } + if (this.check(TokenKind.OPERATOR)) { + this.advance(); + s.methods.append(this.parseMethod(mvis, false, false, false, false, false, true)); + } else if (this.peekAt(1).kind == TokenKind.COLON) { + s.fields.append(this.parseField(mvis, isSt, isRo)); + } else { + s.methods.append(this.parseMethod(mvis, isSt, false, false, false, false, false)); + } + } + this.eat(TokenKind.RBRACE); + return Item.fromStruct(s); + } + + private parseExceptionDecl(vis: Integer, isManual: Boolean) : Item { + String name = this.eatIdent(); + String ext = "Exception"; + if (this.check(TokenKind.EXTENDS)) { + this.advance(); + if (this.check(TokenKind.TYPE_EXCEPTION)) { this.advance(); ext = "Exception"; } + else { ext = this.eatIdent(); } + } + ExceptionDecl ex = ExceptionDecl(vis, name, ext); + ex.isManual = isManual; + this.parseExceptionBody(ex); + return Item.fromException(ex); + } + + private parseExceptionBody(ex: ExceptionDecl) : Void { + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List annots = this.parseAnnotations(); + Integer vis = Visibility.PRIVATE; + Boolean isStatic = false; + Boolean isOverride = false; + Boolean isReadonly = false; + Boolean isAsync = false; + if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } + else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } + else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } + Boolean contMod = true; + while (contMod) { + if (this.check(TokenKind.STATIC)) { this.advance(); isStatic = true; } + else if (this.check(TokenKind.OVERRIDE)) { this.advance(); isOverride = true; } + else if (this.check(TokenKind.READONLY)) { this.advance(); isReadonly = true; } + else if (this.check(TokenKind.ASYNC)) { this.advance(); isAsync = true; } + else { contMod = false; } + } + if (this.check(TokenKind.CONSTRUCTOR)) { + this.advance(); + List params = this.parseParams(); + List body = this.parseBlock(); + ex.ctor = ConstructorDecl(vis, params, body); + } else if (this.peekAt(1).kind == TokenKind.COLON) { + ex.fields.append(this.parseField(vis, isStatic, isReadonly)); + } else { + ex.methods.append( + this.parseMethod(vis, isStatic, false, false, isOverride, isAsync, false) + ); + } + } + this.eat(TokenKind.RBRACE); + } + + private parseExternDecl() : Item { + String abi = "C"; + if (this.check(TokenKind.STR)) { abi = this.advance().strVal; } + ExternDecl ext = ExternDecl(abi); + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + String fname = this.eatIdent(); + ExternFnDecl fn = ExternFnDecl(fname); + fn.params = this.parseParams(); + if (this.check(TokenKind.COLON)) { + this.advance(); + fn.returnTy = this.parseType(); + } + this.skipSemicolon(); + ext.decls.append(fn); + } + this.eat(TokenKind.RBRACE); + return Item.fromExtern(ext); + } + + private parseExtensionDecl() : Item { + String target = this.eatIdent(); + ExtensionDecl ex = ExtensionDecl(target); + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List an = this.parseAnnotations(); + Integer mvis = Visibility.PUBLIC; + Boolean mSt = false; + if (this.check(TokenKind.PUBLIC)) { this.advance(); } + if (this.check(TokenKind.STATIC)) { this.advance(); mSt = true; } + ex.methods.append(this.parseMethod(mvis, mSt, false, false, false, false, false)); + } + this.eat(TokenKind.RBRACE); + return Item.fromExtension(ex); + } + + private parseUnionDecl(vis: Integer) : Item { + String name = this.eatIdent(); + UnionDecl u = UnionDecl(vis, name); + this.eat(TokenKind.LBRACE); + while (!this.check(TokenKind.RBRACE) && !this.check(TokenKind.EOF)) { + List an = this.parseAnnotations(); + String fname = this.eatIdent(); + this.eat(TokenKind.COLON); + AstType ty = this.parseType(); + FieldDecl f = FieldDecl(Visibility.PUBLIC, fname, ty); + u.fields.append(f); + this.skipSemicolon(); + } + this.eat(TokenKind.RBRACE); + return Item.fromUnion(u); + } + + private parseTypeAliasDecl() : Item { + String name = this.eatIdent(); + this.eat(TokenKind.EQ); + AstType ty = this.parseType(); + this.skipSemicolon(); + return Item.fromAlias(TypeAliasDecl(name, ty)); + } + + public parseItem() : Item { + List annots = this.parseAnnotations(); + + Boolean isManual = false; + Boolean isSealed = false; + Boolean isImmutable = false; + Boolean isPacked = false; + + Integer ai = 0; + while (ai < annots.length()) { + String an = annots.get(ai); + if (an == "ManualMemory") { isManual = true; } + if (an == "Sealed") { isSealed = true; } + if (an == "Immutable") { isImmutable = true; } + if (an == "Packed") { isPacked = true; } + ai = ai + 1; + } + + Integer vis = Visibility.PUBLIC; + Boolean isAbstract = false; + + if (this.check(TokenKind.PUBLIC)) { this.advance(); vis = Visibility.PUBLIC; } + else if (this.check(TokenKind.PRIVATE)) { this.advance(); vis = Visibility.PRIVATE; } + else if (this.check(TokenKind.PROTECTED)) { this.advance(); vis = Visibility.PROTECTED; } + else if (this.check(TokenKind.INTERNAL)) { this.advance(); vis = Visibility.INTERNAL; } + + if (this.check(TokenKind.ABSTRACT)) { this.advance(); isAbstract = true; } + + if (this.check(TokenKind.CLASS)) { + this.advance(); + return this.parseClassDecl(vis, isAbstract, isManual, isSealed, isImmutable); + } + if (this.check(TokenKind.INTERFACE)) { + this.advance(); + return this.parseInterfaceDecl(vis); + } + if (this.check(TokenKind.ENUM)) { + this.advance(); + return this.parseEnumDecl(vis); + } + if (this.check(TokenKind.STRUCT)) { + this.advance(); + return this.parseStructDecl(vis, isPacked); + } + if (this.check(TokenKind.IDENT) && this.peek().strVal == "exception") { + this.advance(); + return this.parseExceptionDecl(vis, isManual); + } + if (this.check(TokenKind.EXTERN)) { + this.advance(); + return this.parseExternDecl(); + } + if (this.check(TokenKind.EXTEND)) { + this.advance(); + return this.parseExtensionDecl(); + } + if (this.check(TokenKind.UNION)) { + this.advance(); + return this.parseUnionDecl(vis); + } + if (this.check(TokenKind.KW_TYPE)) { + this.advance(); + return this.parseTypeAliasDecl(); + } + + if (this.check(TokenKind.CONST)) { + this.advance(); + AstType cty = this.parseType(); + String cnam = this.eatIdent(); + this.eat(TokenKind.EQ); + Expr cval = this.parseExpr(0); + this.skipSemicolon(); + return Item.fromConst(cnam, cty, cval); + } + + Token t = this.peek(); + IO.error("parse error at ${t.line}:${t.col} - unexpected token at top level: ${t.kind}"); + this.hasError = true; + this.advance(); + return Item.fromClass(ClassDecl(Visibility.PUBLIC, "Error")); + } + + + public parse() : ArimoModule { + ArimoModule mod = ArimoModule(""); + if (this.check(TokenKind.PACKAGE)) { + this.advance(); + String path = this.parseDottedName(); + mod.path = path; + this.skipSemicolon(); + } + + while (this.check(TokenKind.IMPORT)) { + this.advance(); + String imp = this.parseDottedName(); + mod.imports.append(imp); + this.skipSemicolon(); + } + + while (!this.check(TokenKind.EOF)) { + Item item = this.parseItem(); + mod.items.append(item); + } + return mod; + } + + private parseDottedName() : String { + String name = this.eatIdent(); + while (this.check(TokenKind.DOT)) { + this.advance(); + if (this.check(TokenKind.STAR)) { + this.advance(); + name = name.concat(".*"); + } else { + name = name.concat(".").concat(this.eatIdent()); + } + } + return name; + } +} diff --git a/arimo/compiler/typechecker/ScopeFrame.arm b/arimo/compiler/typechecker/ScopeFrame.arm index db163bc..914f1b2 100644 --- a/arimo/compiler/typechecker/ScopeFrame.arm +++ b/arimo/compiler/typechecker/ScopeFrame.arm @@ -1,45 +1,45 @@ -/* -Arimo Lang Compiler - A modern programming language and compiler -Copyright (C) 2026 Egecan Akıncıoğlu - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package arimo.compiler.typechecker; - -import arimo.compiler.ast.types.AstType; - -public class ScopeFrame { - public names : List; - public types : List; - - public constructor() { - this.names = List(); - this.types = List(); - } - - public define(name: String, ty: AstType) : Void { - this.names.append(name); - this.types.append(ty); - } - - public lookup(name: String) : AstType? { - Integer i = this.names.length() - 1; - while (i >= 0) { - if (this.names.get(i) == name) { return this.types.get(i); } - i--; - } - return null; - } -} +/* +Arimo Lang Compiler - A modern programming language and compiler +Copyright (C) 2026 Egecan Akıncıoğlu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package arimo.compiler.typechecker; + +import arimo.compiler.ast.types.AstType; + +public class ScopeFrame { + public names : List; + public types : List; + + public constructor() { + this.names = List(); + this.types = List(); + } + + public define(name: String, ty: AstType) : Void { + this.names.append(name); + this.types.append(ty); + } + + public lookup(name: String) : AstType? { + Integer i = this.names.length() - 1; + while (i >= 0) { + if (this.names.get(i) == name) { return this.types.get(i); } + i = i - 1; + } + return null; + } +} diff --git a/arimo/compiler/typechecker/TypeChecker.arm b/arimo/compiler/typechecker/TypeChecker.arm index af8e5b4..790e672 100644 --- a/arimo/compiler/typechecker/TypeChecker.arm +++ b/arimo/compiler/typechecker/TypeChecker.arm @@ -1,599 +1,599 @@ -/* -Arimo Lang Compiler - A modern programming language and compiler -Copyright (C) 2026 Egecan Akıncıoğlu - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package arimo.compiler.typechecker; - -import arimo.compiler.typechecker.ScopeFrame; -import arimo.compiler.ast.ArimoModule; -import arimo.compiler.ast.types.AstType; -import arimo.compiler.ast.stmt.StmtKind; -import arimo.compiler.ast.stmt.Stmt; -import arimo.compiler.ast.stmt.CatchClause; -import arimo.compiler.ast.stmt.ElseIfBranch; -import arimo.compiler.ast.stmt.SwitchCase; -import arimo.compiler.ast.decl.Param; -import arimo.compiler.ast.decl.FieldDecl; -import arimo.compiler.ast.decl.MethodDecl; -import arimo.compiler.ast.decl.ConstructorDecl; -import arimo.compiler.ast.decl.ExternFnDecl; -import arimo.compiler.ast.decl.EnumVariant; -import arimo.compiler.ast.item.ItemKind; -import arimo.compiler.ast.item.Item; -import arimo.compiler.ast.item.ClassDecl; -import arimo.compiler.ast.item.EnumDecl; -import arimo.compiler.ast.item.StructDecl; -import arimo.compiler.ast.item.ExceptionDecl; -import arimo.compiler.ast.item.InterfaceDecl; -import arimo.compiler.ast.item.ExtensionDecl; -import arimo.compiler.ast.item.ExternDecl; -import arimo.compiler.ast.item.UnionDecl; - -public class TypeChecker { - public classNames : List; - public classItems : List; - private enumNames : List; - private enumItems : List; - private structNames : List; - private structItems : List; - private exNames : List; - private exItems : List; - private ifaceNames : List; - private ifaceItems : List; - private extNames : List; - private extItems : List; - private externFnNames : List; - private externFns : List; - private aliasNames : List; - private aliasTypes : List; - private unionNames : List; - private unionItems : List; - public constNames : List; - public constTypes : List; - - private scopeStack : List; - private currentClassName : String; - private currentRetTy : AstType; - - public errors : List; - public warnings : List; - - public constructor() { - this.classNames = List(); - this.classItems = List(); - this.enumNames = List(); - this.enumItems = List(); - this.structNames = List(); - this.structItems = List(); - this.exNames = List(); - this.exItems = List(); - this.ifaceNames = List(); - this.ifaceItems = List(); - this.extNames = List(); - this.extItems = List(); - this.externFnNames = List(); - this.externFns = List(); - this.aliasNames = List(); - this.aliasTypes = List(); - this.unionNames = List(); - this.unionItems = List(); - this.constNames = List(); - this.constTypes = List(); - this.scopeStack = List(); - this.currentClassName = ""; - this.errors = List(); - this.warnings = List(); - } - - public check(module: ArimoModule) : Void { - this.collectDecls(module); - this.checkItems(module); - } - - public registerModule(module: ArimoModule) : Void { - this.collectDecls(module); - } - - private collectDecls(module: ArimoModule) : Void { - Integer i = 0; - while (i < module.items.length()) { - Item item = module.items.get(i); - if (item.kind == ItemKind.CLASS) { - this.classNames.append(item.classDecl.name); - this.classItems.append(item.classDecl); - } else if (item.kind == ItemKind.ENUM) { - this.enumNames.append(item.enumDecl.name); - this.enumItems.append(item.enumDecl); - } else if (item.kind == ItemKind.STRUCT) { - this.structNames.append(item.structDecl.name); - this.structItems.append(item.structDecl); - } else if (item.kind == ItemKind.EXCEPTION) { - this.exNames.append(item.exDecl.name); - this.exItems.append(item.exDecl); - } else if (item.kind == ItemKind.INTERFACE) { - this.ifaceNames.append(item.ifaceDecl.name); - this.ifaceItems.append(item.ifaceDecl); - } else if (item.kind == ItemKind.EXTERN) { - ExternDecl ext = item.externDecl; - Integer j = 0; - while (j < ext.decls.length()) { - ExternFnDecl fn = ext.decls.get(j); - this.externFnNames.append(fn.name); - this.externFns.append(fn); - j++; - } - } else if (item.kind == ItemKind.ALIAS) { - this.aliasNames.append(item.aliasDecl.name); - this.aliasTypes.append(item.aliasDecl.ty); - } else if (item.kind == ItemKind.EXTENSION) { - this.extNames.append(item.extDecl.target); - this.extItems.append(item.extDecl); - } else if (item.kind == ItemKind.UNION) { - this.unionNames.append(item.unionDecl.name); - this.unionItems.append(item.unionDecl); - } else if (item.kind == ItemKind.CONST) { - this.constNames.append(item.constName); - this.constTypes.append(item.constType); - } - i++; - } - } - - public checkItems(module: ArimoModule) : Void { - Integer i = 0; - while (i < module.items.length()) { - Item item = module.items.get(i); - if (item.kind == ItemKind.CLASS) { - this.checkClass(item.classDecl); - } else if (item.kind == ItemKind.EXCEPTION) { - this.checkException(item.exDecl); - } else if (item.kind == ItemKind.STRUCT) { - this.checkStruct(item.structDecl); - } else if (item.kind == ItemKind.EXTENSION) { - this.checkExtension(item.extDecl); - } - i++; - } - } - - private checkClass(c: ClassDecl) : Void { - this.currentClassName = c.name; - if (c.ctor != null) { this.checkConstructor(c.ctor); } - Integer i = 0; - while (i < c.methods.length()) { - this.checkMethod(c.methods.get(i)); - i++; - } - } - - private checkException(e: ExceptionDecl) : Void { - this.currentClassName = e.name; - if (e.ctor != null) { this.checkConstructor(e.ctor); } - Integer i = 0; - while (i < e.methods.length()) { - this.checkMethod(e.methods.get(i)); - i++; - } - } - - private checkStruct(s: StructDecl) : Void { - this.currentClassName = s.name; - Integer i = 0; - while (i < s.methods.length()) { - this.checkMethod(s.methods.get(i)); - i++; - } - } - - private checkExtension(e: ExtensionDecl) : Void { - this.currentClassName = e.target; - Integer i = 0; - while (i < e.methods.length()) { - this.checkMethod(e.methods.get(i)); - i++; - } - } - - private checkConstructor(ctor: ConstructorDecl) : Void { - this.pushScope(); - Integer i = 0; - while (i < ctor.params.length()) { - Param p = ctor.params.get(i); - this.define(p.name, p.ty); - i++; - } - this.checkStmts(ctor.body); - this.popScope(); - } - - private checkMethod(m: MethodDecl) : Void { - if (m.body == null) { return; } - this.currentRetTy = m.returnTy; - this.pushScope(); - Integer i = 0; - while (i < m.params.length()) { - Param p = m.params.get(i); - this.define(p.name, p.ty); - i++; - } - this.checkStmts(m.body); - this.popScope(); - } - - private checkStmts(stmts: List) : Void { - if (stmts == null) { return; } - Integer i = 0; - while (i < stmts.length()) { - this.checkStmt(stmts.get(i)); - i++; - } - } - - private checkStmt(s: Stmt) : Void { - if (s == null) { return; } - Integer k = s.kind; - - if (k == StmtKind.VAR_DECL) { - this.define(s.name, s.ty); - - } else if (k == StmtKind.IF) { - this.pushScope(); - this.checkStmts(s.thenBody); - this.popScope(); - if (s.elseIfs.length() > 0) { - Integer i = 0; - while (i < s.elseIfs.length()) { - this.pushScope(); - this.checkStmts(s.elseIfs.get(i).body); - this.popScope(); - i++; - } - } - if (s.elseBody.length() > 0) { - this.pushScope(); - this.checkStmts(s.elseBody); - this.popScope(); - } - - } else if (k == StmtKind.WHILE) { - this.pushScope(); - this.checkStmts(s.body); - this.popScope(); - - } else if (k == StmtKind.FOR_EACH) { - this.pushScope(); - this.define(s.iterName, s.iterTy); - this.checkStmts(s.body); - this.popScope(); - - } else if (k == StmtKind.FOR) { - this.pushScope(); - if (s.forInit != null) { this.checkStmt(s.forInit); } - this.checkStmts(s.forBody); - this.popScope(); - - } else if (k == StmtKind.SWITCH) { - if (s.cases.length() > 0) { - Integer i = 0; - while (i < s.cases.length()) { - this.pushScope(); - this.checkStmts(s.cases.get(i).body); - this.popScope(); - i++; - } - } - - } else if (k == StmtKind.TRY) { - this.pushScope(); - this.checkStmts(s.tryBody); - this.popScope(); - if (s.catches.length() > 0) { - Integer i = 0; - while (i < s.catches.length()) { - CatchClause cc = s.catches.get(i); - this.pushScope(); - this.define(cc.name, cc.exType); - this.checkStmts(cc.body); - this.popScope(); - i++; - } - } - if (s.finallyBody.length() > 0) { - this.pushScope(); - this.checkStmts(s.finallyBody); - this.popScope(); - } - - } else if (k == StmtKind.BLOCK) { - this.pushScope(); - this.checkStmts(s.body); - this.popScope(); - } - } - - private pushScope() : Void { - this.scopeStack.append(ScopeFrame()); - } - - private popScope() : Void { - Integer last = this.scopeStack.length() - 1; - if (last >= 0) { this.scopeStack.removeAt(last); } - } - - private define(name: String, ty: AstType) : Void { - Integer last = this.scopeStack.length() - 1; - if (last >= 0) { - ScopeFrame frame = this.scopeStack.get(last) as ScopeFrame; - frame.define(name, ty); - } - } - - public lookupVar(name: String) : AstType? { - Integer i = this.scopeStack.length() - 1; - while (i >= 0) { - ScopeFrame frame = this.scopeStack.get(i) as ScopeFrame; - AstType? ty = frame.lookup(name); - if (ty != null) { return ty; } - i--; - } - return null; - } - - public findClass(name: String) : ClassDecl? { - Integer i = 0; - while (i < this.classNames.length()) { - if (this.classNames.get(i) == name) { return this.classItems.get(i); } - i++; - } - return null; - } - - public findEnum(name: String) : EnumDecl? { - Integer i = 0; - while (i < this.enumNames.length()) { - if (this.enumNames.get(i) == name) { return this.enumItems.get(i); } - i++; - } - return null; - } - - public findStruct(name: String) : StructDecl? { - Integer i = 0; - while (i < this.structNames.length()) { - if (this.structNames.get(i) == name) { return this.structItems.get(i); } - i++; - } - return null; - } - - public findException(name: String) : ExceptionDecl? { - Integer i = 0; - while (i < this.exNames.length()) { - if (this.exNames.get(i) == name) { return this.exItems.get(i); } - i++; - } - return null; - } - - public findInterface(name: String) : InterfaceDecl? { - Integer i = 0; - while (i < this.ifaceNames.length()) { - if (this.ifaceNames.get(i) == name) { return this.ifaceItems.get(i); } - i++; - } - return null; - } - - public findExternFn(name: String) : ExternFnDecl? { - Integer i = 0; - while (i < this.externFnNames.length()) { - if (this.externFnNames.get(i) == name) { return this.externFns.get(i); } - i++; - } - return null; - } - - public findExtension(target: String) : ExtensionDecl? { - Integer i = 0; - while (i < this.extNames.length()) { - if (this.extNames.get(i) == target) { return this.extItems.get(i); } - i++; - } - return null; - } - - public findUnion(name: String) : UnionDecl? { - Integer i = 0; - while (i < this.unionNames.length()) { - if (this.unionNames.get(i) == name) { return this.unionItems.get(i); } - i++; - } - return null; - } - - public resolveAlias(name: String) : AstType? { - Integer i = 0; - while (i < this.aliasNames.length()) { - if (this.aliasNames.get(i) == name) { return this.aliasTypes.get(i); } - i++; - } - return null; - } - - public isKnownType(name: String) : Boolean { - if (this.findClass(name) != null) { return true; } - if (this.findEnum(name) != null) { return true; } - if (this.findStruct(name) != null) { return true; } - if (this.findException(name) != null) { return true; } - if (this.findInterface(name) != null) { return true; } - if (this.findUnion(name) != null) { return true; } - if (this.resolveAlias(name) != null) { return true; } - return false; - } - - public allFieldsOf(name: String) : List { - List result = List(); - ClassDecl? c = this.findClass(name); - if (c != null) { - ClassDecl cn = c as ClassDecl; - if (cn.extends_ != "") { - List pf = this.allFieldsOf(cn.extends_); - Integer i = 0; - while (i < pf.length()) { - FieldDecl pfd = pf.get(i) as FieldDecl; - result.append(pfd); - i++; - } - } - Integer i = 0; - while (i < cn.fields.length()) { - FieldDecl cfd = cn.fields.get(i) as FieldDecl; - result.append(cfd); - i++; - } - } else { - ExceptionDecl? ex = this.findException(name); - if (ex != null) { - ExceptionDecl exn = ex as ExceptionDecl; - if (exn.extends_ != "" && exn.extends_ != "Exception") { - List pf = this.allFieldsOf(exn.extends_); - Integer i = 0; - while (i < pf.length()) { - FieldDecl pfd = pf.get(i) as FieldDecl; - result.append(pfd); - i++; - } - } - Integer i = 0; - while (i < exn.fields.length()) { - FieldDecl exfd = exn.fields.get(i) as FieldDecl; - result.append(exfd); - i++; - } - } else { - StructDecl? s = this.findStruct(name); - if (s != null) { - StructDecl sn = s as StructDecl; - Integer i = 0; - while (i < sn.fields.length()) { - FieldDecl sfd = sn.fields.get(i) as FieldDecl; - result.append(sfd); - i++; - } - } else { - UnionDecl? u = this.findUnion(name); - if (u != null) { - UnionDecl un = u as UnionDecl; - Integer i = 0; - while (i < un.fields.length()) { - FieldDecl ufd = un.fields.get(i) as FieldDecl; - result.append(ufd); - i++; - } - } - } - } - } - return result; - } - - public fieldIndexOf(name: String, fieldName: String) : Integer { - List fields = this.allFieldsOf(name); - Integer i = 0; - while (i < fields.length()) { - FieldDecl fd = fields.get(i) as FieldDecl; - if (fd.name == fieldName) { return i; } - i++; - } - return -1; - } - - public findMethodOn(className: String, methodName: String) : MethodDecl? { - ClassDecl? c = this.findClass(className); - if (c != null) { - ClassDecl cn = c as ClassDecl; - Integer i = 0; - while (i < cn.methods.length()) { - MethodDecl md = cn.methods.get(i) as MethodDecl; - if (md.name == methodName) { return md; } - i++; - } - if (cn.extends_ != "") { return this.findMethodOn(cn.extends_, methodName); } - } - ExceptionDecl? ex = this.findException(className); - if (ex != null) { - ExceptionDecl exn = ex as ExceptionDecl; - Integer i = 0; - while (i < exn.methods.length()) { - MethodDecl md = exn.methods.get(i) as MethodDecl; - if (md.name == methodName) { return md; } - i++; - } - } - StructDecl? s = this.findStruct(className); - if (s != null) { - StructDecl sn = s as StructDecl; - Integer i = 0; - while (i < sn.methods.length()) { - MethodDecl md = sn.methods.get(i) as MethodDecl; - if (md.name == methodName) { return md; } - i++; - } - } - ExtensionDecl? ext = this.findExtension(className); - if (ext != null) { - ExtensionDecl extn = ext as ExtensionDecl; - Integer i = 0; - while (i < extn.methods.length()) { - MethodDecl md = extn.methods.get(i) as MethodDecl; - if (md.name == methodName) { return md; } - i++; - } - } - return null; - } - - public findConstructor(name: String) : ConstructorDecl? { - ClassDecl? c = this.findClass(name); - if (c != null) { if (c.ctor != null) { return c.ctor; } } - ExceptionDecl? ex = this.findException(name); - if (ex != null) { if (ex.ctor != null) { return ex.ctor; } } - return null; - } - - public enumVariantIndex(enumName: String, variantName: String) : Integer { - EnumDecl? e = this.findEnum(enumName); - if (e != null) { - EnumDecl en = e as EnumDecl; - Integer ei = 0; - while (ei < en.variants.length()) { - EnumVariant ev = en.variants.get(ei) as EnumVariant; - if (ev.name == variantName) { return ei; } - ei++; - } - } - return -1; - } - - public currentReturnType() : AstType? { - return this.currentRetTy; - } - - public currentClass() : String { - return this.currentClassName; - } -} +/* +Arimo Lang Compiler - A modern programming language and compiler +Copyright (C) 2026 Egecan Akıncıoğlu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package arimo.compiler.typechecker; + +import arimo.compiler.typechecker.ScopeFrame; +import arimo.compiler.ast.ArimoModule; +import arimo.compiler.ast.types.AstType; +import arimo.compiler.ast.stmt.StmtKind; +import arimo.compiler.ast.stmt.Stmt; +import arimo.compiler.ast.stmt.CatchClause; +import arimo.compiler.ast.stmt.ElseIfBranch; +import arimo.compiler.ast.stmt.SwitchCase; +import arimo.compiler.ast.decl.Param; +import arimo.compiler.ast.decl.FieldDecl; +import arimo.compiler.ast.decl.MethodDecl; +import arimo.compiler.ast.decl.ConstructorDecl; +import arimo.compiler.ast.decl.ExternFnDecl; +import arimo.compiler.ast.decl.EnumVariant; +import arimo.compiler.ast.item.ItemKind; +import arimo.compiler.ast.item.Item; +import arimo.compiler.ast.item.ClassDecl; +import arimo.compiler.ast.item.EnumDecl; +import arimo.compiler.ast.item.StructDecl; +import arimo.compiler.ast.item.ExceptionDecl; +import arimo.compiler.ast.item.InterfaceDecl; +import arimo.compiler.ast.item.ExtensionDecl; +import arimo.compiler.ast.item.ExternDecl; +import arimo.compiler.ast.item.UnionDecl; + +public class TypeChecker { + public classNames : List; + public classItems : List; + private enumNames : List; + private enumItems : List; + private structNames : List; + private structItems : List; + private exNames : List; + private exItems : List; + private ifaceNames : List; + private ifaceItems : List; + private extNames : List; + private extItems : List; + private externFnNames : List; + private externFns : List; + private aliasNames : List; + private aliasTypes : List; + private unionNames : List; + private unionItems : List; + public constNames : List; + public constTypes : List; + + private scopeStack : List; + private currentClassName : String; + private currentRetTy : AstType; + + public errors : List; + public warnings : List; + + public constructor() { + this.classNames = List(); + this.classItems = List(); + this.enumNames = List(); + this.enumItems = List(); + this.structNames = List(); + this.structItems = List(); + this.exNames = List(); + this.exItems = List(); + this.ifaceNames = List(); + this.ifaceItems = List(); + this.extNames = List(); + this.extItems = List(); + this.externFnNames = List(); + this.externFns = List(); + this.aliasNames = List(); + this.aliasTypes = List(); + this.unionNames = List(); + this.unionItems = List(); + this.constNames = List(); + this.constTypes = List(); + this.scopeStack = List(); + this.currentClassName = ""; + this.errors = List(); + this.warnings = List(); + } + + public check(module: ArimoModule) : Void { + this.collectDecls(module); + this.checkItems(module); + } + + public registerModule(module: ArimoModule) : Void { + this.collectDecls(module); + } + + private collectDecls(module: ArimoModule) : Void { + Integer i = 0; + while (i < module.items.length()) { + Item item = module.items.get(i); + if (item.kind == ItemKind.CLASS) { + this.classNames.append(item.classDecl.name); + this.classItems.append(item.classDecl); + } else if (item.kind == ItemKind.ENUM) { + this.enumNames.append(item.enumDecl.name); + this.enumItems.append(item.enumDecl); + } else if (item.kind == ItemKind.STRUCT) { + this.structNames.append(item.structDecl.name); + this.structItems.append(item.structDecl); + } else if (item.kind == ItemKind.EXCEPTION) { + this.exNames.append(item.exDecl.name); + this.exItems.append(item.exDecl); + } else if (item.kind == ItemKind.INTERFACE) { + this.ifaceNames.append(item.ifaceDecl.name); + this.ifaceItems.append(item.ifaceDecl); + } else if (item.kind == ItemKind.EXTERN) { + ExternDecl ext = item.externDecl; + Integer j = 0; + while (j < ext.decls.length()) { + ExternFnDecl fn = ext.decls.get(j); + this.externFnNames.append(fn.name); + this.externFns.append(fn); + j = j + 1; + } + } else if (item.kind == ItemKind.ALIAS) { + this.aliasNames.append(item.aliasDecl.name); + this.aliasTypes.append(item.aliasDecl.ty); + } else if (item.kind == ItemKind.EXTENSION) { + this.extNames.append(item.extDecl.target); + this.extItems.append(item.extDecl); + } else if (item.kind == ItemKind.UNION) { + this.unionNames.append(item.unionDecl.name); + this.unionItems.append(item.unionDecl); + } else if (item.kind == ItemKind.CONST) { + this.constNames.append(item.constName); + this.constTypes.append(item.constType); + } + i = i + 1; + } + } + + public checkItems(module: ArimoModule) : Void { + Integer i = 0; + while (i < module.items.length()) { + Item item = module.items.get(i); + if (item.kind == ItemKind.CLASS) { + this.checkClass(item.classDecl); + } else if (item.kind == ItemKind.EXCEPTION) { + this.checkException(item.exDecl); + } else if (item.kind == ItemKind.STRUCT) { + this.checkStruct(item.structDecl); + } else if (item.kind == ItemKind.EXTENSION) { + this.checkExtension(item.extDecl); + } + i = i + 1; + } + } + + private checkClass(c: ClassDecl) : Void { + this.currentClassName = c.name; + if (c.ctor != null) { this.checkConstructor(c.ctor); } + Integer i = 0; + while (i < c.methods.length()) { + this.checkMethod(c.methods.get(i)); + i = i + 1; + } + } + + private checkException(e: ExceptionDecl) : Void { + this.currentClassName = e.name; + if (e.ctor != null) { this.checkConstructor(e.ctor); } + Integer i = 0; + while (i < e.methods.length()) { + this.checkMethod(e.methods.get(i)); + i = i + 1; + } + } + + private checkStruct(s: StructDecl) : Void { + this.currentClassName = s.name; + Integer i = 0; + while (i < s.methods.length()) { + this.checkMethod(s.methods.get(i)); + i = i + 1; + } + } + + private checkExtension(e: ExtensionDecl) : Void { + this.currentClassName = e.target; + Integer i = 0; + while (i < e.methods.length()) { + this.checkMethod(e.methods.get(i)); + i = i + 1; + } + } + + private checkConstructor(ctor: ConstructorDecl) : Void { + this.pushScope(); + Integer i = 0; + while (i < ctor.params.length()) { + Param p = ctor.params.get(i); + this.define(p.name, p.ty); + i = i + 1; + } + this.checkStmts(ctor.body); + this.popScope(); + } + + private checkMethod(m: MethodDecl) : Void { + if (m.body == null) { return; } + this.currentRetTy = m.returnTy; + this.pushScope(); + Integer i = 0; + while (i < m.params.length()) { + Param p = m.params.get(i); + this.define(p.name, p.ty); + i = i + 1; + } + this.checkStmts(m.body); + this.popScope(); + } + + private checkStmts(stmts: List) : Void { + if (stmts == null) { return; } + Integer i = 0; + while (i < stmts.length()) { + this.checkStmt(stmts.get(i)); + i = i + 1; + } + } + + private checkStmt(s: Stmt) : Void { + if (s == null) { return; } + Integer k = s.kind; + + if (k == StmtKind.VAR_DECL) { + this.define(s.name, s.ty); + + } else if (k == StmtKind.IF) { + this.pushScope(); + this.checkStmts(s.thenBody); + this.popScope(); + if (s.elseIfs.length() > 0) { + Integer i = 0; + while (i < s.elseIfs.length()) { + this.pushScope(); + this.checkStmts(s.elseIfs.get(i).body); + this.popScope(); + i = i + 1; + } + } + if (s.elseBody.length() > 0) { + this.pushScope(); + this.checkStmts(s.elseBody); + this.popScope(); + } + + } else if (k == StmtKind.WHILE) { + this.pushScope(); + this.checkStmts(s.body); + this.popScope(); + + } else if (k == StmtKind.FOR_EACH) { + this.pushScope(); + this.define(s.iterName, s.iterTy); + this.checkStmts(s.body); + this.popScope(); + + } else if (k == StmtKind.FOR) { + this.pushScope(); + if (s.forInit != null) { this.checkStmt(s.forInit); } + this.checkStmts(s.forBody); + this.popScope(); + + } else if (k == StmtKind.SWITCH) { + if (s.cases.length() > 0) { + Integer i = 0; + while (i < s.cases.length()) { + this.pushScope(); + this.checkStmts(s.cases.get(i).body); + this.popScope(); + i = i + 1; + } + } + + } else if (k == StmtKind.TRY) { + this.pushScope(); + this.checkStmts(s.tryBody); + this.popScope(); + if (s.catches.length() > 0) { + Integer i = 0; + while (i < s.catches.length()) { + CatchClause cc = s.catches.get(i); + this.pushScope(); + this.define(cc.name, cc.exType); + this.checkStmts(cc.body); + this.popScope(); + i = i + 1; + } + } + if (s.finallyBody.length() > 0) { + this.pushScope(); + this.checkStmts(s.finallyBody); + this.popScope(); + } + + } else if (k == StmtKind.BLOCK) { + this.pushScope(); + this.checkStmts(s.body); + this.popScope(); + } + } + + private pushScope() : Void { + this.scopeStack.append(ScopeFrame()); + } + + private popScope() : Void { + Integer last = this.scopeStack.length() - 1; + if (last >= 0) { this.scopeStack.removeAt(last); } + } + + private define(name: String, ty: AstType) : Void { + Integer last = this.scopeStack.length() - 1; + if (last >= 0) { + ScopeFrame frame = this.scopeStack.get(last) as ScopeFrame; + frame.define(name, ty); + } + } + + public lookupVar(name: String) : AstType? { + Integer i = this.scopeStack.length() - 1; + while (i >= 0) { + ScopeFrame frame = this.scopeStack.get(i) as ScopeFrame; + AstType? ty = frame.lookup(name); + if (ty != null) { return ty; } + i = i - 1; + } + return null; + } + + public findClass(name: String) : ClassDecl? { + Integer i = 0; + while (i < this.classNames.length()) { + if (this.classNames.get(i) == name) { return this.classItems.get(i); } + i = i + 1; + } + return null; + } + + public findEnum(name: String) : EnumDecl? { + Integer i = 0; + while (i < this.enumNames.length()) { + if (this.enumNames.get(i) == name) { return this.enumItems.get(i); } + i = i + 1; + } + return null; + } + + public findStruct(name: String) : StructDecl? { + Integer i = 0; + while (i < this.structNames.length()) { + if (this.structNames.get(i) == name) { return this.structItems.get(i); } + i = i + 1; + } + return null; + } + + public findException(name: String) : ExceptionDecl? { + Integer i = 0; + while (i < this.exNames.length()) { + if (this.exNames.get(i) == name) { return this.exItems.get(i); } + i = i + 1; + } + return null; + } + + public findInterface(name: String) : InterfaceDecl? { + Integer i = 0; + while (i < this.ifaceNames.length()) { + if (this.ifaceNames.get(i) == name) { return this.ifaceItems.get(i); } + i = i + 1; + } + return null; + } + + public findExternFn(name: String) : ExternFnDecl? { + Integer i = 0; + while (i < this.externFnNames.length()) { + if (this.externFnNames.get(i) == name) { return this.externFns.get(i); } + i = i + 1; + } + return null; + } + + public findExtension(target: String) : ExtensionDecl? { + Integer i = 0; + while (i < this.extNames.length()) { + if (this.extNames.get(i) == target) { return this.extItems.get(i); } + i = i + 1; + } + return null; + } + + public findUnion(name: String) : UnionDecl? { + Integer i = 0; + while (i < this.unionNames.length()) { + if (this.unionNames.get(i) == name) { return this.unionItems.get(i); } + i = i + 1; + } + return null; + } + + public resolveAlias(name: String) : AstType? { + Integer i = 0; + while (i < this.aliasNames.length()) { + if (this.aliasNames.get(i) == name) { return this.aliasTypes.get(i); } + i = i + 1; + } + return null; + } + + public isKnownType(name: String) : Boolean { + if (this.findClass(name) != null) { return true; } + if (this.findEnum(name) != null) { return true; } + if (this.findStruct(name) != null) { return true; } + if (this.findException(name) != null) { return true; } + if (this.findInterface(name) != null) { return true; } + if (this.findUnion(name) != null) { return true; } + if (this.resolveAlias(name) != null) { return true; } + return false; + } + + public allFieldsOf(name: String) : List { + List result = List(); + ClassDecl? c = this.findClass(name); + if (c != null) { + ClassDecl cn = c as ClassDecl; + if (cn.extends_ != "") { + List pf = this.allFieldsOf(cn.extends_); + Integer i = 0; + while (i < pf.length()) { + FieldDecl pfd = pf.get(i) as FieldDecl; + result.append(pfd); + i = i + 1; + } + } + Integer i = 0; + while (i < cn.fields.length()) { + FieldDecl cfd = cn.fields.get(i) as FieldDecl; + result.append(cfd); + i = i + 1; + } + } else { + ExceptionDecl? ex = this.findException(name); + if (ex != null) { + ExceptionDecl exn = ex as ExceptionDecl; + if (exn.extends_ != "" && exn.extends_ != "Exception") { + List pf = this.allFieldsOf(exn.extends_); + Integer i = 0; + while (i < pf.length()) { + FieldDecl pfd = pf.get(i) as FieldDecl; + result.append(pfd); + i = i + 1; + } + } + Integer i = 0; + while (i < exn.fields.length()) { + FieldDecl exfd = exn.fields.get(i) as FieldDecl; + result.append(exfd); + i = i + 1; + } + } else { + StructDecl? s = this.findStruct(name); + if (s != null) { + StructDecl sn = s as StructDecl; + Integer i = 0; + while (i < sn.fields.length()) { + FieldDecl sfd = sn.fields.get(i) as FieldDecl; + result.append(sfd); + i = i + 1; + } + } else { + UnionDecl? u = this.findUnion(name); + if (u != null) { + UnionDecl un = u as UnionDecl; + Integer i = 0; + while (i < un.fields.length()) { + FieldDecl ufd = un.fields.get(i) as FieldDecl; + result.append(ufd); + i = i + 1; + } + } + } + } + } + return result; + } + + public fieldIndexOf(name: String, fieldName: String) : Integer { + List fields = this.allFieldsOf(name); + Integer i = 0; + while (i < fields.length()) { + FieldDecl fd = fields.get(i) as FieldDecl; + if (fd.name == fieldName) { return i; } + i = i + 1; + } + return -1; + } + + public findMethodOn(className: String, methodName: String) : MethodDecl? { + ClassDecl? c = this.findClass(className); + if (c != null) { + ClassDecl cn = c as ClassDecl; + Integer i = 0; + while (i < cn.methods.length()) { + MethodDecl md = cn.methods.get(i) as MethodDecl; + if (md.name == methodName) { return md; } + i = i + 1; + } + if (cn.extends_ != "") { return this.findMethodOn(cn.extends_, methodName); } + } + ExceptionDecl? ex = this.findException(className); + if (ex != null) { + ExceptionDecl exn = ex as ExceptionDecl; + Integer i = 0; + while (i < exn.methods.length()) { + MethodDecl md = exn.methods.get(i) as MethodDecl; + if (md.name == methodName) { return md; } + i = i + 1; + } + } + StructDecl? s = this.findStruct(className); + if (s != null) { + StructDecl sn = s as StructDecl; + Integer i = 0; + while (i < sn.methods.length()) { + MethodDecl md = sn.methods.get(i) as MethodDecl; + if (md.name == methodName) { return md; } + i = i + 1; + } + } + ExtensionDecl? ext = this.findExtension(className); + if (ext != null) { + ExtensionDecl extn = ext as ExtensionDecl; + Integer i = 0; + while (i < extn.methods.length()) { + MethodDecl md = extn.methods.get(i) as MethodDecl; + if (md.name == methodName) { return md; } + i = i + 1; + } + } + return null; + } + + public findConstructor(name: String) : ConstructorDecl? { + ClassDecl? c = this.findClass(name); + if (c != null) { if (c.ctor != null) { return c.ctor; } } + ExceptionDecl? ex = this.findException(name); + if (ex != null) { if (ex.ctor != null) { return ex.ctor; } } + return null; + } + + public enumVariantIndex(enumName: String, variantName: String) : Integer { + EnumDecl? e = this.findEnum(enumName); + if (e != null) { + EnumDecl en = e as EnumDecl; + Integer ei = 0; + while (ei < en.variants.length()) { + EnumVariant ev = en.variants.get(ei) as EnumVariant; + if (ev.name == variantName) { return ei; } + ei = ei + 1; + } + } + return -1; + } + + public currentReturnType() : AstType? { + return this.currentRetTy; + } + + public currentClass() : String { + return this.currentClassName; + } +} diff --git a/benchmarks/selfhost-chain/Dockerfile b/benchmarks/selfhost-chain/Dockerfile new file mode 100644 index 0000000..4d65293 --- /dev/null +++ b/benchmarks/selfhost-chain/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash python3 coreutils \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace + +COPY run-chain.sh /workspace/run-chain.sh +RUN chmod +x /workspace/run-chain.sh + +ENTRYPOINT ["/workspace/run-chain.sh"] diff --git a/benchmarks/selfhost-chain/run-chain.sh b/benchmarks/selfhost-chain/run-chain.sh new file mode 100644 index 0000000..d4333e2 --- /dev/null +++ b/benchmarks/selfhost-chain/run-chain.sh @@ -0,0 +1,244 @@ +#!/bin/bash +set -euo pipefail + +# === Arimo V1 Self-Hosting Chain Test === +# S1→S2→S3→S4 with memory monitoring at each stage +# Kills process if memory > 5GB +# arc build writes output to project dir, we copy to out/ + +PROJECT="/workspace/arimo" +S1="/workspace/arc.v1" +OUTDIR="/workspace/out" +MEMLOG="/workspace/out/memory.log" +KILL_LIMIT_KB=5242880 # 5 GB in KB + +mkdir -p "$OUTDIR" + +echo "=== Arimo V1 Self-Hosting Chain Test ===" | tee "$MEMLOG" +echo "=== Started: $(date)" | tee -a "$MEMLOG" +echo "=== Kernel: $(uname -r)" | tee -a "$MEMLOG" +echo "=== CPU: $(nproc) cores" | tee -a "$MEMLOG" +echo "=== RAM: $(free -m | awk '/Mem:/{print $2}') MB total" | tee -a "$MEMLOG" + +# Verify S1 exists +S1_SIZE=$(stat -c%s "$S1" 2>/dev/null || echo 0) +S1_SHA=$(sha256sum "$S1" | awk '{print $1}') +echo "[S1] Binary: ${S1_SIZE}bytes, SHA256=${S1_SHA}" | tee -a "$MEMLOG" + +# --- memory monitor (background, subshell) --- +monitor_mem() { + local pid=$1 + local label=$2 + local logfile=$3 + local max_kb=0 + local peak=0 + local killed=0 + while kill -0 "$pid" 2>/dev/null; do + if [ -f "/proc/$pid/status" ]; then + peak=$(grep VmPeak /proc/"$pid"/status 2>/dev/null | awk '{print $2}' || echo 0) + if [ "$peak" -gt "$max_kb" ]; then + max_kb=$peak + fi + if [ "$peak" -gt "$KILL_LIMIT_KB" ]; then + echo "[FATAL] $label: VmPeak=${peak}KB > 5GB limit! Killing PID=$pid" | tee -a "$logfile" + kill -9 "$pid" 2>/dev/null || true + killed=1 + break + fi + fi + sleep 0.1 + done + wait "$pid" 2>/dev/null || true + local exit_code=$? + local max_mb=$((max_kb / 1024)) + if [ "$killed" -eq 1 ]; then + echo "[MEM] $label: VmPeak=${max_mb}MB (KILLED — exceeded 5GB)" | tee -a "$logfile" + return 99 + fi + echo "[MEM] $label: VmPeak=${max_mb}MB (${max_kb}KB), ExitCode=${exit_code}" | tee -a "$logfile" + return $exit_code +} + +# --- compile step --- +compile_step() { + local compiler="$1" + local label="$2" + local dest="$3" + + echo "" | tee -a "$MEMLOG" + echo "=========================================" | tee -a "$MEMLOG" + echo "=== $label" | tee -a "$MEMLOG" + echo "=== Compiler: $(basename "$compiler")" | tee -a "$MEMLOG" + echo "=== Time: $(date '+%H:%M:%S')" | tee -a "$MEMLOG" + echo "=========================================" | tee -a "$MEMLOG" + + cd "$PROJECT" + + # Remove stale output binary so we can detect new one + rm -f arc + + local start_ts=$(date +%s%3N) + + # Run compiler in subshell + ( + "$compiler" build --target linux & + local cpid=$! + monitor_mem "$cpid" "$label" "$MEMLOG" + exit $? + ) + local exit_code=$? + + local end_ts=$(date +%s%3N) + local elapsed=$((end_ts - start_ts)) + + if [ "$exit_code" -eq 99 ]; then + echo "[FATAL] $label: KILLED — memory exceeded 5GB" | tee -a "$MEMLOG" + return 99 + fi + + if [ "$exit_code" -ne 0 ]; then + echo "[FAIL] $label: Compile failed with exit code $exit_code" | tee -a "$MEMLOG" + return 1 + fi + + # arc build writes output to ./arc in the project dir + if [ ! -f "arc" ]; then + echo "[FAIL] $label: Output binary not found at ./arc in project dir" | tee -a "$MEMLOG" + echo "[FAIL] Files in project dir:" | tee -a "$MEMLOG" + ls -la "$PROJECT/" >> "$MEMLOG" 2>&1 || true + return 1 + fi + + # Copy to output directory with stage name + cp arc "$dest" + chmod +x "$dest" + + local size_bytes=$(stat -c%s "$dest" 2>/dev/null || echo 0) + local sha=$(sha256sum "$dest" | awk '{print $1}') + echo "[OK] $label: ${elapsed}ms, Binary=${size_bytes}bytes" | tee -a "$MEMLOG" + echo "[OK] $label: SHA256=${sha}" | tee -a "$MEMLOG" + + # Save metadata + echo "$sha" > "${dest}.sha256" + echo "$elapsed" > "${dest}.elapsed" + return 0 +} + +# --- main --- + +# --- S1 → S2 --- +echo "" | tee -a "$MEMLOG" +echo ">>> Phase 1: S1 → S2" | tee -a "$MEMLOG" +compile_step "$S1" "S1→S2" "$OUTDIR/arc.s2" +S2_RC=$? +if [ "$S2_RC" -eq 99 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S1→S2 memory > 5GB — test ABORTED" | tee -a "$MEMLOG" + exit 1 +elif [ "$S2_RC" -ne 0 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S1→S2 failed — test ABORTED" | tee -a "$MEMLOG" + exit 1 +fi + +# --- S2 → S3 --- +echo "" | tee -a "$MEMLOG" +echo ">>> Phase 2: S2 → S3" | tee -a "$MEMLOG" +compile_step "$OUTDIR/arc.s2" "S2→S3" "$OUTDIR/arc.s3" +S3_RC=$? +if [ "$S3_RC" -eq 99 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S2→S3 memory > 5GB — test ABORTED" | tee -a "$MEMLOG" + exit 1 +elif [ "$S3_RC" -ne 0 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S2→S3 failed — test ABORTED" | tee -a "$MEMLOG" + exit 1 +fi + +# --- S3 → S4 --- +echo "" | tee -a "$MEMLOG" +echo ">>> Phase 3: S3 → S4" | tee -a "$MEMLOG" +compile_step "$OUTDIR/arc.s3" "S3→S4" "$OUTDIR/arc.s4" +S4_RC=$? +if [ "$S4_RC" -eq 99 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S3→S4 memory > 5GB — test ABORTED" | tee -a "$MEMLOG" + exit 1 +elif [ "$S4_RC" -ne 0 ]; then + echo "" | tee -a "$MEMLOG" + echo "❌ S3→S4 failed — test ABORTED" | tee -a "$MEMLOG" + exit 1 +fi + +# --- Final Report --- +echo "" | tee -a "$MEMLOG" +echo "=========================================" | tee -a "$MEMLOG" +echo "=== FINAL REPORT" | tee -a "$MEMLOG" +echo "=========================================" | tee -a "$MEMLOG" + +S2_SHA=$(cat "$OUTDIR/arc.s2.sha256") +S3_SHA=$(cat "$OUTDIR/arc.s3.sha256") +S4_SHA=$(cat "$OUTDIR/arc.s4.sha256") + +S2_TIME=$(cat "$OUTDIR/arc.s2.elapsed") +S3_TIME=$(cat "$OUTDIR/arc.s3.elapsed") +S4_TIME=$(cat "$OUTDIR/arc.s4.elapsed") + +S2_SIZE=$(stat -c%s "$OUTDIR/arc.s2") +S3_SIZE=$(stat -c%s "$OUTDIR/arc.s3") +S4_SIZE=$(stat -c%s "$OUTDIR/arc.s4") + +echo "" | tee -a "$MEMLOG" +echo "Binary | Time(ms) | Size(B) | SHA256" | tee -a "$MEMLOG" +echo "-------+----------+----------+------------------------------------------" | tee -a "$MEMLOG" +echo "S2 | ${S2_TIME}ms | ${S2_SIZE}B | ${S2_SHA}" | tee -a "$MEMLOG" +echo "S3 | ${S3_TIME}ms | ${S3_SIZE}B | ${S3_SHA}" | tee -a "$MEMLOG" +echo "S4 | ${S4_TIME}ms | ${S4_SIZE}B | ${S4_SHA}" | tee -a "$MEMLOG" +echo "" | tee -a "$MEMLOG" + +# Determinism check +DETERMINISM_RESULT="" +if [ "$S2_SHA" = "$S3_SHA" ] && [ "$S3_SHA" = "$S4_SHA" ]; then + echo "✅ DETERMINISTIC: S2 == S3 == S4 (identical SHA256)" | tee -a "$MEMLOG" + DETERMINISM_RESULT="DETERMINISTIC ✅" +elif [ "$S2_SHA" = "$S3_SHA" ]; then + echo "⚠️ PARTIAL: S2 == S3, but S4 differs" | tee -a "$MEMLOG" + DETERMINISM_RESULT="PARTIAL ⚠️" +elif [ "$S3_SHA" = "$S4_SHA" ]; then + echo "⚠️ CONVERGED: S3 == S4, but differs from S2" | tee -a "$MEMLOG" + DETERMINISM_RESULT="CONVERGED ⚠️" +else + echo "❌ NOT DETERMINISTIC: All three binaries differ" | tee -a "$MEMLOG" + DETERMINISM_RESULT="NOT DETERMINISTIC ❌" +fi + +# Memory growth analysis +echo "" | tee -a "$MEMLOG" +echo "=== Memory Analysis ===" | tee -a "$MEMLOG" + +MEM_S1S2=$(grep 'S1→S2' "$MEMLOG" | grep '\[MEM\]' | grep -oP 'VmPeak=\K[0-9]+' || echo 0) +MEM_S2S3=$(grep 'S2→S3' "$MEMLOG" | grep '\[MEM\]' | grep -oP 'VmPeak=\K[0-9]+' || echo 0) +MEM_S3S4=$(grep 'S3→S4' "$MEMLOG" | grep '\[MEM\]' | grep -oP 'VmPeak=\K[0-9]+' || echo 0) + +echo "VmPeak trend: S1→S2: ${MEM_S1S2}MB, S2→S3: ${MEM_S2S3}MB, S3→S4: ${MEM_S3S4}MB" | tee -a "$MEMLOG" + +if [ "$MEM_S1S2" -gt 0 ] && [ "$MEM_S2S3" -gt 0 ] && [ "$MEM_S3S4" -gt 0 ]; then + if [ "$MEM_S3S4" -gt "$MEM_S2S3" ] && [ "$MEM_S2S3" -gt "$MEM_S1S2" ]; then + echo "⚠️ Memory GROWS each generation!" | tee -a "$MEMLOG" + elif [ "$MEM_S2S3" -le "$MEM_S1S2" ] && [ "$MEM_S3S4" -le "$MEM_S2S3" ]; then + echo "✅ Memory STABLE or DECREASING across generations" | tee -a "$MEMLOG" + else + echo "➡️ Memory pattern MIXED (no clear trend)" | tee -a "$MEMLOG" + fi +fi + +echo "" | tee -a "$MEMLOG" +echo "=== Test completed: $(date) ===" | tee -a "$MEMLOG" + +# Return status +if echo "$DETERMINISM_RESULT" | grep -q "DETERMINISTIC"; then + exit 0 +else + exit 1 +fi diff --git a/benchmarks/selfhost-chain/run-isolated.sh b/benchmarks/selfhost-chain/run-isolated.sh new file mode 100644 index 0000000..b81ce27 --- /dev/null +++ b/benchmarks/selfhost-chain/run-isolated.sh @@ -0,0 +1,102 @@ +#!/bin/bash +set -euo pipefail + +# === Arimo V1 Self-Hosting Chain — Isolated Test === +# Uses bwrap (bubblewrap) for filesystem isolation +# Project source COPIED to tmpfs (compiler writes output to project dir) +# Monitors memory via /proc, kills if > 5GB + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_SRC="/home/arimo/Desktop/arimo" +V1_BINARY="/tmp/arimo-chain-test/arimo-linux-x64/arc" +OUTDIR="/tmp/arimo-chain-results" + +echo "=== Arimo V1 Self-Hosting Chain (Isolated) ===" +echo "Project: $PROJECT_SRC" +echo "V1 binary: $V1_BINARY" +echo "Output: $OUTDIR" +echo "" + +# Verify inputs +if [ ! -f "$V1_BINARY" ]; then + echo "ERROR: V1 binary not found at $V1_BINARY" + exit 1 +fi + +V1_SHA=$(sha256sum "$V1_BINARY" | awk '{print $1}') +echo "V1 SHA256: $V1_SHA" + +# Prepare output directory +rm -rf "$OUTDIR" +mkdir -p "$OUTDIR" + +# Create temp workspace +TMPDIR=$(mktemp -d /tmp/arimo-bwrap-XXXXXX) +echo "Temp dir: $TMPDIR" + +# Copy project source to tmpdir (compiler needs to write output binary to project dir) +echo "Copying project source..." +cp -r "$PROJECT_SRC" "$TMPDIR/arimo" +# Remove any stale build artifacts +rm -f "$TMPDIR/arimo/arc" 2>/dev/null || true + +# Copy run script and V1 binary +cp "$SCRIPT_DIR/run-chain.sh" "$TMPDIR/run-chain.sh" +cp "$V1_BINARY" "$TMPDIR/arc.v1" +chmod +x "$TMPDIR/arc.v1" +chmod +x "$TMPDIR/run-chain.sh" +mkdir -p "$TMPDIR/out" + +echo "Starting isolated test..." +echo "Memory limit: 5 GB" +echo "=========================================" + +# Run inside bwrap: +# - All system dirs read-only (protection) +# - /tmpfs for workspace (isolated, writable) +# - /proc and /dev for runtime +bwrap \ + --ro-bind /usr /usr \ + --ro-bind /lib64 /lib64 \ + --ro-bind /lib /lib \ + --ro-bind /bin /bin \ + --ro-bind /sbin /sbin \ + --ro-bind /etc /etc \ + --ro-bind /run /run \ + --ro-bind /opt /opt 2>/dev/null || true \ + --proc /proc \ + --dev /dev \ + --tmpfs /tmp \ + --bind "$TMPDIR" /workspace \ + /workspace/run-chain.sh + +EXIT_CODE=$? + +echo "" +echo "=========================================" +echo "Exit code: $EXIT_CODE" + +# Copy results back +if [ -d "$TMPDIR/out" ]; then + cp -r "$TMPDIR/out"/* "$OUTDIR/" 2>/dev/null || true + echo "Results copied to $OUTDIR" +fi +# Also copy the generated arc binaries from the project dir +for stage in s2 s3 s4; do + SRC="$TMPDIR/arimo/arc.${stage}" + if [ -f "$SRC" ]; then + cp "$SRC" "$OUTDIR/arc.${stage}" 2>/dev/null || true + fi +done + +# Copy the final arc binary produced in the project dir +if [ -f "$TMPDIR/arimo/arc" ]; then + echo "Final arc binary found in project dir" +fi + +# Cleanup temp +rm -rf "$TMPDIR" + +echo "" +echo "=== Test complete ===" +exit $EXIT_CODE diff --git a/stdlib/arimo/fs/File.arm b/stdlib/arimo/fs/File.arm index 56f7e28..4e2661c 100644 --- a/stdlib/arimo/fs/File.arm +++ b/stdlib/arimo/fs/File.arm @@ -1,71 +1,70 @@ -package arimo.fs; - -import arimo.fs.IOException; - -extern "C" { - fopen(path: String, mode: String) : RawPtr; - fread(buf: RawPtr, size: Integer, n: Integer, f: RawPtr) : Integer; - fwrite(buf: RawPtr, size: Integer, n: Integer, f: RawPtr) : Integer; - fclose(f: RawPtr) : Integer; - fseek(f: RawPtr, offset: Integer, whence: Integer) : Integer; - ftell(f: RawPtr) : Integer; - remove(path: String) : Integer; - calloc(n: Integer, size: Integer) : RawPtr; -} - -public class File { - - public static exists(path: String) : Boolean { - RawPtr f = fopen(path, "r"); - if (f == null) { return false; } - fclose(f); - return true; - } - - public static read(path: String) : String { - RawPtr f = fopen(path, "rb"); - if (f == null) { - throw IOException("cannot open file: ${path}"); - } - fseek(f, 0, 2); - Integer size = ftell(f); - fseek(f, 0, 0); - RawPtr buf = calloc(size + 1, 1); - fread(buf, 1, size, f); - fclose(f); - return buf as String; - } - - public static write(path: String, content: String) : Void { - RawPtr f = fopen(path, "w"); - if (f == null) { - throw IOException("cannot write file: ${path}"); - } - fwrite(content as RawPtr, 1, content.length(), f); - fclose(f); - } - - public static append(path: String, content: String) : Void { - RawPtr f = fopen(path, "a"); - if (f == null) { - throw IOException("cannot open file for append: ${path}"); - } - fwrite(content as RawPtr, 1, content.length(), f); - fclose(f); - } - - public static delete(path: String) : Boolean { - Integer r = remove(path); - if (r == 0) { return true; } - return false; - } - - public static size(path: String) : Integer { - RawPtr f = fopen(path, "r"); - if (f == null) { return -1; } - fseek(f, 0, 2); - Integer sz = ftell(f); - fclose(f); - return sz; - } -} +package arimo.fs; + +import arimo.fs.IOException; + +extern "C" { + fopen(path: String, mode: String) : RawPtr; + fread(buf: RawPtr, size: Integer, n: Integer, f: RawPtr) : Integer; + fwrite(buf: RawPtr, size: Integer, n: Integer, f: RawPtr) : Integer; + fclose(f: RawPtr) : Integer; + fseek(f: RawPtr, offset: Integer, whence: Integer) : Integer; + ftell(f: RawPtr) : Integer; + remove(path: String) : Integer; + calloc(n: Integer, size: Integer) : RawPtr; +} + +public class File { + + public static exists(path: String) : Boolean { + RawPtr f = fopen(path, "r"); + if (f == null) { return false; } + return true; + } + + public static read(path: String) : String { + RawPtr f = fopen(path, "rb"); + if (f == null) { + throw IOException("cannot open file: ${path}"); + } + fseek(f, 0, 2); + Integer size = ftell(f); + fseek(f, 0, 0); + RawPtr buf = calloc(size + 1, 1); + fread(buf, 1, size, f); + fclose(f); + return buf as String; + } + + public static write(path: String, content: String) : Void { + RawPtr f = fopen(path, "w"); + if (f == null) { + throw IOException("cannot write file: ${path}"); + } + fwrite(content as RawPtr, 1, content.length(), f); + fclose(f); + } + + public static append(path: String, content: String) : Void { + RawPtr f = fopen(path, "a"); + if (f == null) { + throw IOException("cannot open file for append: ${path}"); + } + fwrite(content as RawPtr, 1, content.length(), f); + fclose(f); + } + + public static delete(path: String) : Boolean { + Integer r = remove(path); + if (r == 0) { return true; } + return false; + } + + public static size(path: String) : Integer { + RawPtr f = fopen(path, "r"); + if (f == null) { return -1; } + fseek(f, 0, 2); + Integer sz = ftell(f); + fclose(f); + return sz; + } +} diff --git a/test/env_stubs.arm b/test/env_stubs.arm new file mode 100644 index 0000000..a5b526a --- /dev/null +++ b/test/env_stubs.arm @@ -0,0 +1,21 @@ +package test; + +import arimo.env.Env; + +public class EnvStubTest { + public static main() : Void { + // Env.args() stub — must not SIGSEGV, returns empty list + List a = Env.args(); + + // Env.exePath() stub — must not SIGSEGV, returns empty string + String p = Env.exePath(); + + // Env.platform() — must not SIGSEGV + String pl = Env.platform(); + + IO.println("Env.args OK"); + IO.println("Env.exePath OK"); + IO.println("Env.platform OK"); + IO.println("Env stubs PASS"); + } +}