A JavaScript lexer and syntax highlighter for Gleam!

Add highlighting tests

+287 -11
+17
birdie_snapshots/highlight_arithmetic_ansi.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_arithmetic_ansi 4 + --- 5 + 6 + let x = 1 + 2 / 3.5e10; 7 + let y = 0n * 0xff8n ** x; 8 + x <= y === false; 9 + 10 + 11 + --- 12 + 13 +  14 + let x = 1 + 2 / 3.5e10; 15 + let y = 0n * 0xff8n ** x; 16 + x <= y === false; 17 + 
+16
birdie_snapshots/highlight_arithmetic_html.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_arithmetic_html 4 + --- 5 + 6 + let x = 1 + 2 / 3.5e10; 7 + let y = 0n * 0xff8n ** x; 8 + x <= y === false; 9 + 10 + 11 + --- 12 + 13 + 14 + <span class=hl-keyword>let</span> <span class=hl-variable>x</span> <span class=hl-operator>=</span> <span class=hl-number>1</span> <span class=hl-operator>+</span> <span class=hl-number>2</span> <span class=hl-operator>/</span> <span class=hl-number>3.5e10</span><span class=hl-punctuation>;</span> 15 + <span class=hl-keyword>let</span> <span class=hl-variable>y</span> <span class=hl-operator>=</span> <span class=hl-number>0n</span> <span class=hl-operator>*</span> <span class=hl-number>0xff8n</span> <span class=hl-operator>**</span> <span class=hl-variable>x</span><span class=hl-punctuation>;</span> 16 + <span class=hl-variable>x</span> <span class=hl-operator>&lt;=</span> <span class=hl-variable>y</span> <span class=hl-operator>===</span> <span class=hl-keyword>false</span><span class=hl-punctuation>;</span>
+23
birdie_snapshots/highlight_basic_program_ansi.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_basic_program_ansi 4 + --- 5 + 6 + export function main() { 7 + const message = 'Hello, world!'; 8 + console.log(message); 9 + } 10 + 11 + main(); 12 + 13 + 14 + --- 15 + 16 +  17 + export function main() { 18 +  const message = 'Hello, world!'; 19 +  console.log(message); 20 + } 21 +  22 + main(); 23 + 
+22
birdie_snapshots/highlight_basic_program_html.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_basic_program_html 4 + --- 5 + 6 + export function main() { 7 + const message = 'Hello, world!'; 8 + console.log(message); 9 + } 10 + 11 + main(); 12 + 13 + 14 + --- 15 + 16 + 17 + <span class=hl-keyword>export</span> <span class=hl-keyword>function</span> <span class=hl-function>main</span><span class=hl-punctuation>(</span><span class=hl-punctuation>)</span> <span class=hl-punctuation>{</span> 18 + <span class=hl-keyword>const</span> <span class=hl-variable>message</span> <span class=hl-operator>=</span> <span class=hl-string>&#39;Hello, world!&#39;</span><span class=hl-punctuation>;</span> 19 + <span class=hl-variable>console</span><span class=hl-punctuation>.</span><span class=hl-function>log</span><span class=hl-punctuation>(</span><span class=hl-variable>message</span><span class=hl-punctuation>)</span><span class=hl-punctuation>;</span> 20 + <span class=hl-punctuation>}</span> 21 + 22 + <span class=hl-function>main</span><span class=hl-punctuation>(</span><span class=hl-punctuation>)</span><span class=hl-punctuation>;</span>
+27
birdie_snapshots/highlight_class_ansi.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_class_ansi 4 + --- 5 + 6 + class RegexWrapper { 7 + constructor(r) { 8 + this.r = r; 9 + } 10 + } 11 + 12 + const rw = new RegexWrapper(/hello [regex]?/); 13 + console.log(rw instanceof RegexWrapper); 14 + 15 + 16 + --- 17 + 18 +  19 + class RegexWrapper { 20 +  constructor(r) { 21 +  this.r = r; 22 +  } 23 + } 24 +  25 + const rw = new RegexWrapper(/hello [regex]?/); 26 + console.log(rw instanceof RegexWrapper); 27 + 
+25
birdie_snapshots/highlight_comments_ansi.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_comments_ansi 4 + --- 5 + #! shebang! 6 + // This is some kind of comment 7 + console.log("Hi"); 8 + /* 9 + Another comment 10 + that spans several lines 11 + */ 12 + let x = /* this on is inline! */ 10; 13 + 14 + 15 + --- 16 + 17 + #! shebang! 18 + // This is some kind of comment 19 + console.log("Hi"); 20 + /* 21 + Another comment 22 + that spans several lines 23 + */ 24 + let x = /* this on is inline! */ 10; 25 + 
+23
birdie_snapshots/highlight_errors_ansi.accepted
··· 1 + --- 2 + version: 1.2.6 3 + title: highlight_errors_ansi 4 + --- 5 + 6 + let unknown = @; 7 + let ustring = 'This string does not finish 8 + let uregex = /uh oh 9 + let badNumber = 0xabcdefg; 10 + /* 11 + This comment spans the rest of the program. 12 + 13 + 14 + --- 15 + 16 +  17 + let unknown = @; 18 + let ustring = 'This string does not finish 19 + let uregex = /uh oh 20 + let badNumber = 0xabcdefg; 21 + /* 22 + This comment spans the rest of the program. 23 + 
+1
gleam.toml
··· 20 20 [dev-dependencies] 21 21 gleeunit = ">= 1.0.0 and < 2.0.0" 22 22 simplifile = ">= 2.2.1 and < 3.0.0" 23 + birdie = ">= 1.2.6 and < 2.0.0"
+11
manifest.toml
··· 2 2 # You typically do not need to edit this file 3 3 4 4 packages = [ 5 + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 + { name = "birdie", version = "1.2.6", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "term_size", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "1363F4C7E7433A4A8350CC682BCDDBA5BBC6F66C94EFC63BC43025F796C4F6D0" }, 7 + { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, 5 8 { name = "filepath", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "65F51013BCF78A603AFFD7992EF1CC6ECA96C74038EB48887F656DE44DBC1902" }, 9 + { name = "glance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "106111453AE9BA959184302B7DADF2E8CF322B27A7CB68EE78F3EE43FEACCE2C" }, 6 10 { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, 7 11 { name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" }, 12 + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, 8 13 { name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, 9 14 { name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" }, 10 15 { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 11 16 { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, 17 + { name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" }, 12 18 { name = "houdini", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "houdini", source = "hex", outer_checksum = "5BA517E5179F132F0471CB314F27FE210A10407387DA1EA4F6FD084F74469FC2" }, 19 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 20 + { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, 13 21 { name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" }, 22 + { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, 23 + { name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" }, 14 24 ] 15 25 16 26 [requirements] 27 + birdie = { version = ">= 1.2.6 and < 2.0.0" } 17 28 gleam_community_ansi = { version = ">= 1.4.3 and < 2.0.0" } 18 29 gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 19 30 gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+1 -1
src/just.gleam
··· 396 396 Error(_) -> 397 397 maybe_token( 398 398 error(lexer, UnterminatedComment), 399 - token.MultiLineComment(lexed), 399 + token.UnterminatedComment(lexed), 400 400 !lexer.ignore_comments, 401 401 ) 402 402 Ok(#(char, source)) ->
+10 -10
src/just/highlight.gleam
··· 40 40 /// 41 41 /// If you wish to use other colours or another format, use `to_tokens`. 42 42 /// 43 - pub fn to_ansi(code: String) -> String { 44 - to_tokens(code) 43 + pub fn ansi(code: String) -> String { 44 + tokens(code) 45 45 |> list.fold("", fn(code, token) { 46 46 code 47 47 <> case token { ··· 96 96 /// 97 97 /// If you wish to use another format see `to_ansi` or `to_tokens`. 98 98 /// 99 - pub fn to_html(code: String) -> String { 100 - to_tokens(code) 99 + pub fn html(code: String) -> String { 100 + tokens(code) 101 101 |> list.fold("", fn(acc, token) { 102 102 case token { 103 103 Whitespace(s) -> acc <> s ··· 132 132 /// 133 133 /// To convert code into syntax tokens, see `just.tokenise`. 134 134 /// 135 - pub fn to_tokens(code: String) -> List(Token) { 135 + pub fn tokens(code: String) -> List(Token) { 136 136 let lexer = just.new(code) 137 137 let #(tokens, _errors) = just.tokenise(lexer) 138 138 do_to_tokens(tokens, []) ··· 340 340 [t.DoublePipeEqual, ..in] -> do_to_tokens(in, [Operator("||="), ..out]) 341 341 [t.DoubleQuestionEqual, ..in] -> do_to_tokens(in, [Operator("??="), ..out]) 342 342 343 - [t.Unknown(value), ..] -> do_to_tokens(in, [Other(value), ..out]) 344 - [t.UnterminatedComment(value), ..] -> 343 + [t.Unknown(value), ..in] -> do_to_tokens(in, [Other(value), ..out]) 344 + [t.UnterminatedComment(value), ..in] -> 345 345 do_to_tokens(in, [Comment("/*" <> value), ..out]) 346 - [t.UnterminatedRegularExpression(value), ..] -> 346 + [t.UnterminatedRegularExpression(value), ..in] -> 347 347 do_to_tokens(in, [Regexp("/" <> value), ..out]) 348 - [t.UnterminatedString(quote:, contents:), ..] -> 348 + [t.UnterminatedString(quote:, contents:), ..in] -> 349 349 do_to_tokens(in, [String(quote <> contents), ..out]) 350 - [t.UnterminatedTemplate(contents), ..] -> 350 + [t.UnterminatedTemplate(contents), ..in] -> 351 351 do_to_tokens(in, [String(contents), ..out]) 352 352 } 353 353 }
+111
test/just/highlight_test.gleam
··· 1 + import birdie 2 + import just/highlight 3 + 4 + fn assert_ansi_highlight(title: String, src: String) -> Nil { 5 + birdie.snap( 6 + src <> "\n\n---\n\n" <> highlight.ansi(src), 7 + "highlight_" <> title <> "_ansi", 8 + ) 9 + } 10 + 11 + fn assert_html_highlight(title: String, src: String) -> Nil { 12 + birdie.snap( 13 + src <> "\n\n---\n\n" <> highlight.html(src), 14 + "highlight_" <> title <> "_html", 15 + ) 16 + } 17 + 18 + pub fn basic_program_test() { 19 + assert_ansi_highlight( 20 + "basic_program", 21 + " 22 + export function main() { 23 + const message = 'Hello, world!'; 24 + console.log(message); 25 + } 26 + 27 + main(); 28 + ", 29 + ) 30 + } 31 + 32 + pub fn basic_program_html_test() { 33 + assert_html_highlight( 34 + "basic_program", 35 + " 36 + export function main() { 37 + const message = 'Hello, world!'; 38 + console.log(message); 39 + } 40 + 41 + main(); 42 + ", 43 + ) 44 + } 45 + 46 + pub fn arithmetic_test() { 47 + assert_ansi_highlight( 48 + "arithmetic", 49 + " 50 + let x = 1 + 2 / 3.5e10; 51 + let y = 0n * 0xff8n ** x; 52 + x <= y === false; 53 + ", 54 + ) 55 + } 56 + 57 + pub fn arithmetic_html_test() { 58 + assert_html_highlight( 59 + "arithmetic", 60 + " 61 + let x = 1 + 2 / 3.5e10; 62 + let y = 0n * 0xff8n ** x; 63 + x <= y === false; 64 + ", 65 + ) 66 + } 67 + 68 + pub fn comments_test() { 69 + assert_ansi_highlight( 70 + "comments", 71 + "#! shebang! 72 + // This is some kind of comment 73 + console.log(\"Hi\"); 74 + /* 75 + Another comment 76 + that spans several lines 77 + */ 78 + let x = /* this on is inline! */ 10; 79 + ", 80 + ) 81 + } 82 + 83 + pub fn class_test() { 84 + assert_ansi_highlight( 85 + "class", 86 + " 87 + class RegexWrapper { 88 + constructor(r) { 89 + this.r = r; 90 + } 91 + } 92 + 93 + const rw = new RegexWrapper(/hello [regex]?/); 94 + console.log(rw instanceof RegexWrapper); 95 + ", 96 + ) 97 + } 98 + 99 + pub fn errors_test() { 100 + assert_ansi_highlight( 101 + "errors", 102 + " 103 + let unknown = @; 104 + let ustring = 'This string does not finish 105 + let uregex = /uh oh 106 + let badNumber = 0xabcdefg; 107 + /* 108 + This comment spans the rest of the program. 109 + ", 110 + ) 111 + }