245 lines
6.9 KiB
Ruby
245 lines
6.9 KiB
Ruby
# -*- coding: utf-8 -*- #
|
|
# frozen_string_literal: true
|
|
|
|
module Rouge
|
|
module Lexers
|
|
class J < RegexLexer
|
|
title 'J'
|
|
desc "The J programming language (jsoftware.com)"
|
|
tag 'j'
|
|
filenames '*.ijs', '*.ijt'
|
|
|
|
# For J-specific terms we use, see:
|
|
# https://code.jsoftware.com/wiki/Vocabulary/AET
|
|
# https://code.jsoftware.com/wiki/Vocabulary/Glossary
|
|
|
|
# https://code.jsoftware.com/wiki/Vocabulary/PartsOfSpeech
|
|
def self.token_map
|
|
@token_map ||= {
|
|
noun: Keyword::Constant,
|
|
verb: Name::Function,
|
|
modifier: Operator,
|
|
name: Name,
|
|
param: Name::Builtin::Pseudo,
|
|
other: Punctuation,
|
|
nil => Error,
|
|
}
|
|
end
|
|
|
|
# https://code.jsoftware.com/wiki/NuVoc
|
|
def self.inflection_list
|
|
@inflection_list ||= ['', '.', ':', '..', '.:', ':.', '::']
|
|
end
|
|
|
|
def self.primitive_table
|
|
@primitive_table ||= Hash.new([:name]).tap do |h|
|
|
{
|
|
'()' => [:other],
|
|
'=' => [:verb, :other, :other],
|
|
'<>+-*%$|,#' => [:verb, :verb, :verb],
|
|
'^' => [:verb, :verb, :modifier],
|
|
'~"' => [:modifier, :verb, :verb],
|
|
'.:@' => [:modifier, :modifier, :modifier],
|
|
';' => [:verb, :modifier, :verb],
|
|
'!' => [:verb, :modifier, :modifier],
|
|
'/\\' => [:modifier, :modifier, :verb],
|
|
'[' => [:verb, nil, :verb],
|
|
']' => [:verb],
|
|
'{' => [:verb, :verb, :verb, nil, nil, nil, :verb],
|
|
'}' => [:modifier, :verb, :verb, nil, nil, nil, :modifier],
|
|
'`' => [:modifier, nil, :modifier],
|
|
'&' => [:modifier, :modifier, :modifier, nil, :modifier],
|
|
'?' => [:verb, :verb],
|
|
'a' => [:name, :noun, :noun],
|
|
'ACeEIjorv' => [:name, :verb],
|
|
'bdfHMT' => [:name, :modifier],
|
|
'Dt' => [:name, :modifier, :modifier],
|
|
'F' => [:name, :modifier, :modifier, :modifier, :modifier,
|
|
:modifier, :modifier],
|
|
'iu' => [:name, :verb, :verb],
|
|
'L' => [:name, :verb, :modifier],
|
|
'mny' => [:param],
|
|
'p' => [:name, :verb, :verb, :verb],
|
|
'qsZ' => [:name, nil, :verb],
|
|
'S' => [:name, nil, :modifier],
|
|
'u' => [:param, :verb, :verb],
|
|
'v' => [:param, :verb],
|
|
'x' => [:param, nil, :verb],
|
|
}.each {|k, v| k.each_char {|c| h[c] = v } }
|
|
end
|
|
end
|
|
|
|
def self.primitive(char, inflection)
|
|
i = inflection_list.index(inflection) or return Error
|
|
token_map[primitive_table[char][i]]
|
|
end
|
|
|
|
def self.control_words
|
|
@control_words ||= Set.new %w(
|
|
assert break case catch catchd catcht continue do else elseif end
|
|
fcase for if return select throw try while whilst
|
|
)
|
|
end
|
|
|
|
def self.control_words_id
|
|
@control_words_id ||= Set.new %w(for goto label)
|
|
end
|
|
|
|
state :expr do
|
|
rule %r/\s+/, Text
|
|
|
|
rule %r'([!-&(-/:-@\[-^`{-~]|[A-Za-z]\b)([.:]*)' do |m|
|
|
token J.primitive(m[1], m[2])
|
|
end
|
|
|
|
rule %r/(?:\d|_\d?):([.:]*)/ do |m|
|
|
token m[1].empty? ? J.token_map[:verb] : Error
|
|
end
|
|
|
|
rule %r/[\d_][\w.]*([.:]*)/ do |m|
|
|
token m[1].empty? ? Num : Error
|
|
end
|
|
|
|
rule %r/'/, Str::Single, :str
|
|
|
|
rule %r/NB\.(?![.:]).*/, Comment::Single
|
|
|
|
rule %r/([A-Za-z]\w*)([.:]*)/ do |m|
|
|
if m[2] == '.'
|
|
word, sep, id = m[1].partition '_'
|
|
list = if sep.empty?
|
|
J.control_words
|
|
elsif not id.empty?
|
|
J.control_words_id
|
|
end
|
|
if list and list.include? word
|
|
token Keyword, word + sep
|
|
token((word == 'for' ? Name : Name::Label), id)
|
|
token Keyword, m[2]
|
|
else
|
|
token Error
|
|
end
|
|
else
|
|
token m[2].empty? ? Name : Error
|
|
end
|
|
end
|
|
end
|
|
|
|
state :str do
|
|
rule %r/''/, Str::Escape
|
|
rule %r/[^'\n]+/, Str::Single
|
|
rule %r/'|$/, Str::Single, :pop!
|
|
end
|
|
|
|
start do
|
|
@note_next = false
|
|
end
|
|
|
|
state :root do
|
|
rule %r/\n/ do
|
|
token Text
|
|
if @note_next
|
|
push :note
|
|
@note_next = false
|
|
end
|
|
end
|
|
|
|
# https://code.jsoftware.com/wiki/Vocabulary/com
|
|
# https://code.jsoftware.com/wiki/Vocabulary/NounExplicitDefinition
|
|
rule %r/
|
|
([0-4]|13|adverb|conjunction|dyad|monad|noun|verb)([\ \t]+)
|
|
(def(?:ine)?\b|:)(?![.:])([\ \t]*)
|
|
/x do |m|
|
|
groups Keyword::Pseudo, Text, Keyword::Pseudo, Text
|
|
@def_body = (m[1] == '0' || m[1] == 'noun') ? :noun : :code
|
|
if m[3] == 'define'
|
|
# stack: [:root]
|
|
# or [:root, ..., :def_next]
|
|
pop! if stack.size > 1
|
|
push @def_body
|
|
push :def_next # [:root, ..., @def_body, :def_next]
|
|
else
|
|
push :expl_def
|
|
end
|
|
end
|
|
|
|
rule %r/^([ \t]*)(Note\b(?![.:]))([ \t\r]*)(?!=[.:]|$)/ do
|
|
groups Text, Name, Text
|
|
@note_next = true
|
|
end
|
|
|
|
rule %r/[mnuvxy]\b(?![.:])/, Name
|
|
mixin :expr
|
|
end
|
|
|
|
state :def_next do
|
|
rule %r/\n/, Text, :pop!
|
|
mixin :root
|
|
end
|
|
|
|
state :expl_def do
|
|
rule %r/0\b(?![.:])/ do
|
|
token Keyword::Pseudo
|
|
# stack: [:root, :expl_def]
|
|
# or [:root, ..., :def_next, :expl_def]
|
|
pop! if stack.size > 2
|
|
goto @def_body
|
|
push :def_next # [:root, ..., @def_body, :def_next]
|
|
end
|
|
rule %r/'/ do
|
|
if @def_body == :noun
|
|
token Str::Single
|
|
goto :str
|
|
else
|
|
token Punctuation
|
|
goto :q_expr
|
|
end
|
|
end
|
|
rule(//) { pop! }
|
|
end
|
|
|
|
# `q_expr` lexes the content of a string literal which is a part of an
|
|
# explicit definition.
|
|
# e.g. dyad def 'x + y'
|
|
state :q_expr do
|
|
rule %r/''/, Str::Single, :q_str
|
|
rule %r/'|$/, Punctuation, :pop!
|
|
rule %r/NB\.(?![.:])([^'\n]|'')*/, Comment::Single
|
|
mixin :expr
|
|
end
|
|
|
|
state :q_str do
|
|
rule %r/''''/, Str::Escape
|
|
rule %r/[^'\n]+/, Str::Single
|
|
rule %r/''/, Str::Single, :pop!
|
|
rule(/'|$/) { token Punctuation; pop! 2 }
|
|
end
|
|
|
|
state :note do
|
|
mixin :delimiter
|
|
rule %r/.+\n?/, Comment::Multiline
|
|
end
|
|
|
|
state :noun do
|
|
mixin :delimiter
|
|
rule %r/.+\n?/, Str::Heredoc
|
|
end
|
|
|
|
state :code do
|
|
mixin :delimiter
|
|
rule %r/^([ \t]*)(:)([ \t\r]*)$/ do
|
|
groups Text, Punctuation, Text
|
|
end
|
|
mixin :expr
|
|
end
|
|
|
|
state :delimiter do
|
|
rule %r/^([ \t]*)(\))([ \t\r]*$\n?)/ do
|
|
groups Text, Punctuation, Text
|
|
pop!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|