Skip to content

Commit

Permalink
LiveScript lexer
Browse files Browse the repository at this point in the history
  • Loading branch information
lkinasiewicz committed Sep 21, 2017
1 parent e43b5f6 commit 2c65cb3
Show file tree
Hide file tree
Showing 5 changed files with 1,696 additions and 0 deletions.
15 changes: 15 additions & 0 deletions lib/rouge/demos/livescript
@@ -0,0 +1,15 @@
mitch =
age: 21
height: 180cm
pets: [\dog, \goldfish]

phile = {}
phile{height, pets} = mitch
phile.height #=> 180
phile.pets #=> ['dog', 'goldfish']

a = [2 7 1 8]
..push 3
..shift!
..sort!
a #=> [1,3,7,8]
1 change: 1 addition & 0 deletions lib/rouge/lexers/coffeescript.rb
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- #
# beware: livescript lexer extends this one so make sure your changes don't break it

module Rouge
module Lexers
Expand Down
218 changes: 218 additions & 0 deletions lib/rouge/lexers/livescript.rb
@@ -0,0 +1,218 @@
# -*- coding: utf-8 -*- #

module Rouge
module Lexers
load_lexer 'coffeescript.rb'
class Livescript < Coffeescript
tag 'livescript'
aliases 'ls'
filenames '*.ls'
mimetypes 'text/livescript'

title 'LiveScript'
desc 'LiveScript, a language which compiles to JavaScript'

def self.detect?(text)
return text.shebang? 'lsc'
end

def self.declarations
@declarations ||= Set.new %w(const var class function let extends implements)
end

def self.keywords
@keywords ||= Set.new %w(
loop until for in of while break return continue switch case
fallthrough default otherwise when then if unless else throw try
catch finally new delete typeof instanceof super by from to til
with require do debugger import export yield
)
end

def self.constants
@constants ||= Set.new %w(
true false yes no on off null NaN Infinity undefined void
)
end

def self.builtins
@builtins ||= Javascript.builtins + %w(this it that arguments)
end

loop_control_keywords = Set.new %w(break continue)
id = /[$a-z_]((-(?=[a-z]))?[a-z0-9_])*/i
int_number = /\d+(_\d+)*/
int = /#{int_number}(e-?#{int_number})?([A-Za-z_][A-Za-z0-9_]*)?/

state :root do
rule(%r(^(?=\s|/))) { push :slash_starts_regex }
mixin :comments
mixin :whitespace

# list of words
rule /(<\[)(.*?)(\]>)/m do
groups Punctuation, Str, Punctuation
end

# function declarations
rule /!\s*function\b/, Keyword::Declaration
rule /!?[-~]>|<[-~]!?/, Keyword::Declaration

# switch arrow
rule /(=>)/, Keyword

# prototype attributes
rule /(::)(#{id})/ do
groups Punctuation, Name::Attribute
push :id
end
rule /(::)(#{int})/ do
groups Punctuation, Num::Integer
push :id
end

# instance attributes
rule /(@)(#{id})/ do
groups Name::Variable::Instance, Name::Attribute
push :id
end
rule /([.])(#{id})/ do
groups Punctuation, Name::Attribute
push :id
end
rule /([.])(\d+)/ do
groups Punctuation, Num::Integer
push :id
end
rule /#{id}(?=\s*:[^:=])/, Name::Attribute
rule /(&)(#{id})?/ do
groups Name::Builtin, Name::Attribute
end

# operators
rule %r(
[+][+]|--|~|&&|\b(and|x?or|is(nt)?|not)\b(?!-[a-zA-Z]|_)|[?]|=|
[|]{2}|\.([|&^]|<<|>>>?)\.|\\(?=\n)|[.:]=|(<<|>>>?|==?|!=?|
<[|]|[|]>|[-<>+*`%^/])=?
)x, Operator, :slash_starts_regex

# switch case
rule /[|]|\bcase(?=\s)/, Keyword, :switch_underscore

rule /@/, Name::Variable::Instance
rule /[.]{3}/, Punctuation
rule /:/, Punctuation

# keywords
rule /#{id}/ do |m|
if loop_control_keywords.include? m[0]
token Keyword
push :loop_control
next
elsif self.class.keywords.include? m[0]
token Keyword
elsif self.class.constants.include? m[0]
token Name::Constant
elsif self.class.builtins.include? m[0]
token Name::Builtin
elsif self.class.declarations.include? m[0]
token Keyword::Declaration
elsif /^[A-Z]/.match(m[0]) && /[^-][a-z]/.match(m[0])
token Name::Class
else
token Name::Variable
end
push :id
end

# punctuation and brackets
rule /\](?=[!?.]|#{id})/, Punctuation, :id
rule /[{(\[;,]/, Punctuation, :slash_starts_regex
rule /[})\].]/, Punctuation

# literals
rule /#{int_number}[.]#{int}/, Num::Float
rule /0x[0-9A-Fa-f]+/, Num::Hex
rule /#{int}/, Num::Integer

# strings
rule /"""/, Str, :tdqs
rule /'''/, Str, :tsqs
rule /"/, Str, :dqs
rule /'/, Str, :sqs

# words
rule /\\\S[^\s,;\])}]*/, Str
end

append :has_interpolation do
rule /(#)(#{id})/ do |m|
groups Str::Interpol, if self.class.builtins.include? m[2] then Name::Builtin else Name::Variable end
end
end

state :whitespace do
# white space and loop labels
rule /(\s+?)(?:^([^\S\n]*)(:#{id}))?/m do
groups Text, Text, Name::Label
end
end

state :whitespace_single_line do
rule %r([^\S\n]+), Text
end

state :multiline_regex_begin do
rule %r(//) do
token Str::Regex
goto :multiline_regex
end
end

state :multiline_regex_end do
rule %r(//([gimy]+\b|\B)), Str::Regex, :pop!
end

state :comments do
rule %r(/\*.*?\*/)m, Comment::Multiline
rule /#.*$/, Comment::Single
end

state :regex_comment do
rule /^#(\s+.*)?$/, Comment::Single
rule /(\s+)(#)(\s+.*)?$/ do
groups Text, Comment::Single, Comment::Single
end
end

state :switch_underscore do
mixin :whitespace_single_line
rule /_(?=\s*=>|\s+then\b)/, Keyword
rule (//) { pop! }
end

state :loop_control do
mixin :whitespace_single_line
rule /#{id}(?=[);\n])/, Name::Label
rule (//) { pop! }
end

state :id do
rule /[!?]|[.](?!=)/, Punctuation
rule /[{]/ do
token Punctuation
push do
rule /[,;]/, Punctuation
rule /#{id}/, Name::Attribute
rule /#{int}/, Num::Integer
mixin :whitespace
rule /[}]/, Punctuation, :pop!
end
end
rule /#{id}/, Name::Attribute
rule /#{int}/, Num::Integer
rule (//) { pop!; push :slash_starts_regex }
end
end
end
end
21 changes: 21 additions & 0 deletions spec/lexers/livescript_spec.rb
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*- #

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

describe 'guessing' do
include Support::Guessing

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

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/livescript'
end

it 'guesses by source' do
assert_guess :source => '#!/usr/bin/env lsc'
end
end
end

0 comments on commit 2c65cb3

Please sign in to comment.