From 6d465602c87303279f53ae75e4291eba93978f2c Mon Sep 17 00:00:00 2001 From: Arclight Automata Date: Fri, 14 Feb 2025 12:23:39 -0600 Subject: [PATCH] Add Picat lexer --- lib/rouge/demos/picat | 13 +++ lib/rouge/lexers/picat.rb | 138 +++++++++++++++++++++++++++++ spec/lexers/picat_spec.rb | 18 ++++ spec/visual/samples/picat | 179 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 348 insertions(+) create mode 100644 lib/rouge/demos/picat create mode 100644 lib/rouge/lexers/picat.rb create mode 100644 spec/lexers/picat_spec.rb create mode 100644 spec/visual/samples/picat diff --git a/lib/rouge/demos/picat b/lib/rouge/demos/picat new file mode 100644 index 0000000000..a5c1a98d1c --- /dev/null +++ b/lib/rouge/demos/picat @@ -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). diff --git a/lib/rouge/lexers/picat.rb b/lib/rouge/lexers/picat.rb new file mode 100644 index 0000000000..26249b8fc2 --- /dev/null +++ b/lib/rouge/lexers/picat.rb @@ -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 diff --git a/spec/lexers/picat_spec.rb b/spec/lexers/picat_spec.rb new file mode 100644 index 0000000000..07b3fecba9 --- /dev/null +++ b/spec/lexers/picat_spec.rb @@ -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 diff --git a/spec/visual/samples/picat b/spec/visual/samples/picat new file mode 100644 index 0000000000..c9676a5a5e --- /dev/null +++ b/spec/visual/samples/picat @@ -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).