From 7944c76a6a876d88ed92327cf19f8bc2b93e1a1a Mon Sep 17 00:00:00 2001
From: sfja <sfja2004@gmail.com>
Date: Sun, 22 Dec 2024 02:30:23 +0100
Subject: [PATCH] add char literals

---
 compiler/arch.ts                              |  3 ++-
 compiler/lexer.ts                             | 26 +++++++++++++++++++
 editors/vim/syntax/slige.vim                  |  3 +++
 editors/vscode/syntaxes/slige.tmLanguage.json | 11 ++++++++
 runtime/arch.hpp                              |  3 ++-
 runtime/vm.cpp                                |  8 +++++-
 tests/char_literal.slg                        | 10 +++++++
 7 files changed, 61 insertions(+), 3 deletions(-)
 create mode 100644 tests/char_literal.slg

diff --git a/compiler/arch.ts b/compiler/arch.ts
index eca36bd..b6fc64f 100644
--- a/compiler/arch.ts
+++ b/compiler/arch.ts
@@ -40,7 +40,8 @@ export const Ops = {
 
 export type Builtins = typeof Builtins;
 export const Builtins = {
-    IntToString: 0x00,
+    Exit: 0x00,
+    IntToString: 0x01,
     StringConcat: 0x10,
     StringEqual: 0x11,
     StringCharAt: 0x12,
diff --git a/compiler/lexer.ts b/compiler/lexer.ts
index 99c8e24..4774ae9 100644
--- a/compiler/lexer.ts
+++ b/compiler/lexer.ts
@@ -70,6 +70,32 @@ export class Lexer {
             return { ...this.token("int", pos), intValue: 0 };
         }
 
+        if (this.test("'")) {
+            this.step();
+            let value: string;
+            if (this.test("\\")) {
+                this.step();
+                if (this.done()) {
+                    this.report("malformed character literal", pos);
+                    return this.token("error", pos);
+                }
+                value = {
+                    n: "\n",
+                    t: "\t",
+                    "0": "\0",
+                }[this.current()] ?? this.current();
+            } else {
+                value = this.current();
+            }
+            this.step();
+            if (this.done() || !this.test("'") || value.length === 0) {
+                this.report("malformed character literal", pos);
+                return this.token("error", pos);
+            }
+            this.step();
+            return { ...this.token("int", pos), intValue: value.charCodeAt(0) };
+        }
+
         if (this.test('"')) {
             this.step();
             let value = "";
diff --git a/editors/vim/syntax/slige.vim b/editors/vim/syntax/slige.vim
index 89473ba..a9f334f 100644
--- a/editors/vim/syntax/slige.vim
+++ b/editors/vim/syntax/slige.vim
@@ -37,6 +37,9 @@ syn match Number '0[0-7]\+'
 syn match Number '0x[0-9a-fA-F]\+'
 syn match Number '0b[01]\+'
 
+syn match Character "'[^\\]'"
+syn match Character "'\\.'"
+
 syn region String   start=+"+  skip=+\\"+  end=+"+
 
 syn keyword Todo contained TODO FIXME XXX NOTE
diff --git a/editors/vscode/syntaxes/slige.tmLanguage.json b/editors/vscode/syntaxes/slige.tmLanguage.json
index edbfb9d..99c0096 100644
--- a/editors/vscode/syntaxes/slige.tmLanguage.json
+++ b/editors/vscode/syntaxes/slige.tmLanguage.json
@@ -53,6 +53,17 @@
                 }
             ]
         },
+        "chars": {
+            "name": "string.quoted.double.slige",
+            "begin": "'",
+            "end": "'",
+            "patterns": [
+                {
+                    "name": "constant.character.escape.slige",
+                    "match": "\\\\."
+                }
+            ]
+        },
         "numbers": {
             "patterns": [
                 {
diff --git a/runtime/arch.hpp b/runtime/arch.hpp
index d7ae557..91ce34d 100644
--- a/runtime/arch.hpp
+++ b/runtime/arch.hpp
@@ -41,7 +41,8 @@ enum class Op : uint32_t {
 };
 
 enum class Builtin : uint32_t {
-    IntToString = 0x00,
+    Exit = 0x00,
+    IntToString = 0x01,
     StringConcat = 0x10,
     StringEqual = 0x11,
     StringCharAt = 0x12,
diff --git a/runtime/vm.cpp b/runtime/vm.cpp
index cd0087f..a659714 100644
--- a/runtime/vm.cpp
+++ b/runtime/vm.cpp
@@ -300,9 +300,15 @@ void VM::run_builtin(Builtin builtin_id)
             maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
     }
     switch (builtin_id) {
+        case Builtin::Exit: {
+            assert_stack_has(1);
+            auto status_code = stack_pop().as_int().value;
+            std::exit(status_code);
+            break;
+        }
         case Builtin::IntToString: {
             assert_stack_has(1);
-            auto number = static_cast<int32_t>(stack_pop().as_int().value);
+            auto number = stack_pop().as_int().value;
             auto str = std::to_string(number);
             stack_push(String(str));
             break;
diff --git a/tests/char_literal.slg b/tests/char_literal.slg
new file mode 100644
index 0000000..91e1163
--- /dev/null
+++ b/tests/char_literal.slg
@@ -0,0 +1,10 @@
+
+fn exit(status_code: int) #[builtin(Exit)] {}
+
+fn main() {
+    if 'A' != 65 {
+        exit(1);
+    }
+    exit(0);
+}
+