From 143ae185be839ad1ec29a57b2e502fa6308e25a1 Mon Sep 17 00:00:00 2001 From: Peter Hull Date: Wed, 19 May 2021 14:58:32 +0100 Subject: [PATCH 1/6] Initial check-in of Dylan language --- lib/rouge/demos/dylan | 8 +++++ lib/rouge/lexers/dylan.rb | 69 +++++++++++++++++++++++++++++++++++++++ spec/visual/samples/dylan | 51 +++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 lib/rouge/demos/dylan create mode 100644 lib/rouge/lexers/dylan.rb create mode 100644 spec/visual/samples/dylan diff --git a/lib/rouge/demos/dylan b/lib/rouge/demos/dylan new file mode 100644 index 0000000000..06fb771c4d --- /dev/null +++ b/lib/rouge/demos/dylan @@ -0,0 +1,8 @@ +// Various keywords and constants +define method demo(x :: , #key y = "demo") => () + let z = #xFEE8 + #o277; + let w = '\n'; + format-out("%d %s %d %c", x + 100, y, z, w); +end method; + +demo(99, y: "hello"); \ No newline at end of file diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb new file mode 100644 index 0000000000..01a604a55e --- /dev/null +++ b/lib/rouge/lexers/dylan.rb @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +module Rouge + module Lexers + class Dylan < RegexLexer + title 'Dylan' + desc 'Dylan Language' + tag 'dylan' + filenames '*.dylan' + # Definitions from the Dylan Reference Manual + core_word=%w(define end handler let local macro otherwise) + begin_word=%w(begin block case for if method) + function_word=[] + define_body_word=%w(class library method module) + define_list_word=%w(constant variable domain) + reserved_word=core_word+begin_word+function_word+define_body_word+define_list_word + punctuation=%w'( ) , . ; [ ] { } :: - = == => #( #[ ## ? ?? ?= ... ' + hash_word=%w(#t #f #next #rest #key #all-keys #include) + graphic_character=%w(! & * < > | ^ $ % @ _) + special_character=%w(- + ~ ? / =) + binary_operator=%w(+ - * / ^ = == ~= ~== < <= > >= & | :=) + unary_operator=%w(- ~) + binary_integer=%r(#b[01]+) + octal_integer=%r(#o[0-7]+) + decimal_integer=%r([+-]?[0-9]+) + hex_integer=%r(#x[0-9a-f]+)i + ratio=%r([+-]?[0-9]+/[0-9]+) + floating_point=%r([+-]?([0-9]*\.[0-9]+(E[+-]?[0-9]+)?|[0-9]+\.[0-9]*(E[+-]?[0-9]+)?|[0-9]+E[+-]?[0-9]+))i + word_character=/[a-z0-9#{Regexp.escape((graphic_character+special_character).join(''))}]/i + initial_word_character=/[\\a-z0-9#{Regexp.escape(graphic_character.join(''))}]/i + word=%r(#{initial_word_character}#{word_character}*)i + punc = Regexp.new(punctuation.map {|s| Regexp.escape(s)}.join("|")) + oper = Regexp.new((binary_operator+unary_operator).map {|s| Regexp.escape(s)}.join("|")) + # Words in < > brackets are class names but only by convention + def angle?(matched) + /^<.+>$/ =~ matched[0] + end + state :root do + # Comments + rule %r(//.*?$), Comment::Single + rule %r(/\*.*?\*/)m, Comment::Multiline + # Keywords + rule %r/(#{reserved_word.join('|')})\b/, Keyword + rule %r/(#{hash_word.join('|')})\b/, Keyword::Constant + # Numbers + rule ratio, Literal::Number::Other + rule floating_point, Literal::Number::Float + rule binary_integer, Literal::Number::Bin + rule octal_integer, Literal::Number::Oct + rule decimal_integer, Literal::Number::Integer + rule hex_integer, Literal::Number::Hex + # Names + rule %r/[-\w\d\.]+:/, Name::Tag + rule word do |w| + token angle?(w) ? Name::Class : Name + end + # Operators and punctuation + rule punc, Operator + rule oper, Operator + rule %r/:/, Operator # For 'constrained names' + # Strings, characters and whitespace + rule %r/"(\\\\|\\"|[^"])*"/, Str + rule %r/'([^\\']|(\\[\\'abefnrt0])|(\\[0-9a-f]+))'/, Str::Char + rule %r/\s+/, Text::Whitespace + end + end + end +end diff --git a/spec/visual/samples/dylan b/spec/visual/samples/dylan new file mode 100644 index 0000000000..2ba41b416d --- /dev/null +++ b/spec/visual/samples/dylan @@ -0,0 +1,51 @@ +Module: Factorial +Synopsis: Factorial "application." +Author: Steve Rowley +Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. + All rights reserved. +License: See License.txt in this distribution for details. +Warranty: Distributed WITHOUT WARRANTY OF ANY KIND + +/// +/// The canonical recursive function. +/// + +define function factorial (n :: ) => (n! :: ) + case + n < 0 => error("Can't take factorial of negative integer: %d\n", n); + n = 0 => 1; + otherwise => n * factorial(n - 1); + end +end; + +define function factorial-top-level () => () + let arguments = application-arguments(); + if (arguments.size == 0) + format-out("Usage: %s number ...\n", application-name()); + else + format-out("$maximum-integer = %d\n", $maximum-integer); + format-out("$minimum-integer = %d\n", $minimum-integer); + for (i from 0 below size(arguments)) + let arg = arguments[i]; + block (loop-continue) + let n = block () + string-to-integer(arg); + exception () + format-out("Skipping invalid argument %=.\n", arg); + loop-continue(); + end block; + let n! = 0; + profiling (cpu-time-seconds, cpu-time-microseconds) + n! := factorial(n) + results + format-out("factorial(%d) = %d (in %d.%s seconds)\n", n, n!, + cpu-time-seconds, integer-to-string(cpu-time-microseconds, size: 6)); + end profiling; + exception (e :: ) + format-out("factorial(%s) - Error: %s\n", arg, e); + end block; + end for; + end if; +end function factorial-top-level; + +factorial-top-level(); From 8be8946bee4ed05392cd1730d5bdab11cd36b994 Mon Sep 17 00:00:00 2001 From: Peter Hull Date: Fri, 16 Dec 2022 22:14:17 +0000 Subject: [PATCH 2/6] Add newline to end to fix linter error --- lib/rouge/demos/dylan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/demos/dylan b/lib/rouge/demos/dylan index 06fb771c4d..9e4aef2dfa 100644 --- a/lib/rouge/demos/dylan +++ b/lib/rouge/demos/dylan @@ -5,4 +5,4 @@ define method demo(x :: , #key y = "demo") => () format-out("%d %s %d %c", x + 100, y, z, w); end method; -demo(99, y: "hello"); \ No newline at end of file +demo(99, y: "hello"); From 399bedaea343cbb639d9693b6ead3e14733c5488 Mon Sep 17 00:00:00 2001 From: jneen Date: Mon, 9 Mar 2026 18:40:50 -0400 Subject: [PATCH 3/6] clean up the Dylan lexer --- lib/rouge/lexers/dylan.rb | 125 +++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb index 01a604a55e..f5e883f4c8 100644 --- a/lib/rouge/lexers/dylan.rb +++ b/lib/rouge/lexers/dylan.rb @@ -8,61 +8,100 @@ class Dylan < RegexLexer desc 'Dylan Language' tag 'dylan' filenames '*.dylan' + # Definitions from the Dylan Reference Manual - core_word=%w(define end handler let local macro otherwise) - begin_word=%w(begin block case for if method) - function_word=[] - define_body_word=%w(class library method module) - define_list_word=%w(constant variable domain) - reserved_word=core_word+begin_word+function_word+define_body_word+define_list_word - punctuation=%w'( ) , . ; [ ] { } :: - = == => #( #[ ## ? ?? ?= ... ' - hash_word=%w(#t #f #next #rest #key #all-keys #include) - graphic_character=%w(! & * < > | ^ $ % @ _) - special_character=%w(- + ~ ? / =) - binary_operator=%w(+ - * / ^ = == ~= ~== < <= > >= & | :=) - unary_operator=%w(- ~) - binary_integer=%r(#b[01]+) - octal_integer=%r(#o[0-7]+) - decimal_integer=%r([+-]?[0-9]+) - hex_integer=%r(#x[0-9a-f]+)i - ratio=%r([+-]?[0-9]+/[0-9]+) - floating_point=%r([+-]?([0-9]*\.[0-9]+(E[+-]?[0-9]+)?|[0-9]+\.[0-9]*(E[+-]?[0-9]+)?|[0-9]+E[+-]?[0-9]+))i - word_character=/[a-z0-9#{Regexp.escape((graphic_character+special_character).join(''))}]/i - initial_word_character=/[\\a-z0-9#{Regexp.escape(graphic_character.join(''))}]/i - word=%r(#{initial_word_character}#{word_character}*)i - punc = Regexp.new(punctuation.map {|s| Regexp.escape(s)}.join("|")) - oper = Regexp.new((binary_operator+unary_operator).map {|s| Regexp.escape(s)}.join("|")) - # Words in < > brackets are class names but only by convention - def angle?(matched) - /^<.+>$/ =~ matched[0] - end + # see: + # https://opendylan.org/books/drm/Modules + # https://opendylan.org/books/drm/Conditional_Execution + # https://opendylan.org/books/drm/Statement_Macros + reserved_words = Set.new %w( + begin block case class constant create define domain else + end exception for function generic handler if let library local + macro method module otherwise select unless until variable while + ) + + hash_word = Set.new %w(#t #f #next #rest #key #all-keys #include) + operators = Set.new %w(+ - * / ^ = == ~ ~= ~== < <= > >= & | :=) + state :root do + rule %r/^[\w-]+:/, Comment::Preproc, :header + rule %r/\s+/, Text::Whitespace + rule(%r//) { goto :main } + end + + # see https://opendylan.org/books/drm/Dylan_Interchange_Format + state :header do + rule(/.*?$/) { token Comment; goto :header_value } + end + + state :header_value do + # line continuations are defined as any line that starts with whitespace + rule %r/^[ \t]+.*?$/, Comment + rule %r/\n+/, Comment + rule(//) { pop! } + end + + state :main do # Comments rule %r(//.*?$), Comment::Single rule %r(/\*.*?\*/)m, Comment::Multiline + rule %r/\s+/, Text::Whitespace + # Keywords - rule %r/(#{reserved_word.join('|')})\b/, Keyword - rule %r/(#{hash_word.join('|')})\b/, Keyword::Constant + rule %r/\w+/ do |m| + if reserved_words.include?(m[0]) + token Keyword + elsif hash_word.include?(m[0]) + token Keyword::Constant + else + fallthrough! + end + end + + rule %r/#(t|f|next|rest|key|all-keys|include)\b/, Keyword::Constant + # Numbers - rule ratio, Literal::Number::Other - rule floating_point, Literal::Number::Float - rule binary_integer, Literal::Number::Bin - rule octal_integer, Literal::Number::Oct - rule decimal_integer, Literal::Number::Integer - rule hex_integer, Literal::Number::Hex + rule %r([+-]?\d+/\d+), Literal::Number::Other + rule %r/[+-]?\d*[.]\d+(?:e[+-]?\d+)?/i, Literal::Number::Float + rule %r/[+-]?\d+[.]\d*(?:e[+-]?\d+)?/i, Literal::Number::Float + rule %r/[+-]\d+(?:e[+-]?\d+)?/i, Literal::Number::Float + rule %r/#b[01]+/, Literal::Number::Bin + rule %r/#o[0-7]+/, Literal::Number::Oct + rule %r/[+-]?[0-9]+/, Literal::Number::Integer + rule %r/#x[0-9a-f]+/i, Literal::Number::Hex + # Names - rule %r/[-\w\d\.]+:/, Name::Tag - rule word do |w| - token angle?(w) ? Name::Class : Name - end + rule %r/[-\w\d\.]+:/, Name::Tag + rule %r/[-]/, Operator + # Operators and punctuation - rule punc, Operator - rule oper, Operator + rule %r/::|=>|#[(\[#]|[.][.][.]|[(),.;\[\]{}=?]/, Punctuation + + rule %r([\w!&*<>|^\$%@][\w!&*<>|^\$%@=/?~+-]*) do |m| + word = m[0] + if operators.include?(m[0]) + token Operator + elsif word.start_with?('<') && word.end_with?('>') + token Name::Class + elsif word.start_with?('*') && word.end_with?('*') + token Name::Variable::Instance + elsif word.start_with?('$') + token Name::Constant + else + token Name + end + end + rule %r/:/, Operator # For 'constrained names' # Strings, characters and whitespace - rule %r/"(\\\\|\\"|[^"])*"/, Str + rule %r/"/, Str::Double, :dq rule %r/'([^\\']|(\\[\\'abefnrt0])|(\\[0-9a-f]+))'/, Str::Char - rule %r/\s+/, Text::Whitespace + end + + state :dq do + rule %r/\\[\\'"abefnrt0]/, Str::Escape + rule %r/[^\\"]+/, Str::Double + rule %r/"/, Str::Double, :pop! end end end From f31990a8358bb5ff4eafd77b4f01b0c747a39358 Mon Sep 17 00:00:00 2001 From: jneen Date: Mon, 9 Mar 2026 18:47:02 -0400 Subject: [PATCH 4/6] remove unneeded Name::Tag declaration --- lib/rouge/lexers/dylan.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb index f5e883f4c8..8412d9266d 100644 --- a/lib/rouge/lexers/dylan.rb +++ b/lib/rouge/lexers/dylan.rb @@ -24,7 +24,7 @@ class Dylan < RegexLexer operators = Set.new %w(+ - * / ^ = == ~ ~= ~== < <= > >= & | :=) state :root do - rule %r/^[\w-]+:/, Comment::Preproc, :header + rule %r/^[\w.-]+:/, Comment::Preproc, :header rule %r/\s+/, Text::Whitespace rule(%r//) { goto :main } end @@ -71,7 +71,6 @@ class Dylan < RegexLexer rule %r/#x[0-9a-f]+/i, Literal::Number::Hex # Names - rule %r/[-\w\d\.]+:/, Name::Tag rule %r/[-]/, Operator # Operators and punctuation From e481c75ad6a6b098b3bedef722495989d3cbc0e6 Mon Sep 17 00:00:00 2001 From: jneen Date: Mon, 9 Mar 2026 18:49:59 -0400 Subject: [PATCH 5/6] lex unary + as an operator --- lib/rouge/lexers/dylan.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb index 8412d9266d..8036d534e7 100644 --- a/lib/rouge/lexers/dylan.rb +++ b/lib/rouge/lexers/dylan.rb @@ -71,7 +71,7 @@ class Dylan < RegexLexer rule %r/#x[0-9a-f]+/i, Literal::Number::Hex # Names - rule %r/[-]/, Operator + rule %r/[+-]/, Operator # Operators and punctuation rule %r/::|=>|#[(\[#]|[.][.][.]|[(),.;\[\]{}=?]/, Punctuation From c9efb8507062374623310007d717ba7bfa7eefce Mon Sep 17 00:00:00 2001 From: jneen Date: Mon, 9 Mar 2026 18:52:26 -0400 Subject: [PATCH 6/6] add a spec and a url to the description --- lib/rouge/lexers/dylan.rb | 2 +- spec/lexers/dylan_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 spec/lexers/dylan_spec.rb diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb index 8036d534e7..1967db7ade 100644 --- a/lib/rouge/lexers/dylan.rb +++ b/lib/rouge/lexers/dylan.rb @@ -5,7 +5,7 @@ module Rouge module Lexers class Dylan < RegexLexer title 'Dylan' - desc 'Dylan Language' + desc 'Dylan Language (https://opendylan.org)' tag 'dylan' filenames '*.dylan' diff --git a/spec/lexers/dylan_spec.rb b/spec/lexers/dylan_spec.rb new file mode 100644 index 0000000000..ce2ad1a198 --- /dev/null +++ b/spec/lexers/dylan_spec.rb @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Lexers::Dylan do + let(:subject) { Rouge::Lexers::Dylan.new } + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'myfile.dylan' + end + end +end