diff --git a/lib/rouge/demos/dylan b/lib/rouge/demos/dylan new file mode 100644 index 0000000000..9e4aef2dfa --- /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"); diff --git a/lib/rouge/lexers/dylan.rb b/lib/rouge/lexers/dylan.rb new file mode 100644 index 0000000000..1967db7ade --- /dev/null +++ b/lib/rouge/lexers/dylan.rb @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +module Rouge + module Lexers + class Dylan < RegexLexer + title 'Dylan' + desc 'Dylan Language (https://opendylan.org)' + tag 'dylan' + filenames '*.dylan' + + # Definitions from the Dylan Reference Manual + # 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/\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 %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/[+-]/, Operator + + # Operators and punctuation + 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::Double, :dq + rule %r/'([^\\']|(\\[\\'abefnrt0])|(\\[0-9a-f]+))'/, Str::Char + end + + state :dq do + rule %r/\\[\\'"abefnrt0]/, Str::Escape + rule %r/[^\\"]+/, Str::Double + rule %r/"/, Str::Double, :pop! + end + end + end +end 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 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();