summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.projectile0
-rw-r--r--lexer/lexer.odin257
-rw-r--r--main.odin17
-rw-r--r--repl/repl.odin30
-rw-r--r--token/token.odin84
5 files changed, 388 insertions, 0 deletions
diff --git a/.projectile b/.projectile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.projectile
diff --git a/lexer/lexer.odin b/lexer/lexer.odin
new file mode 100644
index 0000000..d4ad9b3
--- /dev/null
+++ b/lexer/lexer.odin
@@ -0,0 +1,257 @@
+package lexer
+
+import token "../token"
+import "core:fmt"
+import "core:log"
+import "core:reflect"
+import "core:strconv"
+import "core:testing"
+
+Lexer :: struct {
+ input: string,
+ position: int,
+ read_position: int,
+ ch: byte,
+}
+
+new :: proc(input: string) -> Lexer {
+ l := Lexer{input, 0, 0, 0}
+ read_char(&l)
+ return l
+}
+
+read_char :: proc(l: ^Lexer) {
+ if l.read_position >= len(l.input) {
+ l.ch = 0
+ } else {
+ l.ch = l.input[l.read_position]
+ }
+ l.position = l.read_position
+ l.read_position += 1
+}
+
+peek_char :: proc(l: ^Lexer) -> byte {
+ if l.read_position >= len(l.input) {
+ return 0
+ } else {
+ return l.input[l.read_position]
+ }
+
+}
+
+next_token :: proc(l: ^Lexer) -> token.Token {
+ tok: token.Token
+
+ skip_whitespace(l)
+
+ switch l.ch {
+ case '=':
+ if peek_char(l) == '=' {
+ tok = token.Equals{}
+ read_char(l)
+ } else {
+ tok = token.Equal_Sign{}
+ }
+ read_char(l)
+ case '+':
+ tok = token.Plus{}
+ read_char(l)
+ case '-':
+ tok = token.Minus{}
+ read_char(l)
+ case '!':
+ if peek_char(l) == '=' {
+ tok = token.Not_Equals{}
+ read_char(l)
+ } else {
+ tok = token.Bang{}
+ }
+ read_char(l)
+ case '/':
+ tok = token.Forward_Slash{}
+ read_char(l)
+ case '*':
+ tok = token.Asterisk{}
+ read_char(l)
+ case '<':
+ tok = token.Less_Than{}
+ read_char(l)
+ case '>':
+ tok = token.Greater_Than{}
+ read_char(l)
+ case ';':
+ tok = token.Semicolon{}
+ read_char(l)
+ case ',':
+ tok = token.Comma{}
+ read_char(l)
+ case '(':
+ tok = token.Left_Parenthesis{}
+ read_char(l)
+ case ')':
+ tok = token.Right_Parenthesis{}
+ read_char(l)
+ case '{':
+ tok = token.Left_Brace{}
+ read_char(l)
+ case '}':
+ tok = token.Right_Brace{}
+ read_char(l)
+ case 0:
+ tok = token.EOF{}
+ read_char(l)
+ case:
+ if is_letter(l.ch) {
+ tok = read_identifier(l)
+ } else if is_digit(l.ch) {
+ tok = read_number(l)
+ } else {
+ tok = token.Illegal{}
+ }
+ }
+
+ return tok
+}
+
+skip_whitespace :: proc(l: ^Lexer) {
+ for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
+ read_char(l)
+ }
+}
+
+read_identifier :: proc(l: ^Lexer) -> token.Token {
+ position := l.position
+ for is_letter(l.ch) {
+ read_char(l)
+ }
+ ident := l.input[position:l.position]
+ return token.lookup_identifier(ident)
+}
+
+read_number :: proc(l: ^Lexer) -> token.Token {
+ position := l.position
+ for is_digit(l.ch) {
+ read_char(l)
+ }
+ integer_raw := l.input[position:l.position]
+ integer, ok := strconv.parse_int(integer_raw)
+ if !ok {
+ return token.Illegal{}
+ }
+ return token.Integer{integer}
+}
+
+is_letter :: proc(ch: byte) -> bool {
+ return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
+}
+
+is_digit :: proc(ch: byte) -> bool {
+ return '0' <= ch && ch <= '9'
+}
+
+@(test)
+test_next_token :: proc(t: ^testing.T) {
+ input := `let five = 5;
+let ten = 10;
+let add = fn(x, y) {
+ x + y;
+};
+
+let result = add(five, ten);
+!-/*5;
+5 < 10 > 5;
+
+if (5 < 10) {
+ return true;
+} else {
+ return false;
+}
+
+10 == 10;
+10 != 9;
+`
+
+ tests := []token.Token {
+ token.Let{},
+ token.Identifier{"five"},
+ token.Equal_Sign{},
+ token.Integer{5},
+ token.Semicolon{},
+ token.Let{},
+ token.Identifier{"ten"},
+ token.Equal_Sign{},
+ token.Integer{10},
+ token.Semicolon{},
+ token.Let{},
+ token.Identifier{"add"},
+ token.Equal_Sign{},
+ token.Function{},
+ token.Left_Parenthesis{},
+ token.Identifier{"x"},
+ token.Comma{},
+ token.Identifier{"y"},
+ token.Right_Parenthesis{},
+ token.Left_Brace{},
+ token.Identifier{"x"},
+ token.Plus{},
+ token.Identifier{"y"},
+ token.Semicolon{},
+ token.Right_Brace{},
+ token.Semicolon{},
+ token.Let{},
+ token.Identifier{"result"},
+ token.Equal_Sign{},
+ token.Identifier{"add"},
+ token.Left_Parenthesis{},
+ token.Identifier{"five"},
+ token.Comma{},
+ token.Identifier{"ten"},
+ token.Right_Parenthesis{},
+ token.Semicolon{},
+ token.Bang{},
+ token.Minus{},
+ token.Forward_Slash{},
+ token.Asterisk{},
+ token.Integer{5},
+ token.Semicolon{},
+ token.Integer{5},
+ token.Less_Than{},
+ token.Integer{10},
+ token.Greater_Than{},
+ token.Integer{5},
+ token.Semicolon{},
+ token.If{},
+ token.Left_Parenthesis{},
+ token.Integer{5},
+ token.Less_Than{},
+ token.Integer{10},
+ token.Right_Parenthesis{},
+ token.Left_Brace{},
+ token.Return{},
+ token.True{},
+ token.Semicolon{},
+ token.Right_Brace{},
+ token.Else{},
+ token.Left_Brace{},
+ token.Return{},
+ token.False{},
+ token.Semicolon{},
+ token.Right_Brace{},
+ token.Integer{10},
+ token.Equals{},
+ token.Integer{10},
+ token.Semicolon{},
+ token.Integer{10},
+ token.Not_Equals{},
+ token.Integer{9},
+ token.Semicolon{},
+ token.EOF{},
+ }
+
+ l := new(input)
+ for tt in tests {
+ char := l.ch
+ tok := next_token(&l)
+ testing.expect_value(t, tok, tt)
+ }
+}
diff --git a/main.odin b/main.odin
new file mode 100644
index 0000000..8a4e1cb
--- /dev/null
+++ b/main.odin
@@ -0,0 +1,17 @@
+package main
+
+import "core:fmt"
+import "core:os"
+import "repl"
+
+main :: proc() {
+ user, ok := os.lookup_env_alloc("USER", context.allocator)
+ if !ok {
+ fmt.eprintln("Could not determine username")
+ return
+ }
+
+ fmt.printf("Hello %s! This is the Monkey programming language!\n", user)
+ fmt.println("Feel free to type in commands")
+ repl.start(os.to_stream(os.stdin), os.to_stream(os.stdout))
+}
diff --git a/repl/repl.odin b/repl/repl.odin
new file mode 100644
index 0000000..bb4a1b2
--- /dev/null
+++ b/repl/repl.odin
@@ -0,0 +1,30 @@
+package repl
+
+import "../lexer"
+import "../token"
+import "core:bufio"
+import "core:fmt"
+import "core:io"
+
+PROMPT :: ">> "
+
+start :: proc(r: io.Reader, w: io.Writer) {
+ s := bufio.scanner_init(&bufio.Scanner{}, r)
+ defer bufio.scanner_destroy(s)
+
+ for {
+ fmt.wprintf(w, PROMPT)
+ scanned := bufio.scan(s)
+ if !scanned {
+ return
+ }
+
+ line := bufio.scanner_text(s)
+ l := lexer.new(line)
+
+ eof: token.EOF
+ for tok := lexer.next_token(&l); tok != eof; tok = lexer.next_token(&l) {
+ fmt.wprintf(w, "%v\n", tok)
+ }
+ }
+}
diff --git a/token/token.odin b/token/token.odin
new file mode 100644
index 0000000..20efd6f
--- /dev/null
+++ b/token/token.odin
@@ -0,0 +1,84 @@
+package token
+
+Token :: union {
+ Asterisk,
+ Bang,
+ Comma,
+ EOF,
+ Else,
+ Equal_Sign,
+ Equals,
+ False,
+ Forward_Slash,
+ Function,
+ Greater_Than,
+ Identifier,
+ If,
+ Illegal,
+ Integer,
+ Left_Brace,
+ Left_Parenthesis,
+ Less_Than,
+ Let,
+ Minus,
+ Not_Equals,
+ Plus,
+ Return,
+ Right_Brace,
+ Right_Parenthesis,
+ Semicolon,
+ True,
+}
+
+Asterisk :: struct {}
+Bang :: struct {}
+Comma :: struct {}
+EOF :: struct {}
+Else :: struct {}
+Equal_Sign :: struct {}
+Equals :: struct {}
+False :: struct {}
+Forward_Slash :: struct {}
+Function :: struct {}
+Greater_Than :: struct {}
+Identifier :: struct {
+ literal: string,
+}
+If :: struct {}
+Illegal :: struct {}
+Integer :: struct {
+ literal: int,
+}
+Left_Brace :: struct {}
+Left_Parenthesis :: struct {}
+Less_Than :: struct {}
+Let :: struct {}
+Minus :: struct {}
+Not_Equals :: struct {}
+Plus :: struct {}
+Return :: struct {}
+Right_Brace :: struct {}
+Right_Parenthesis :: struct {}
+Semicolon :: struct {}
+True :: struct {}
+
+lookup_identifier :: proc(ident: string) -> Token {
+ switch ident {
+ case "fn":
+ return Function{}
+ case "let":
+ return Let{}
+ case "if":
+ return If{}
+ case "else":
+ return Else{}
+ case "true":
+ return True{}
+ case "false":
+ return False{}
+ case "return":
+ return Return{}
+ case:
+ return Identifier{ident}
+ }
+}