From 2f57a079c8d2c408f77b44a36774a77c39a5ed6a Mon Sep 17 00:00:00 2001 From: bad-antics <160459796+bad-antics@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:28:45 -0700 Subject: [PATCH] Add Lateralus lexer Adds a lexer for the Lateralus programming language, a statically typed, pipeline-oriented language with algebraic data types, pattern matching, and first-class effect capabilities. - File extension: .ltl - Aliases: ltl - Homepage: https://lateralus.dev Includes demo file, visual sample, and an RSpec spec covering filename and mimetype guessing, the pipeline operator, typed integer/float literals, keywords, builtin types, decorators, and comment styles. --- lib/rouge/demos/lateralus | 22 +++++ lib/rouge/lexers/lateralus.rb | 152 ++++++++++++++++++++++++++++++++++ spec/lexers/lateralus_spec.rb | 66 +++++++++++++++ spec/visual/samples/lateralus | 22 +++++ 4 files changed, 262 insertions(+) create mode 100644 lib/rouge/demos/lateralus create mode 100644 lib/rouge/lexers/lateralus.rb create mode 100644 spec/lexers/lateralus_spec.rb create mode 100644 spec/visual/samples/lateralus diff --git a/lib/rouge/demos/lateralus b/lib/rouge/demos/lateralus new file mode 100644 index 0000000000..5668ec051b --- /dev/null +++ b/lib/rouge/demos/lateralus @@ -0,0 +1,22 @@ +// examples/fibonacci.ltl - Fibonacci demonstration + +module examples::fibonacci + +import std::io + +fn fib(n: int) -> int { + if n <= 0 { return 0 } + if n == 1 { return 1 } + return fib(n - 1) + fib(n - 2) +} + +fn main() { + io::println("Fibonacci sequence:") + let nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + let results = nums |> map(fib) + for (i, r) in results |> enumerate() { + io::println("fib({i}) = {r}") + } +} + +main() diff --git a/lib/rouge/lexers/lateralus.rb b/lib/rouge/lexers/lateralus.rb new file mode 100644 index 0000000000..380324e705 --- /dev/null +++ b/lib/rouge/lexers/lateralus.rb @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +module Rouge + module Lexers + class Lateralus < RegexLexer + tag 'lateralus' + aliases 'ltl' + filenames '*.ltl' + mimetypes 'text/x-lateralus' + + title 'Lateralus' + desc 'The Lateralus programming language (lateralus.dev)' + + def self.keywords + @keywords ||= %w( + fn let mut match if else elif while for in return break continue + import export module pub priv struct enum impl trait where type + const static async await spawn guard defer use as self Self super + yield do + ) + end + + def self.builtin_types + @builtin_types ||= %w( + int i8 i16 i32 i64 i128 + uint u8 u16 u32 u64 u128 + float f32 f64 + bool str char bytes any never + list map set tuple Option Result Some None Ok Err + ) + end + + def self.builtins + @builtins ||= %w( + print println eprint eprintln format panic assert assert_eq + todo unimplemented unreachable + len range map filter reduce fold zip enumerate + sort sorted reverse sum min max + ) + end + + def self.constants + @constants ||= %w(true false null) + end + + id = /[A-Za-z_][A-Za-z0-9_]*/ + type_id = /[A-Z][A-Za-z0-9_]*/ + hex = /[0-9a-fA-F]/ + int_suf = /(?:_?[iu](?:8|16|32|64|128))?/ + flt_suf = /(?:_?f(?:32|64))?/ + + state :whitespace do + rule %r/\s+/, Text + rule %r(///[^\n]*), Comment::Special + rule %r(//[^\n]*), Comment::Single + rule %r(/\*), Comment::Multiline, :block_comment + end + + state :block_comment do + rule %r([^*/]+), Comment::Multiline + rule %r(/\*), Comment::Multiline, :block_comment + rule %r(\*/), Comment::Multiline, :pop! + rule %r([/*]), Comment::Multiline + end + + state :strings do + # raw strings: r"...", r#"..."#, r##"..."##, ... + rule %r/r(#*)"(?:\\.|(?!\1").)*"\1/m, Str + # byte strings + rule %r/b"/, Str, :string + # regular / interpolated strings + rule %r/"/, Str, :string + # char literals + rule %r/'(?:\\(?:[nrt'"\\0]|x#{hex}{2}|u\{#{hex}{1,6}\})|[^'\\])'/, Str::Char + end + + state :string do + rule %r/"/, Str, :pop! + rule %r/\\(?:[nrt'"\\0]|x#{hex}{2}|u\{#{hex}{1,6}\})/, Str::Escape + rule %r/\{[^}]*\}/, Str::Interpol + rule %r/[^"\\{}]+/, Str + rule %r/[\\{}]/, Str + end + + state :attribute do + rule %r/[^\[\]]+/, Name::Decorator + rule %r/\[/, Name::Decorator, :attribute + rule %r/\]/, Name::Decorator, :pop! + end + + state :root do + mixin :whitespace + mixin :strings + + # Attribute decorators: @memo, @doc("..."), @foreign("c") + rule %r/@#{id}/, Name::Decorator + # Capability annotations: #[caps(io, net)] + rule %r/\#\[/, Name::Decorator, :attribute + + # Numeric literals + rule %r/[0-9][0-9_]*\.[0-9][0-9_]*(?:[eE][-+]?[0-9][0-9_]*)?#{flt_suf}/, + Num::Float + rule %r/0x#{hex}[#{hex}_]*#{int_suf}/, Num::Hex + rule %r/0o[0-7][0-7_]*#{int_suf}/, Num::Oct + rule %r/0b[01][01_]*#{int_suf}/, Num::Bin + rule %r/[0-9][0-9_]*#{int_suf}/, Num::Integer + + # Pipeline operator (Lateralus's signature feature) + rule %r/\|>/, Operator + + # Function / type / struct / enum / trait / impl definitions + rule %r/(fn)(\s+)(#{id})/ do + groups Keyword, Text, Name::Function + end + rule %r/(struct|enum|trait|type|impl)(\s+)(#{type_id})/ do + groups Keyword, Text, Name::Class + end + + # Module path segment: `foo::` + rule %r/(#{id})(::)/ do + groups Name::Namespace, Punctuation + end + + # Identifiers with semantic lookup + rule id do |m| + name = m[0] + if self.class.keywords.include?(name) + token Keyword + elsif self.class.builtin_types.include?(name) + token Keyword::Type + elsif self.class.constants.include?(name) + token Keyword::Constant + elsif self.class.builtins.include?(name) + token Name::Builtin + elsif name =~ /\A[A-Z]/ + token Name::Class + else + token Name + end + end + + # Operators + rule %r/==|!=|<=|>=|->|=>|&&|\|\||<<|>>|::|\.\.=?|\?\?|[+\-*\/%<>!=&|^~?]/, + Operator + + # Punctuation + rule %r/[{}()\[\];,.:]/, Punctuation + end + end + end +end diff --git a/spec/lexers/lateralus_spec.rb b/spec/lexers/lateralus_spec.rb new file mode 100644 index 0000000000..35d2278435 --- /dev/null +++ b/spec/lexers/lateralus_spec.rb @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Lexers::Lateralus do + let(:subject) { Rouge::Lexers::Lateralus.new } + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'foo.ltl' + end + + it 'guesses by mimetype' do + assert_guess :mimetype => 'text/x-lateralus' + end + end + + describe 'lexing' do + include Support::Lexing + + it 'lexes the pipeline operator' do + assert_tokens_equal 'x |> f', + ['Name', 'x'], + ['Text', ' '], + ['Operator', '|>'], + ['Text', ' '], + ['Name', 'f'] + end + + it 'lexes typed integers' do + assert_tokens_equal '42u64', ['Literal.Number.Integer', '42u64'] + assert_tokens_equal '0xFF_i32', ['Literal.Number.Hex', '0xFF_i32'] + assert_tokens_equal '0b1010u8', ['Literal.Number.Bin', '0b1010u8'] + end + + it 'lexes typed floats' do + assert_tokens_equal '3.14_f32', ['Literal.Number.Float', '3.14_f32'] + end + + it 'recognizes keywords and types' do + assert_tokens_equal 'fn', ['Keyword', 'fn'] + assert_tokens_equal 'let', ['Keyword', 'let'] + assert_tokens_equal 'int', ['Keyword.Type', 'int'] + assert_tokens_equal 'str', ['Keyword.Type', 'str'] + end + + it 'recognizes decorators' do + assert_tokens_equal '@memo', ['Name.Decorator', '@memo'] + end + + it 'treats UpperCamelCase as a class name' do + assert_tokens_equal 'Some', ['Keyword.Type', 'Some'] + assert_tokens_equal 'MyType', ['Name.Class', 'MyType'] + end + + it 'highlights builtin functions' do + assert_tokens_equal 'println', ['Name.Builtin', 'println'] + end + + it 'handles line and doc comments' do + assert_tokens_equal '// hi', ['Comment.Single', '// hi'] + assert_tokens_equal '/// doc', ['Comment.Special', '/// doc'] + end + end +end diff --git a/spec/visual/samples/lateralus b/spec/visual/samples/lateralus new file mode 100644 index 0000000000..5668ec051b --- /dev/null +++ b/spec/visual/samples/lateralus @@ -0,0 +1,22 @@ +// examples/fibonacci.ltl - Fibonacci demonstration + +module examples::fibonacci + +import std::io + +fn fib(n: int) -> int { + if n <= 0 { return 0 } + if n == 1 { return 1 } + return fib(n - 1) + fib(n - 2) +} + +fn main() { + io::println("Fibonacci sequence:") + let nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + let results = nums |> map(fib) + for (i, r) in results |> enumerate() { + io::println("fib({i}) = {r}") + } +} + +main()