rux 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +310 -0
- data/Rakefile +14 -0
- data/bin/ruxc +96 -0
- data/lib/rux.rb +73 -0
- data/lib/rux/ast.rb +9 -0
- data/lib/rux/ast/list_node.rb +15 -0
- data/lib/rux/ast/ruby_node.rb +15 -0
- data/lib/rux/ast/string_node.rb +15 -0
- data/lib/rux/ast/tag_node.rb +17 -0
- data/lib/rux/ast/text_node.rb +17 -0
- data/lib/rux/buffer.rb +15 -0
- data/lib/rux/default_tag_builder.rb +20 -0
- data/lib/rux/default_visitor.rb +67 -0
- data/lib/rux/file.rb +27 -0
- data/lib/rux/lex.rb +9 -0
- data/lib/rux/lex/patterns.rb +41 -0
- data/lib/rux/lex/state.rb +33 -0
- data/lib/rux/lex/states.csv +39 -0
- data/lib/rux/lex/transition.rb +22 -0
- data/lib/rux/lexer.rb +64 -0
- data/lib/rux/parser.rb +244 -0
- data/lib/rux/ruby_lexer.rb +143 -0
- data/lib/rux/rux_lexer.rb +157 -0
- data/lib/rux/utils.rb +15 -0
- data/lib/rux/version.rb +3 -0
- data/lib/rux/visitor.rb +33 -0
- data/rux.gemspec +20 -0
- data/spec/parser_spec.rb +229 -0
- data/spec/spec_helper.rb +6 -0
- metadata +102 -0
data/lib/rux.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
# Racc uses LoadErrors for flow control and in certain ruby versions will
|
4
|
+
# print one along with its stack trace when required. To avoid receiving a
|
5
|
+
# long, arresting, and entirely irrelevant stack trace every time you invoke
|
6
|
+
# rux, temporarily redirect STDOUT here and throw away the output.
|
7
|
+
begin
|
8
|
+
old_stdout = $stdout
|
9
|
+
$stdout = StringIO.new
|
10
|
+
require 'racc/parser'
|
11
|
+
ensure
|
12
|
+
$stdout = old_stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'cgi'
|
16
|
+
require 'parser/current'
|
17
|
+
require 'unparser'
|
18
|
+
|
19
|
+
module Rux
|
20
|
+
autoload :AST, 'rux/ast'
|
21
|
+
autoload :Buffer, 'rux/buffer'
|
22
|
+
autoload :Component, 'rux/component'
|
23
|
+
autoload :DefaultTagBuilder, 'rux/default_tag_builder'
|
24
|
+
autoload :DefaultVisitor, 'rux/default_visitor'
|
25
|
+
autoload :File, 'rux/file'
|
26
|
+
autoload :Lex, 'rux/lex'
|
27
|
+
autoload :Lexer, 'rux/lexer'
|
28
|
+
autoload :Parser, 'rux/parser'
|
29
|
+
autoload :RubyLexer, 'rux/ruby_lexer'
|
30
|
+
autoload :RuxLexer, 'rux/rux_lexer'
|
31
|
+
autoload :Utils, 'rux/utils'
|
32
|
+
autoload :Visitor, 'rux/visitor'
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_accessor :tag_builder, :buffer
|
36
|
+
|
37
|
+
def to_ruby(str, visitor: default_visitor, pretty: true)
|
38
|
+
ruby_code = visitor.visit(Parser.parse(str))
|
39
|
+
return ruby_code unless pretty
|
40
|
+
|
41
|
+
::Unparser.unparse(
|
42
|
+
::Parser::CurrentRuby.parse(ruby_code)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_visitor
|
47
|
+
@default_visitor ||= DefaultVisitor.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_tag_builder
|
51
|
+
@default_tag_builder ||= DefaultTagBuilder.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_buffer
|
55
|
+
@default_buffer ||= Buffer
|
56
|
+
end
|
57
|
+
|
58
|
+
def tag(tag_name, attributes = {}, &block)
|
59
|
+
tag_builder.call(tag_name, attributes, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_buffer
|
63
|
+
buffer.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def library_paths
|
67
|
+
@library_paths ||= []
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
self.tag_builder = self.default_tag_builder
|
72
|
+
self.buffer = self.default_buffer
|
73
|
+
end
|
data/lib/rux/ast.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Rux
|
2
|
+
module AST
|
3
|
+
class TagNode
|
4
|
+
attr_reader :name, :attrs, :children
|
5
|
+
|
6
|
+
def initialize(name, attrs)
|
7
|
+
@name = name
|
8
|
+
@attrs = attrs
|
9
|
+
@children = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def accept(visitor)
|
13
|
+
visitor.visit_tag(self)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rux/buffer.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rux
|
2
|
+
class DefaultTagBuilder
|
3
|
+
def call(tag_name, attributes = {})
|
4
|
+
"<#{tag_name} #{serialize_attrs(attributes)}>" <<
|
5
|
+
(block_given? ? Array(yield) : []).join <<
|
6
|
+
"</#{tag_name}>"
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def serialize_attrs(attributes)
|
12
|
+
''.tap do |result|
|
13
|
+
attributes.each_pair.with_index do |(k, v), idx|
|
14
|
+
result << ' ' unless idx == 0
|
15
|
+
result << "#{k.to_s.gsub('-', '_')}=\"#{CGI.escape_html(v.to_s)}\""
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Rux
|
4
|
+
class DefaultVisitor < Visitor
|
5
|
+
def visit_list(node)
|
6
|
+
node.children.map { |child| visit(child) }.join
|
7
|
+
end
|
8
|
+
|
9
|
+
def visit_ruby(node)
|
10
|
+
node.code
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit_string(node)
|
14
|
+
node.str
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_tag(node)
|
18
|
+
''.tap do |result|
|
19
|
+
block_arg = if (as = node.attrs['as'])
|
20
|
+
visit(as)
|
21
|
+
end
|
22
|
+
|
23
|
+
at = node.attrs.each_with_object([]) do |(k, v), ret|
|
24
|
+
next if k == 'as'
|
25
|
+
ret << Utils.attr_to_hash_elem(k, visit(v))
|
26
|
+
end
|
27
|
+
|
28
|
+
if node.name.start_with?(/[A-Z]/)
|
29
|
+
result << "render(#{node.name}.new"
|
30
|
+
|
31
|
+
unless node.attrs.empty?
|
32
|
+
result << "({ #{at.join(', ')} })"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
result << "Rux.tag('#{node.name}'"
|
36
|
+
|
37
|
+
unless node.attrs.empty?
|
38
|
+
result << ", { #{at.join(', ')} }"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
result << ')'
|
43
|
+
|
44
|
+
if node.children.size > 1
|
45
|
+
result << " { "
|
46
|
+
result << "|#{block_arg}| " if block_arg
|
47
|
+
result << "Rux.create_buffer.tap { |_rux_buf_| "
|
48
|
+
|
49
|
+
node.children.each do |child|
|
50
|
+
result << "_rux_buf_ << #{visit(child).strip};"
|
51
|
+
end
|
52
|
+
|
53
|
+
result << " }.to_s }"
|
54
|
+
elsif node.children.size == 1
|
55
|
+
result << ' { '
|
56
|
+
result << "|#{block_arg}| " if block_arg
|
57
|
+
result << visit(node.children.first).strip
|
58
|
+
result << ' }'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def visit_text(node)
|
64
|
+
"\"#{CGI.escape_html(node.text)}\""
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/rux/file.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rux
|
2
|
+
class File
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_ruby(visitor: Rux.default_visitor, **kwargs)
|
10
|
+
Rux.to_ruby(contents, visitor: visitor, **kwargs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(outfile = nil, **kwargs)
|
14
|
+
::File.write(outfile || default_outfile, to_ruby(**kwargs))
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_outfile
|
18
|
+
@outfile ||= "#{path.chomp('.rux')}.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def contents
|
24
|
+
@contents ||= ::File.read(path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rux/lex.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Rux
|
2
|
+
module Lex
|
3
|
+
class DefaultPattern
|
4
|
+
def matches?(_)
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class CharsetPattern
|
10
|
+
def self.parse(str)
|
11
|
+
pairs = str.scan(/(.)-?(.)?/)
|
12
|
+
|
13
|
+
new(
|
14
|
+
pairs.flat_map do |pair|
|
15
|
+
if pair[1]
|
16
|
+
(pair[0]..pair[1]).to_a
|
17
|
+
else
|
18
|
+
[pair[0]]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :chars
|
25
|
+
|
26
|
+
def initialize(chars)
|
27
|
+
@chars = Set.new(chars)
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(char)
|
31
|
+
chars.include?(char)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NegatedCharsetPattern < CharsetPattern
|
36
|
+
def matches?(char)
|
37
|
+
!chars.include?(char)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rux
|
2
|
+
module Lex
|
3
|
+
class State
|
4
|
+
def self.parse(state_str, transition_strs, inputs)
|
5
|
+
is_terminal = state_str.end_with?('*')
|
6
|
+
state_name = "tRUX_#{state_str.chomp('*').upcase}".to_sym
|
7
|
+
|
8
|
+
transitions = transition_strs.each_with_object([]).with_index do |(ts, ret), idx|
|
9
|
+
ret << Transition.parse(ts, inputs[idx]) if ts
|
10
|
+
end
|
11
|
+
|
12
|
+
new(state_name, is_terminal, transitions)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name, :is_terminal, :transitions
|
16
|
+
|
17
|
+
alias_method :terminal?, :is_terminal
|
18
|
+
|
19
|
+
def initialize(name, is_terminal, transitions)
|
20
|
+
@name = name
|
21
|
+
@is_terminal = is_terminal
|
22
|
+
@transitions = transitions
|
23
|
+
@cache = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](chr)
|
27
|
+
@cache[chr] ||= transitions.find do |trans|
|
28
|
+
trans.matches?(chr)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
,[<],[a-zA-Z0-9_-_---:-:],[>],[/],(space),[=],"[""]",['],"[^""]",[^'],[{],[}],(default)
|
2
|
+
start,tag_open_test,,,,,,,,,,literal_ruby_code_start,,literal_body
|
3
|
+
tag_open_test,,tag_open_start[0],,tag_close_start,,,,,,,,,
|
4
|
+
tag_open_start*,,tag_open_body,,,,,,,,,,,
|
5
|
+
tag_open_body,,tag_open_body,tag_open[0],tag_self_closing[0],tag_open[0],,,,,,,,
|
6
|
+
tag_open*,,,tag_open_end,,attribute_spaces_body,,,,,,,,
|
7
|
+
tag_open_end*,,,,,,,,,,,,,
|
8
|
+
tag_close_start*,,tag_close_body,,,,,,,,,,,
|
9
|
+
tag_close_body,,tag_close_body,tag_close[0],,tag_close[0],,,,,,,,
|
10
|
+
tag_close*,,,tag_close_end,tag_self_closing[0],tag_close_spaces_body,,,,,,,,
|
11
|
+
tag_close_spaces_body,,,tag_close_spaces[0],,tag_close_spaces_body,,,,,,,,
|
12
|
+
tag_close_spaces*,,,tag_close_end,,,,,,,,,,
|
13
|
+
tag_close_end*,,,,,,,,,,,,,
|
14
|
+
tag_self_closing*,,,,tag_self_closing_start,,,,,,,,,
|
15
|
+
tag_self_closing_start,,,tag_self_closing_end,,,,,,,,,,
|
16
|
+
tag_self_closing_end*,,,,,,,,,,,,,
|
17
|
+
,,,,,,,,,,,,,
|
18
|
+
attribute_spaces_body,,attribute_spaces[0],attribute_spaces[0],attribute_spaces[0],attribute_spaces_body,,,,,,,,
|
19
|
+
attribute_spaces*,,attribute_name_body,tag_close_start,tag_self_closing_start,,,,,,,,,
|
20
|
+
attribute_name_body,,attribute_name_body,attribute_name[0],attribute_name[0],attribute_name[0],attribute_name[0],,,,,,,
|
21
|
+
attribute_name*,,,tag_open_end,tag_self_closing_start,attribute_equals_spaces_body,attribute_equals,,,,,,,
|
22
|
+
attribute_equals_spaces_body,,,attribute_equals_spaces[0],,attribute_equals_spaces_body,attribute_equals_spaces[0],,,,,,,attribute_equals_spaces[0]
|
23
|
+
attribute_equals_spaces*,,,tag_open_end,tag_self_closing_start,,attribute_equals,,,,,,,attribute_name_body
|
24
|
+
attribute_equals*,,attribute_uq_body,,,attribute_value_spaces_body[0],,attribute_dq_body,attribute_sq_body,,,attribute_value_ruby_code_start,,
|
25
|
+
attribute_value_spaces_body,,attribute_value_spaces[0],,,attribute_value_spaces_body,,attribute_value_spaces[0],attribute_value_spaces[0],,,attribute_value_spaces[0],,
|
26
|
+
attribute_value_spaces*,,attribute_uq_body,,,,,attribute_dq_body,attribute_sq_body,,,attribute_value_ruby_code_start,,
|
27
|
+
attribute_dq_body,,,,,,,attribute_value,,attribute_dq_body,,,,
|
28
|
+
attribute_sq_body,,,,,,,,attribute_value,,attribute_sq_body,,,
|
29
|
+
attribute_uq_body,,attribute_uq_body,,,attribute_value,,,,,,,,
|
30
|
+
attribute_value_ruby_code_start*,,,,,,,,,,,,,attribute_value_ruby_code
|
31
|
+
attribute_value_ruby_code*,,,,,,,,,,,,attribute_value_ruby_code_end,
|
32
|
+
attribute_value_ruby_code_end*,,attribute_name,tag_open_end,tag_self_closing_start,attribute_spaces_body,,,,,,,,
|
33
|
+
attribute_value*,,attribute_name,tag_open_end,tag_self_closing_start,attribute_spaces_body,,attribute_value_ending,attribute_value_ending,,,,,
|
34
|
+
,,,,,,,,,,,,,
|
35
|
+
literal_body,literal[0],,,,,,,,,,literal[0],,literal_body
|
36
|
+
literal*,,,,,,,,,,,literal_ruby_code_start,,
|
37
|
+
literal_ruby_code_start*,,,,,,,,,,,,,literal_ruby_code[0]
|
38
|
+
literal_ruby_code*,,,,,,,,,,,,literal_ruby_code_end,
|
39
|
+
literal_ruby_code_end*,,,,,,,,,,,,,
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rux
|
2
|
+
module Lex
|
3
|
+
class Transition
|
4
|
+
attr_reader :input, :to_state, :advance_count
|
5
|
+
|
6
|
+
def self.parse(str, input)
|
7
|
+
to_state, advance_count = str.match(/\A(\w+)\[?(-?\d+)?\]?/).captures
|
8
|
+
new(input, :"tRUX_#{to_state.upcase}", (advance_count || 1).to_i)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(input, to_state, advance_count)
|
12
|
+
@input = input
|
13
|
+
@to_state = to_state
|
14
|
+
@advance_count = advance_count
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(chr)
|
18
|
+
input.matches?(chr)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|