Skip to content

Commit

Permalink
Add Picat lexer
Browse files Browse the repository at this point in the history
  • Loading branch information
arclight0 committed Feb 15, 2025
1 parent bf007b7 commit 6d46560
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/rouge/demos/picat
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module main.

import util.

% Calculate factorial using recursion
fact(0) = 1.
fact(N) = N * fact(N-1) => N > 0.

main =>
X = 5,
writef("Factorial of %d is %d\n", X, fact(X)),
Sum = sum([1,2,3]),
writef("Sum = %d\n", Sum).
138 changes: 138 additions & 0 deletions lib/rouge/lexers/picat.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class Picat < RegexLexer
title "Picat"
desc "The Picat programming language (picat-lang.org)"

tag 'picat'
filenames '*.pi'
mimetypes 'text/x-picat'

def self.keywords
@keywords ||= Set.new %w(
module import private table index
if else elseif end foreach while do
not fail true false
catch try in
repeat once var throw
)
end

def self.builtins
@builtins ||= Set.new %w(
append write writeln print println member length
solve solve_all new_array new_map new_set
min max sum prod floor ceiling round
abs sqrt sin cos tan log exp
open close printf get_heap_map get_global_map
get_table_map instance final action solve
)
end

state :root do
rule %r/\s+/m, Text
rule %r/%.*$/, Comment::Single
rule %r/\/\*/, Comment::Multiline, :multiline_comment

# Module declaration
rule %r/(module)(\s+)([a-z][a-zA-Z0-9_]*)/m do
groups Keyword::Namespace, Text::Whitespace, Name::Namespace
end

# Import declaration
rule %r/(import)(\s+)([a-z][a-zA-Z0-9_]*)/m do
groups Keyword::Namespace, Text::Whitespace, Name::Namespace
push :import_list
end

# Handle bare 'import' without immediate module name
rule %r/(import)(\s*$)/ do
groups Keyword::Namespace, Text::Whitespace
push :import_list
end

# Numbers with underscore separators (must come before regular integers)
rule %r/\d+(_\d+)+/, Num::Integer

# Other numbers
rule %r/0[xX][0-9a-fA-F]+/, Num::Hex
rule %r/0[oO][0-7]+/, Num::Oct
rule %r/0[bB][01]+/, Num::Bin
rule %r/[0-9]+\.[0-9]+([eE][+-]?[0-9]+)?/, Num::Float
rule %r/[0-9]+/, Num::Integer

# Strings and Atoms
rule %r/"(\\.|[^"])*"/, Str::Double
rule %r/'(\\.|[^'])*'/, Str::Symbol # Quoted atoms

# Variables
rule %r/[A-Z_][A-Za-z0-9_]*/, Name::Variable

# Keywords and builtins (moved before other identifier patterns)
rule %r/[a-z][a-zA-Z0-9_]*(?=[^a-zA-Z0-9_])/ do |m|
if self.class.keywords.include? m[0]
token Keyword
elsif self.class.builtins.include? m[0]
token Name::Builtin
else
token Name
end
end

# Module-qualified names
rule %r/([a-z][a-zA-Z0-9_]*)(\.)([a-z][a-zA-Z0-9_]*)/ do
groups Name::Namespace, Punctuation, Name::Function
end

# Structure notation
rule %r/\$[a-z][a-zA-Z0-9_]*/, Name::Class

# Other identifiers
rule %r/[a-z][a-zA-Z0-9_]*/, Name

# Import items (commas and periods)
rule %r/,|\./, Punctuation

# Constraint operators
rule %r/#=>|#<=>|#\/\\|#\\\/|#\^|#~|#=|#!=|#<|#=<|#<=|#>|#>=/, Operator

# Term comparison operators
rule %r/@<|@=<|@<=|@>|@>=/, Operator

# DCG notation
rule %r/-->/, Operator

# List comprehension separator
rule %r/\s+:\s+/, Punctuation

# Range notation
rule %r/\.\./, Operator

# List cons operator (|)
rule %r/\|/, Punctuation

# Other operators and punctuation
rule %r/=>|:=|\?=>|==|!=|=<|>=|::|\+\+|--|!|;|:|\.|=|<|>|\+|-|\*|\/|\[|\]|\{|\}|\(|\)|\$|@/, Operator

# Table/index declarations
rule %r/(\+|-)(?=[\s,\)])/, Operator
end

state :multiline_comment do
rule %r/[^*\/]+/m, Comment::Multiline
rule %r/\*\//, Comment::Multiline, :pop!
rule %r/[\*\/]/, Comment::Multiline
end

state :import_list do
rule %r/\s+/m, Text::Whitespace
rule %r/[a-z][a-zA-Z0-9_]*/, Name::Namespace
rule %r/,/, Punctuation
rule %r/\./, Punctuation, :pop!
end
end
end
end
18 changes: 18 additions & 0 deletions spec/lexers/picat_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

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

describe 'guessing' do
include Support::Guessing

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

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-picat'
end
end
end
179 changes: 179 additions & 0 deletions spec/visual/samples/picat
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/* Picat Language Sample
Demonstrating syntax highlighting features */

module example.

import
util,
cp.

% Different number formats
Nums = [
1_000_000, % Underscore separator
0xFFF, % Hexadecimal
0o777, % Octal
0b1010, % Binary
1.23e-4, % Scientific notation
3.14159 % Float
].

% String escape sequences
Strings = [
"Double \"quoted\" string",
"Line 1\nLine 2",
"Tab\tafter",
"Unicode \u0041",
'Single \'quoted\' atom'
].

% Special characters in atoms
atom_1,
'atom-with-dashes',
'atom with spaces',
'atom@with@special@chars'.

% Special operators
X #=> Y, % Constraint implication
X #<=> Y, % Constraint equivalence
X #/\ Y, % Constraint AND
X #\/ Y, % Constraint OR
X #^ Y, % Constraint XOR
#~ X, % Constraint NOT

% Special comparison operators
X @< Y, % Term comparison less than
X @=< Y, % Term comparison less than or equal
X @> Y, % Term comparison greater than
X @>= Y, % Term comparison greater than or equal

% Range notation
List1 = 1..10, % Range with step 1
List2 = 1..2..10, % Range with step 2

% Special built-in predicates
once(Goal),
repeat,
true,
fail,

% Assignment vs unification
X = Y, % Unification
X := Y, % Assignment
X == Y, % Term equality
X !== Y, % Term inequality
X =:= Y, % Arithmetic equality

% Special list/array access
List = [1,2,3,4,5],
First = List[1],
Slice = List[2..4],
Array = {1,2,3,4,5},
AFirst = Array[1],
ASlice = Array[2..4].

% DCG notation (since version 3.0)
sentence --> noun_phrase, verb_phrase.

% Variables and lists
process_list(List) =>
[Head|Tail] = List,
foreach(X in Tail)
if X > Head then
println(X)
elseif X < Head then
println("Less")
else
println("Equal")
end
end.

% Using built-in functions
math_ops =>
A = abs(-42),
B = sqrt(2),
C = sin(3.14159),
D = new_array(3),
Map = new_map(),
Set = new_set([1,2,3]).

% Pattern matching and operators
compare(X, Y) ?=>
X >= Y,
X =< 100,
X != Y,
X :: 1..10.
compare(_, _) => fail.

% Solving example
solve_puzzle =>
Vars = [X,Y,Z],
Vars :: 1..9,
sum(Vars) #= 15,
solve(Vars).

% Functions with returns
factorial(0) = 1.
factorial(N) = F => F = N * factorial(N - 1).

% List comprehensions
S = [X*X : X in 1..20, X mod 2 = 0].

% Index on either first or second argument
index (+,-) (-,+)
edge(a,b).
edge(a,c).
edge(b,c).
edge(c,b).

% Private tabled function
private
table
fibonacci(0) = 1.
fibonacci(1) = 1.
fibonacci(N) = F => F = fibonacci(N-1) + fibonacci(N-2).

% Structures
print_books =>
B1 = $book("Dune", "Frank Herbert"),
println(B1[1]), % Access first element
B2 = new_map([title = "Dune", author = "Frank Herbert"]),
println(B2.get(title)).

% Action rules for event-driven programming
echo(X,Flag), var(Flag), {event(X,T)} =>
writeln(T).
echo(_X,_Flag) =>
writeln(done).

% Exception handling
divide(A, B) =>
catch(
(C = A / B),
error(zero_divisor, _),
println("Division by zero")
).

% Pattern matching with as-patterns (@)
process(L@[H|T]) =>
println(L), % whole list
println(H), % head
println(T). % tail

% Pattern matching with disjunction
match_guard(X) ?=>
X = 1;
X = 2;
X = 3.

% Using the planner module
import planner.
final([n,n,n,n]) => true. % Example goal state
action(S,NextS,Action,Cost) => ... % Define possible actions

% Basic I/O
write_data =>
FD = open("data.txt", write),
foreach(I in 1..10)
printf(FD, "%d\n", I)
end,
close(FD).

0 comments on commit 6d46560

Please # to comment.