Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/rouge/demos/rell
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Define an entity with a key and mutable attribute
entity user {
key name: text;
mutable balance: integer = 0;
}

// Query to find a user by name
query get_user(name: text) {
return user @? { .name == name };
}

// Operation to create a new user
operation create_user(name: text, initial_balance: integer) {
create user(name, balance = initial_balance);
}

// Operation to update user balance
operation update_balance(name: text, amount: integer) {
update user @? { .name == name } ( balance += amount );
}
141 changes: 141 additions & 0 deletions lib/rouge/lexers/rell.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class Rell < RegexLexer
title "Rell"
desc "The Rell programming language (https://docs.chromia.com/rell/rell-intro)"
tag 'rell'
filenames '*.rell'
mimetypes 'text/x-rell'

def self.keywords
@keywords ||= %w(
abstract break class continue create delete else entity enum
false for function guard if import in include index key limit
module mutable namespace null object offset operation override
query record return struct true update val var when while and or not
)
end

def self.builtins
@builtins ||= %w(
big_integer boolean byte_array decimal gtv integer json list
map rowid set text iterable collection unit range tuple virtual
)
end

id = /[a-zA-Z_][a-zA-Z0-9_]*/

state :root do
rule %r/\s+/, Text

# Comments
rule %r(//.*?$), Comment::Single
rule %r(/\*), Comment::Multiline, :comment

# Annotations
rule %r/@#{id}/, Name::Decorator

# At-expressions (special operators)
rule %r/@[*+?]/, Operator
rule %r/@/, Operator

# Byte array literals (must come before identifier matching)
rule %r/x'[0-9a-fA-F]*'/, Str::Other
rule %r/x"[0-9a-fA-F]*"/, Str::Other

# Keywords
rule %r/\b(entity|enum|namespace|object|struct)\b/, Keyword::Declaration
rule %r/\b(function|operation|query)\b/ do
token Keyword::Declaration
push :function_name
end

rule id do |m|
name = m[0]
if self.class.keywords.include?(name)
token Keyword
elsif self.class.builtins.include?(name)
token Keyword::Type
else
token Name
end
end

# String literals
rule %r/'/, Str::Single, :string_single
rule %r/"/, Str::Double, :string_double

# Numeric literals
# Hexadecimal
rule %r/-?0[xX][0-9a-fA-F]+/, Num::Hex

# Integers with exponent and L suffix (bigint)
rule %r/-?\d+[eE][+-]?\d+[lL]/, Num::Integer

# Decimals with exponent (no L suffix)
rule %r/-?\d+[eE][+-]?\d+/, Num::Float

# Decimal with decimal point
rule %r/-?\d*\.\d+(?:[eE][+-]?\d+)?/, Num::Float

# Plain integers (with optional L suffix)
rule %r/-?\d+[lL]?/, Num::Integer

# Operators (long operators first)
rule %r/===|!==/, Operator
rule %r/==|!=|<=|>=/, Operator
rule %r/\+=|-=|\*=|\/=|%=/, Operator
rule %r/\+\+|--/, Operator
rule %r/\?\.|!!|\?:|\?\?/, Operator
rule %r/->/, Operator
rule %r/[+\-*\/%<>=!?]/, Operator

# Special characters
rule %r/\$/, Operator
rule %r/\^/, Operator

# Attribute access
rule %r/(\.)(\s*)(#{id})/ do
groups Punctuation, Text, Name::Attribute
end

# Punctuation
rule %r/[{}()\[\];:,.]/, Punctuation
end

state :function_name do
rule %r/\s+/, Text
rule id, Name::Function, :pop!
rule(//) { pop! }
end

state :comment do
rule %r(/\*), Comment::Multiline, :comment
rule %r(\*/), Comment::Multiline, :pop!
rule %r([^/*]+), Comment::Multiline
rule %r([/*]), Comment::Multiline
end

state :string_double do
rule %r/[^\\"]+/, Str::Double
rule %r/\\[\\'"nrtbf]/, Str::Escape
rule %r/\\u[0-9a-fA-F]{4}/, Str::Escape
rule %r/\\U[0-9a-fA-F]{8}/, Str::Escape
rule %r/\\./, Str::Escape
rule %r/"/, Str::Double, :pop!
end

state :string_single do
rule %r/[^\\']+/, Str::Single
rule %r/\\[\\'"nrtbf]/, Str::Escape
rule %r/\\u[0-9a-fA-F]{4}/, Str::Escape
rule %r/\\U[0-9a-fA-F]{8}/, Str::Escape
rule %r/\\./, Str::Escape
rule %r/'/, Str::Single, :pop!
end
end
end
end
64 changes: 64 additions & 0 deletions spec/lexers/rell_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::Rell do
let(:subject) { Rouge::Lexers::Rell.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.rell'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-rell'
end
end

describe 'lexing' do
include Support::Lexing

it 'recognizes comments' do
assert_tokens_equal '// comment', ['Comment.Single', '// comment']
end

it 'recognizes at-expressions' do
assert_tokens_equal '@', %w[Operator @]
assert_tokens_equal '@?', %w[Operator @?]
assert_tokens_equal '@*', %w[Operator @*]
assert_tokens_equal '@+', %w[Operator @+]
end

it 'recognizes annotations' do
assert_tokens_equal '@log', %w[Name.Decorator @log]
end

it 'recognizes numeric literals' do
assert_tokens_equal '123', %w[Literal.Number.Integer 123]
assert_tokens_equal '3.14', %w[Literal.Number.Float 3.14]
assert_tokens_equal '123L', %w[Literal.Number.Integer 123L]
assert_tokens_equal '-42', %w[Literal.Number.Integer -42]
assert_tokens_equal '1E1', %w[Literal.Number.Float 1E1]
assert_tokens_equal '1e1', %w[Literal.Number.Float 1e1]
assert_tokens_equal '1E+1', %w[Literal.Number.Float 1E+1]
assert_tokens_equal '1E-1', %w[Literal.Number.Float 1E-1]
assert_tokens_equal '1E1L', %w[Literal.Number.Integer 1E1L]
assert_tokens_equal '1e1L', %w[Literal.Number.Integer 1e1L]
end

it 'recognizes byte array literals' do
assert_tokens_equal 'x"deadbeef"', %w[Literal.String.Other x"deadbeef"]
end

it 'highlights function names' do
tokens = subject.lex('function my_func() {}')
assert { tokens.any? { |t, v| t.qualname == 'Name.Function' && v == 'my_func' } }
end

it 'recognizes attribute access' do
tokens = subject.lex('user.name')
assert { tokens.any? { |t, v| t.qualname == 'Name.Attribute' && v == 'name' } }
end
end
end
Loading