Lox interpreter written in Gleam, following Crafting Intepreters
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial glox

beaudidly a664c853

+697
+20
.nvim.lua
··· 1 + -- Project-local nvim config for glox. 2 + -- Auto-loaded when nvim starts here (requires `vim.opt.exrc = true`). 3 + -- Run `:trust` once to allowlist this file. 4 + 5 + local function gleam_run_float() 6 + Snacks.terminal({ "gleam", "run" }, { 7 + cwd = vim.fn.getcwd(), 8 + win = { 9 + style = "float", 10 + border = "rounded", 11 + width = 0.85, 12 + height = 0.8, 13 + }, 14 + interactive = true, 15 + }) 16 + end 17 + 18 + vim.keymap.set("n", "<leader>gr", gleam_run_float, { silent = true, desc = "Gleam: run (float)" }) 19 + 20 + vim.api.nvim_create_user_command("GleamRun", gleam_run_float, {})
+20
Makefile
··· 1 + .PHONY: build test test-expr run clean 2 + 3 + build: 4 + gleam build 5 + 6 + test: 7 + gleam test 8 + 9 + test-expr: build 10 + gleam build --target erlang 11 + erl -pa build/dev/erlang/*/ebin -noshell \ 12 + -eval 'eunit:test(expr_test, [verbose]).' \ 13 + -s init stop 14 + 15 + run: 16 + @if [ -z "$(FILE)" ]; then gleam run; \ 17 + else gleam run -- "$(FILE)"; fi 18 + 19 + clean: 20 + rm -rf build
+24
README.md
··· 1 + # glox 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/glox)](https://hex.pm/packages/glox) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/glox/) 5 + 6 + ```sh 7 + gleam add glox@1 8 + ``` 9 + ```gleam 10 + import glox 11 + 12 + pub fn main() -> Nil { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/glox>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+61
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-utils": { 4 + "inputs": { 5 + "systems": "systems" 6 + }, 7 + "locked": { 8 + "lastModified": 1731533236, 9 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 + "owner": "numtide", 11 + "repo": "flake-utils", 12 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 + "type": "github" 14 + }, 15 + "original": { 16 + "owner": "numtide", 17 + "repo": "flake-utils", 18 + "type": "github" 19 + } 20 + }, 21 + "nixpkgs": { 22 + "locked": { 23 + "lastModified": 1776169885, 24 + "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", 25 + "owner": "NixOS", 26 + "repo": "nixpkgs", 27 + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", 28 + "type": "github" 29 + }, 30 + "original": { 31 + "owner": "NixOS", 32 + "ref": "nixos-unstable", 33 + "repo": "nixpkgs", 34 + "type": "github" 35 + } 36 + }, 37 + "root": { 38 + "inputs": { 39 + "flake-utils": "flake-utils", 40 + "nixpkgs": "nixpkgs" 41 + } 42 + }, 43 + "systems": { 44 + "locked": { 45 + "lastModified": 1681028828, 46 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 + "owner": "nix-systems", 48 + "repo": "default", 49 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 + "type": "github" 51 + }, 52 + "original": { 53 + "owner": "nix-systems", 54 + "repo": "default", 55 + "type": "github" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+27
flake.nix
··· 1 + { 2 + description = "Crafting Interpreters - glox dev environment"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-utils.url = "github:numtide/flake-utils"; 7 + }; 8 + 9 + outputs = { self, nixpkgs, flake-utils }: 10 + flake-utils.lib.eachDefaultSystem (system: 11 + let 12 + pkgs = import nixpkgs { inherit system; }; 13 + in 14 + { 15 + devShells.default = pkgs.mkShell { 16 + packages = [ 17 + pkgs.gleam 18 + pkgs.erlang 19 + pkgs.rebar3 20 + ]; 21 + 22 + shellHook = '' 23 + echo "[ devshell: $(gleam --version) on $(erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell) ]" 24 + ''; 25 + }; 26 + }); 27 + }
+22
gleam.toml
··· 1 + name = "glox" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + argv = ">= 1.0.2 and < 2.0.0" 18 + input = ">= 1.0.1 and < 2.0.0" 19 + simplifile = ">= 2.4.0 and < 3.0.0" 20 + 21 + [dev_dependencies] 22 + gleeunit = ">= 1.0.0 and < 2.0.0"
+18
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 7 + { name = "gleam_stdlib", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "960090C2FB391784BB34267B099DC9315CC1B1F6013E7415BC763CEF1905D7D3" }, 8 + { name = "gleeunit", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "254B697FE72EEAD7BF82E941723918E421317813AC49923EE76A18C788C61E72" }, 9 + { name = "input", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "input", source = "hex", outer_checksum = "FE84CDADC78A1367E4AFD561A529825A8FEC88D165CBDF511FD3226CABCDEE6F" }, 10 + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, 11 + ] 12 + 13 + [requirements] 14 + argv = { version = ">= 1.0.2 and < 2.0.0" } 15 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 16 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 17 + input = { version = ">= 1.0.1 and < 2.0.0" } 18 + simplifile = { version = ">= 2.4.0 and < 3.0.0" }
+23
src/ast_printer.gleam
··· 1 + import gleam/list 2 + import gleam/string 3 + import expr.{ 4 + type Expr, ExprBinary, ExprGrouping, ExprLit, ExprUnary, LitBool, LitNil, 5 + LitNumber, LitString, 6 + } 7 + 8 + pub fn print(e: Expr) -> String { 9 + case e { 10 + ExprLit(LitNil) -> "nil" 11 + ExprLit(LitBool(tok)) -> tok.lexeme 12 + ExprLit(LitNumber(tok)) -> tok.lexeme 13 + ExprLit(LitString(tok)) -> tok.lexeme 14 + ExprUnary(op, right) -> parenthesize(op.lexeme, [right]) 15 + ExprBinary(left, op, right) -> parenthesize(op.lexeme, [left, right]) 16 + ExprGrouping(inner) -> parenthesize("group", [inner]) 17 + } 18 + } 19 + 20 + fn parenthesize(name: String, exprs: List(Expr)) -> String { 21 + let parts = list.map(exprs, print) 22 + "(" <> name <> " " <> string.join(parts, " ") <> ")" 23 + }
+151
src/expr.gleam
··· 1 + /////// Binary 2 + 3 + import gleam/list 4 + import gleam/option.{type Option, None, Some} 5 + import gleam/result 6 + import scan.{type Token, type TokenType, Token} 7 + 8 + pub type ParseError { 9 + ParseError(tok: Option(Token), msg: String) 10 + } 11 + 12 + pub type Expr { 13 + ExprLit(ExprLit) 14 + ExprUnary(Token, Expr) 15 + ExprBinary(Expr, Token, Expr) 16 + ExprGrouping(Expr) 17 + } 18 + 19 + pub type ExprLit { 20 + LitString(Token) 21 + LitNumber(Token) 22 + LitBool(Token) 23 + LitNil 24 + } 25 + 26 + pub type UnaryOp { 27 + UnaryMinusOp 28 + UnaryNotOp 29 + } 30 + 31 + pub type BinOp { 32 + BinOpEquals 33 + BinOpNotEquals 34 + BinOpGT 35 + BinOpGTE 36 + BinOpLT 37 + BinOpLTE 38 + BinOpPlus 39 + BinOpMinus 40 + BinOpMult 41 + BinOpDiv 42 + BinOpComma 43 + } 44 + 45 + type ParserResult = 46 + Result(#(Expr, List(Token)), ParseError) 47 + 48 + pub fn parse(tokens: List(Token)) -> ParserResult { 49 + expression(tokens) 50 + } 51 + 52 + fn synchronize(tokens: List(Token)) -> List(Token) { 53 + // Seek until we find a sync point (;, or statement-beginning token) 54 + todo 55 + } 56 + 57 + fn bin_loop_left( 58 + acc: Expr, 59 + tokens: List(Token), 60 + ops: List(TokenType), 61 + next: fn(List(Token)) -> ParserResult, 62 + ) -> ParserResult { 63 + case tokens { 64 + [tok, ..rest] -> { 65 + case list.contains(ops, tok.token_type) { 66 + True -> { 67 + use #(rhs, new_rest) <- result.try(next(rest)) 68 + bin_loop_left(ExprBinary(acc, tok, rhs), new_rest, ops, next) 69 + } 70 + _ -> Ok(#(acc, tokens)) 71 + } 72 + } 73 + _ -> Ok(#(acc, tokens)) 74 + } 75 + } 76 + 77 + pub fn bin_left( 78 + tokens: List(Token), 79 + ops: List(TokenType), 80 + next: fn(List(Token)) -> ParserResult, 81 + ) -> ParserResult { 82 + use #(expr, rest) <- result.try(next(tokens)) 83 + 84 + bin_loop_left(expr, rest, ops, next) 85 + } 86 + 87 + pub fn expression(tokens: List(Token)) -> ParserResult { 88 + comma(tokens) 89 + } 90 + 91 + pub fn comma(tokens: List(Token)) -> ParserResult { 92 + bin_left(tokens, [scan.Comma], equality) 93 + } 94 + 95 + pub fn equality(tokens: List(Token)) -> ParserResult { 96 + bin_left(tokens, [scan.BangEqual, scan.EqualEqual], comparison) 97 + } 98 + 99 + pub fn comparison(tokens: List(Token)) -> ParserResult { 100 + bin_left( 101 + tokens, 102 + [scan.Greater, scan.GreaterEqual, scan.Less, scan.LessEqual], 103 + term, 104 + ) 105 + } 106 + 107 + pub fn term(tokens: List(Token)) -> ParserResult { 108 + bin_left(tokens, [scan.Plus, scan.Minus], factor) 109 + } 110 + 111 + pub fn factor(tokens: List(Token)) -> ParserResult { 112 + bin_left(tokens, [scan.Slash, scan.Star], unary) 113 + } 114 + 115 + pub fn unary(tokens: List(Token)) -> Result(#(Expr, List(Token)), ParseError) { 116 + case tokens { 117 + [Token(token_type: scan.Bang, ..) as tok, ..rest] 118 + | [Token(token_type: scan.Minus, ..) as tok, ..rest] -> { 119 + use #(rhs, new_rest) <- result.try(unary(rest)) 120 + Ok(#(ExprUnary(tok, rhs), new_rest)) 121 + } 122 + _ -> primary(tokens) 123 + } 124 + } 125 + 126 + pub fn primary(tokens: List(Token)) -> Result(#(Expr, List(Token)), ParseError) { 127 + case tokens { 128 + [Token(token_type: scan.FalseKw, ..) as tok, ..rest] -> 129 + Ok(#(ExprLit(LitBool(tok)), rest)) 130 + [Token(token_type: scan.TrueKw, ..) as tok, ..rest] -> 131 + Ok(#(ExprLit(LitBool(tok)), rest)) 132 + [Token(token_type: scan.NilKw, ..), ..rest] -> Ok(#(ExprLit(LitNil), rest)) 133 + [Token(token_type: scan.StringTok, ..) as tok, ..rest] -> 134 + Ok(#(ExprLit(LitString(tok)), rest)) 135 + [Token(token_type: scan.NumberTok, ..) as tok, ..rest] -> 136 + Ok(#(ExprLit(LitNumber(tok)), rest)) 137 + [Token(token_type: scan.LeftParen, ..), ..rest] -> { 138 + use #(expr, new_rest) <- result.try(expression(rest)) 139 + case new_rest { 140 + [Token(token_type: scan.RightParen, ..), ..newer_rest] -> 141 + Ok(#(ExprGrouping(expr), newer_rest)) 142 + [tok, ..] -> 143 + Error(ParseError(Some(tok), "Expected ) after expression.")) 144 + // TODO try and exercise 145 + _ -> Error(ParseError(None, "Did not find matching ')' after '('.")) 146 + } 147 + } 148 + [tok, ..] -> Error(ParseError(Some(tok), "Unexpected token")) 149 + _ -> Error(ParseError(None, "Ran out of tokens parsing for primary")) 150 + } 151 + }
+49
src/glox.gleam
··· 1 + import argv 2 + import ast_printer 3 + import expr 4 + import gleam/io 5 + import gleam/string 6 + import input 7 + import scan 8 + import simplifile 9 + 10 + pub fn main() -> Nil { 11 + case argv.load().arguments { 12 + [] -> run_prompt() 13 + [path] -> run_file(path) 14 + _ -> todo 15 + } 16 + 17 + Nil 18 + } 19 + 20 + fn run_file(path: String) -> Nil { 21 + let assert Ok(contents) = simplifile.read(from: path) 22 + run(contents) 23 + } 24 + 25 + fn run_prompt() -> Nil { 26 + case input.input("> ") { 27 + Ok(line) -> { 28 + run(line) 29 + } 30 + _ -> todo 31 + } 32 + 33 + run_prompt() 34 + } 35 + 36 + fn run(lox: String) { 37 + let assert Ok(tokens) = scan.get_tokens(lox) 38 + io.println("Tokens: " <> string.inspect(tokens)) 39 + 40 + let e = expr.parse(tokens) 41 + io.println("Exprs: " <> string.inspect(e)) 42 + 43 + let eval = case e { 44 + Ok(#(e, _)) -> ast_printer.print(e) 45 + Error(e) -> "Error on parsing." <> e.msg 46 + } 47 + io.println(eval) 48 + Nil 49 + }
+262
src/scan.gleam
··· 1 + import gleam/float 2 + import gleam/int 3 + import gleam/list 4 + import gleam/result 5 + import gleam/string 6 + 7 + pub type TokenType { 8 + LeftParen 9 + RightParen 10 + LeftBrace 11 + RightBrace 12 + Comma 13 + Dot 14 + Minus 15 + Plus 16 + Semicolon 17 + Slash 18 + Star 19 + 20 + Bang 21 + BangEqual 22 + Equal 23 + EqualEqual 24 + Greater 25 + GreaterEqual 26 + Less 27 + LessEqual 28 + 29 + Identifier 30 + StringTok 31 + NumberTok 32 + 33 + And 34 + Class 35 + Else 36 + FalseKw 37 + Fun 38 + For 39 + If 40 + NilKw 41 + Or 42 + Print 43 + Return 44 + Super 45 + This 46 + TrueKw 47 + Var 48 + While 49 + 50 + Eof 51 + } 52 + 53 + pub type Literal { 54 + LitString(String) 55 + LitNumber(Float) 56 + LitNone 57 + } 58 + 59 + pub type Token { 60 + Token(token_type: TokenType, lexeme: String, literal: Literal, line: Int) 61 + } 62 + 63 + pub type ScanError { 64 + ScanError(line: Int, where: String, what: String) 65 + } 66 + 67 + pub fn get_tokens(source: String) -> Result(List(Token), ScanError) { 68 + get_tokens_x(0, string.to_graphemes(source)) 69 + } 70 + 71 + fn get_tokens_x( 72 + line: Int, 73 + lexemes: List(String), 74 + ) -> Result(List(Token), ScanError) { 75 + case lexemes { 76 + [] -> Ok([Token(Eof, "EOF", LitNone, line)]) 77 + 78 + ["/", "/", ..rest] -> get_tokens_x(line, advance_to("\n", rest)) 79 + ["!", "=", ..rest] -> emit(Token(BangEqual, "!=", LitNone, line), line, rest) 80 + ["=", "=", ..rest] -> 81 + emit(Token(EqualEqual, "==", LitNone, line), line, rest) 82 + ["<", "=", ..rest] -> emit(Token(LessEqual, "<=", LitNone, line), line, rest) 83 + [">", "=", ..rest] -> 84 + emit(Token(GreaterEqual, ">=", LitNone, line), line, rest) 85 + 86 + [" ", ..rest] -> get_tokens_x(line, rest) 87 + ["\r", ..rest] -> get_tokens_x(line, rest) 88 + ["\t", ..rest] -> get_tokens_x(line, rest) 89 + ["\n", ..rest] -> get_tokens_x(line + 1, rest) 90 + 91 + ["/", ..rest] -> emit(Token(Slash, "/", LitNone, line), line, rest) 92 + ["(", ..rest] -> emit(Token(LeftParen, "(", LitNone, line), line, rest) 93 + [")", ..rest] -> emit(Token(RightParen, ")", LitNone, line), line, rest) 94 + ["{", ..rest] -> emit(Token(LeftBrace, "{", LitNone, line), line, rest) 95 + ["}", ..rest] -> emit(Token(RightBrace, "}", LitNone, line), line, rest) 96 + [",", ..rest] -> emit(Token(Comma, ",", LitNone, line), line, rest) 97 + [".", ..rest] -> emit(Token(Dot, ".", LitNone, line), line, rest) 98 + ["-", ..rest] -> emit(Token(Minus, "-", LitNone, line), line, rest) 99 + ["+", ..rest] -> emit(Token(Plus, "+", LitNone, line), line, rest) 100 + [";", ..rest] -> emit(Token(Semicolon, ";", LitNone, line), line, rest) 101 + ["*", ..rest] -> emit(Token(Star, "*", LitNone, line), line, rest) 102 + ["!", ..rest] -> emit(Token(Bang, "!", LitNone, line), line, rest) 103 + ["=", ..rest] -> emit(Token(Equal, "=", LitNone, line), line, rest) 104 + ["<", ..rest] -> emit(Token(Less, "<", LitNone, line), line, rest) 105 + [">", ..rest] -> emit(Token(Greater, ">", LitNone, line), line, rest) 106 + 107 + ["\"", ..rest] -> 108 + case consume_str(rest) { 109 + Ok(#(s, new_rest)) -> 110 + emit(Token(StringTok, s, LitString(s), line), line, new_rest) 111 + Error(_) -> Error(ScanError(line, "", "Unterminated string.")) 112 + } 113 + 114 + [c, ..rest] -> 115 + case is_ascii_digit(c) { 116 + True -> 117 + case consume_num(line, [c, ..rest]) { 118 + Ok(#(lexeme, val, rest2)) -> 119 + emit( 120 + Token(NumberTok, lexeme, LitNumber(val), line), 121 + line, 122 + rest2, 123 + ) 124 + Error(e) -> Error(e) 125 + } 126 + False -> 127 + case is_lox_alphanumeric(c) { 128 + True -> { 129 + let #(tok, rest2) = consume_identifier(line, [c, ..rest]) 130 + emit(tok, line, rest2) 131 + } 132 + False -> 133 + Error(ScanError(line, "", "Unexpected character: " <> c)) 134 + } 135 + } 136 + } 137 + } 138 + 139 + fn emit( 140 + tok: Token, 141 + line: Int, 142 + rest: List(String), 143 + ) -> Result(List(Token), ScanError) { 144 + use toks <- result.try(get_tokens_x(line, rest)) 145 + Ok([tok, ..toks]) 146 + } 147 + 148 + fn advance_to(c: String, lst: List(String)) -> List(String) { 149 + list.drop_while(lst, fn(x) { x != c }) 150 + } 151 + 152 + fn consume_str(lst: List(String)) -> Result(#(String, List(String)), Nil) { 153 + case list.split_while(lst, fn(x) { x != "\"" }) { 154 + #(before, ["\"", ..after]) -> Ok(#(string.concat(before), after)) 155 + _ -> Error(Nil) 156 + } 157 + } 158 + 159 + fn consume_num_helper( 160 + lexemes: List(String), 161 + ) -> #(List(String), List(String)) { 162 + list.split_while(lexemes, is_ascii_digit) 163 + } 164 + 165 + fn consume_num( 166 + line: Int, 167 + lexemes: List(String), 168 + ) -> Result(#(String, Float, List(String)), ScanError) { 169 + case consume_num_helper(lexemes) { 170 + #(int_chars, [".", d, ..rest_after_dot]) -> 171 + case is_ascii_digit(d) { 172 + False -> 173 + Error(ScanError(line, "", "Digit dot non-digit not accepted")) 174 + True -> { 175 + let #(frac_chars, rest) = 176 + consume_num_helper([d, ..rest_after_dot]) 177 + let num_chars = list.append(int_chars, [".", ..frac_chars]) 178 + let str = string.concat(num_chars) 179 + Ok(#(str, parse_float(str), rest)) 180 + } 181 + } 182 + #(int_chars, rest) -> { 183 + let str = string.concat(int_chars) 184 + Ok(#(str, parse_float(str), rest)) 185 + } 186 + } 187 + } 188 + 189 + fn parse_float(s: String) -> Float { 190 + case float.parse(s) { 191 + Ok(f) -> f 192 + Error(_) -> 193 + case int.parse(s) { 194 + Ok(i) -> int.to_float(i) 195 + Error(_) -> 0.0 196 + } 197 + } 198 + } 199 + 200 + fn keyword_type(s: String) -> Result(TokenType, Nil) { 201 + case s { 202 + "and" -> Ok(And) 203 + "class" -> Ok(Class) 204 + "else" -> Ok(Else) 205 + "false" -> Ok(FalseKw) 206 + "for" -> Ok(For) 207 + "fun" -> Ok(Fun) 208 + "if" -> Ok(If) 209 + "nil" -> Ok(NilKw) 210 + "or" -> Ok(Or) 211 + "print" -> Ok(Print) 212 + "return" -> Ok(Return) 213 + "super" -> Ok(Super) 214 + "this" -> Ok(This) 215 + "true" -> Ok(TrueKw) 216 + "var" -> Ok(Var) 217 + "while" -> Ok(While) 218 + _ -> Error(Nil) 219 + } 220 + } 221 + 222 + fn is_ascii_digit(c: String) -> Bool { 223 + case c { 224 + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" -> True 225 + _ -> False 226 + } 227 + } 228 + 229 + fn is_lox_alphanumeric(c: String) -> Bool { 230 + case c { 231 + "_" -> True 232 + _ -> is_ascii_digit(c) || is_ascii_alpha(c) 233 + } 234 + } 235 + 236 + fn is_ascii_alpha(c: String) -> Bool { 237 + case string.to_utf_codepoints(c) { 238 + [cp] -> { 239 + let i = string.utf_codepoint_to_int(cp) 240 + { i >= 65 && i <= 90 } || { i >= 97 && i <= 122 } 241 + } 242 + _ -> False 243 + } 244 + } 245 + 246 + fn consume_identifier_helper( 247 + lexemes: List(String), 248 + ) -> #(List(String), List(String)) { 249 + list.split_while(lexemes, is_lox_alphanumeric) 250 + } 251 + 252 + fn consume_identifier( 253 + line: Int, 254 + lexemes: List(String), 255 + ) -> #(Token, List(String)) { 256 + let #(identifier, rest) = consume_identifier_helper(lexemes) 257 + let id_str = string.concat(identifier) 258 + case keyword_type(id_str) { 259 + Ok(kt) -> #(Token(kt, id_str, LitNone, line), rest) 260 + Error(_) -> #(Token(Identifier, id_str, LitNone, line), rest) 261 + } 262 + }
+7
test/expr_test.gleam
··· 1 + import expr 2 + import scan 3 + 4 + pub fn genereal_test() { 5 + todo 6 + //expr.parse([scan.NilKw, scan.BangEqual, scan.NilKw]) 7 + }
+13
test/glox_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }