ruby-ll 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/.yardopts +13 -0
- data/LICENSE +19 -0
- data/README.md +380 -0
- data/bin/ruby-ll +5 -0
- data/doc/DCO.md +25 -0
- data/doc/changelog.md +8 -0
- data/doc/css/common.css +77 -0
- data/ext/c/driver.c +258 -0
- data/ext/c/driver.h +28 -0
- data/ext/c/driver_config.c +209 -0
- data/ext/c/driver_config.h +53 -0
- data/ext/c/extconf.rb +13 -0
- data/ext/c/khash.h +619 -0
- data/ext/c/kvec.h +90 -0
- data/ext/c/libll.c +7 -0
- data/ext/c/libll.h +9 -0
- data/ext/c/macros.h +6 -0
- data/ext/java/Libll.java +12 -0
- data/ext/java/org/libll/Driver.java +247 -0
- data/ext/java/org/libll/DriverConfig.java +193 -0
- data/lib/ll.rb +26 -0
- data/lib/ll/ast/node.rb +13 -0
- data/lib/ll/branch.rb +57 -0
- data/lib/ll/cli.rb +118 -0
- data/lib/ll/code_generator.rb +32 -0
- data/lib/ll/compiled_configuration.rb +35 -0
- data/lib/ll/compiled_grammar.rb +167 -0
- data/lib/ll/configuration_compiler.rb +204 -0
- data/lib/ll/driver.rb +46 -0
- data/lib/ll/driver_config.rb +36 -0
- data/lib/ll/driver_template.erb +51 -0
- data/lib/ll/epsilon.rb +23 -0
- data/lib/ll/erb_context.rb +23 -0
- data/lib/ll/grammar_compiler.rb +359 -0
- data/lib/ll/lexer.rb +582 -0
- data/lib/ll/message.rb +102 -0
- data/lib/ll/parser.rb +280 -0
- data/lib/ll/parser_error.rb +8 -0
- data/lib/ll/rule.rb +53 -0
- data/lib/ll/setup.rb +11 -0
- data/lib/ll/source_line.rb +46 -0
- data/lib/ll/terminal.rb +29 -0
- data/lib/ll/token.rb +30 -0
- data/lib/ll/version.rb +3 -0
- data/ruby-ll.gemspec +47 -0
- metadata +217 -0
data/lib/ll.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'erb'
|
3
|
+
require 'set'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'ast'
|
7
|
+
require 'ansi/code'
|
8
|
+
|
9
|
+
require_relative 'll/setup'
|
10
|
+
require_relative 'll/source_line'
|
11
|
+
require_relative 'll/lexer'
|
12
|
+
require_relative 'll/token'
|
13
|
+
require_relative 'll/parser'
|
14
|
+
require_relative 'll/grammar_compiler'
|
15
|
+
require_relative 'll/code_generator'
|
16
|
+
require_relative 'll/compiled_grammar'
|
17
|
+
require_relative 'll/rule'
|
18
|
+
require_relative 'll/branch'
|
19
|
+
require_relative 'll/terminal'
|
20
|
+
require_relative 'll/epsilon'
|
21
|
+
require_relative 'll/message'
|
22
|
+
require_relative 'll/ast/node'
|
23
|
+
require_relative 'll/erb_context'
|
24
|
+
require_relative 'll/configuration_compiler'
|
25
|
+
require_relative 'll/compiled_configuration'
|
26
|
+
require_relative 'll/cli'
|
data/lib/ll/ast/node.rb
ADDED
data/lib/ll/branch.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module LL
|
2
|
+
##
|
3
|
+
# The Branch class contains information of a single rule branch such as the
|
4
|
+
# steps and the associated callback code.
|
5
|
+
#
|
6
|
+
class Branch
|
7
|
+
attr_reader :steps, :source_line, :ruby_code
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param [Array] steps
|
11
|
+
# @param [LL::SourceLine] source_line
|
12
|
+
# @param [String] ruby_code
|
13
|
+
#
|
14
|
+
def initialize(steps, source_line, ruby_code = nil)
|
15
|
+
@steps = steps
|
16
|
+
@source_line = source_line
|
17
|
+
@ruby_code = ruby_code
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns the FIRST() set of this branch.
|
22
|
+
#
|
23
|
+
# @return [Array<LL::Terminal>]
|
24
|
+
#
|
25
|
+
def first_set
|
26
|
+
first = steps[0]
|
27
|
+
|
28
|
+
return first.is_a?(Rule) ? first.first_set : [first]
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns the FOLLOW() set of this branch.
|
33
|
+
#
|
34
|
+
# @return [Array<LL::Terminal>]
|
35
|
+
#
|
36
|
+
def follow_set
|
37
|
+
follow = steps[1]
|
38
|
+
|
39
|
+
if follow.is_a?(Rule)
|
40
|
+
set = follow.first_set
|
41
|
+
elsif follow
|
42
|
+
set = [follow]
|
43
|
+
else
|
44
|
+
set = []
|
45
|
+
end
|
46
|
+
|
47
|
+
return set
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @return [String]
|
52
|
+
#
|
53
|
+
def inspect
|
54
|
+
return "Branch(steps: #{steps.inspect}, ruby_code: #{ruby_code.inspect})"
|
55
|
+
end
|
56
|
+
end # Branch
|
57
|
+
end # LL
|
data/lib/ll/cli.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module LL
|
2
|
+
##
|
3
|
+
# CLI that can be used to generate ruby-ll parsers from a grammar file.
|
4
|
+
#
|
5
|
+
class CLI
|
6
|
+
##
|
7
|
+
# @param [Array] argv
|
8
|
+
#
|
9
|
+
def run(argv = ARGV)
|
10
|
+
options, leftovers = parse(argv)
|
11
|
+
|
12
|
+
if leftovers.empty?
|
13
|
+
abort <<-EOF.strip
|
14
|
+
Error: you must specify a grammar input file'
|
15
|
+
|
16
|
+
#{parser}
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
|
20
|
+
input = File.expand_path(leftovers[0])
|
21
|
+
|
22
|
+
unless options[:output]
|
23
|
+
options[:output] = output_from_input(input)
|
24
|
+
end
|
25
|
+
|
26
|
+
generate(input, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# @param [String] input
|
31
|
+
# @return [String]
|
32
|
+
#
|
33
|
+
def output_from_input(input)
|
34
|
+
input_ext = File.extname(input)
|
35
|
+
|
36
|
+
return input.gsub(/#{Regexp.compile(input_ext)}$/, '.rb')
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# @param [String] input
|
41
|
+
# @param [Hash] options
|
42
|
+
#
|
43
|
+
def generate(input, options)
|
44
|
+
raw_grammar = File.read(input)
|
45
|
+
parser = Parser.new(raw_grammar, input)
|
46
|
+
gcompiler = GrammarCompiler.new
|
47
|
+
codegen = CodeGenerator.new
|
48
|
+
configcompiler = ConfigurationCompiler.new
|
49
|
+
|
50
|
+
ast = parser.parse
|
51
|
+
cgrammar = gcompiler.compile(ast)
|
52
|
+
|
53
|
+
cgrammar.display_messages
|
54
|
+
|
55
|
+
if cgrammar.valid?
|
56
|
+
config = configcompiler.generate(cgrammar)
|
57
|
+
output = codegen.generate(config, options[:requires])
|
58
|
+
|
59
|
+
File.open(options[:output], 'w') do |file|
|
60
|
+
file.write(output)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# @param [Array] argv
|
69
|
+
# @return [Array]
|
70
|
+
#
|
71
|
+
def parse(argv)
|
72
|
+
options = {
|
73
|
+
:requires => true,
|
74
|
+
:output => nil
|
75
|
+
}
|
76
|
+
|
77
|
+
parser = OptionParser.new do |opt|
|
78
|
+
opt.summary_indent = ' '
|
79
|
+
|
80
|
+
opt.banner = <<-EOF.strip
|
81
|
+
Usage: ruby-ll [INPUT-GRAMMAR] [OPTIONS]
|
82
|
+
|
83
|
+
About:
|
84
|
+
|
85
|
+
Generates a Ruby LL(1) parser from a ruby-ll compatible grammar file.
|
86
|
+
|
87
|
+
Examples:
|
88
|
+
|
89
|
+
ruby-ll lib/ll/parser.rll # output goes to lib/ll/parser.rl
|
90
|
+
ruby-ll lib/ll/parser.rll -o /tmp/parser.rb # output goes to /tmp/parser.rb
|
91
|
+
EOF
|
92
|
+
|
93
|
+
opt.separator "\nOptions:\n\n"
|
94
|
+
|
95
|
+
opt.on '-h', '--help', 'Shows this help message' do
|
96
|
+
abort parser.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
opt.on '--no-requires', 'Disables adding of require calls' do
|
100
|
+
options[:requires] = false
|
101
|
+
end
|
102
|
+
|
103
|
+
opt.on '-o [PATH]', '--output [PATH]', 'Writes output to PATH' do |val|
|
104
|
+
options[:output] = val
|
105
|
+
end
|
106
|
+
|
107
|
+
opt.on '-v', '--version', 'Shows the current version' do
|
108
|
+
puts "ruby-ll #{VERSION} on #{RUBY_DESCRIPTION}"
|
109
|
+
exit
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
leftovers = parser.parse(argv)
|
114
|
+
|
115
|
+
return options, leftovers
|
116
|
+
end
|
117
|
+
end # CLI
|
118
|
+
end # LL
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module LL
|
2
|
+
##
|
3
|
+
# The CodeGenerator class takes a {LL::CompiledConfiguration} instance and
|
4
|
+
# turns it into a block of Ruby source code that can be used as an actual
|
5
|
+
# LL(1) parser.
|
6
|
+
#
|
7
|
+
class CodeGenerator
|
8
|
+
##
|
9
|
+
# The ERB template to use for code generation.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
#
|
13
|
+
TEMPLATE = File.expand_path('../driver_template.erb', __FILE__)
|
14
|
+
|
15
|
+
##
|
16
|
+
# @param [LL::CompiledConfiguration] config
|
17
|
+
# @param [TrueClass|FalseClass] add_requires
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
def generate(config, add_requires = true)
|
21
|
+
context = ERBContext.new(
|
22
|
+
:config => config,
|
23
|
+
:add_requires => add_requires
|
24
|
+
)
|
25
|
+
|
26
|
+
template = File.read(TEMPLATE)
|
27
|
+
erb = ERB.new(template, nil, '-').result(context.get_binding)
|
28
|
+
|
29
|
+
return erb
|
30
|
+
end
|
31
|
+
end # CodeGenerator
|
32
|
+
end # LL
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module LL
|
2
|
+
##
|
3
|
+
# Class for storing the compiled state/lookup/action tables and the likes.
|
4
|
+
#
|
5
|
+
class CompiledConfiguration
|
6
|
+
attr_reader :name, :namespace, :inner, :header, :terminals, :rules, :table,
|
7
|
+
:actions, :action_bodies
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param [Hash] options
|
11
|
+
#
|
12
|
+
# @option options [String] :name
|
13
|
+
# @option options [Array] :namespace
|
14
|
+
# @option options [String] :inner
|
15
|
+
# @option options [String] :header
|
16
|
+
# @option options [Array] :terminals
|
17
|
+
# @option options [Array] :rules
|
18
|
+
# @option options [Array] :table
|
19
|
+
# @option options [Array] :actions
|
20
|
+
# @option options [Hash] :action_bodies
|
21
|
+
#
|
22
|
+
def initialize(options = {})
|
23
|
+
options.each do |key, value|
|
24
|
+
instance_variable_set("@#{key}", value) if respond_to?(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
@namespace ||= []
|
28
|
+
@terminals ||= []
|
29
|
+
@rules ||= []
|
30
|
+
@table ||= []
|
31
|
+
@actions ||= []
|
32
|
+
@action_bodies ||= {}
|
33
|
+
end
|
34
|
+
end # CompiledConfiguration
|
35
|
+
end # LL
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module LL
|
2
|
+
##
|
3
|
+
# The CompiledGrammar class contains compilation results such as the parser
|
4
|
+
# name, the rules of the grammar, the terminals, etc.
|
5
|
+
#
|
6
|
+
class CompiledGrammar
|
7
|
+
attr_accessor :name, :inner, :header
|
8
|
+
|
9
|
+
attr_reader :warnings, :errors
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@warnings = []
|
13
|
+
@errors = []
|
14
|
+
@terminals = {}
|
15
|
+
@rules = {}
|
16
|
+
@inner = nil
|
17
|
+
@header = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# @param [String] message
|
22
|
+
# @param [LL::SourceLine] source_line
|
23
|
+
#
|
24
|
+
def add_error(message, source_line)
|
25
|
+
@errors << Message.new(:error, message, source_line)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @param [String] message
|
30
|
+
# @param [LL::SourceLine] source_line
|
31
|
+
#
|
32
|
+
def add_warning(message, source_line)
|
33
|
+
@warnings << Message.new(:warning, message, source_line)
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param [String] name
|
38
|
+
# @return [TrueClass|FalseClass]
|
39
|
+
#
|
40
|
+
def has_terminal?(name)
|
41
|
+
return @terminals.key?(name)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# @param [String] name
|
46
|
+
# @param [LL::SourceLine] source_line
|
47
|
+
# @return [LL::Terminal]
|
48
|
+
#
|
49
|
+
def add_terminal(name, source_line)
|
50
|
+
return @terminals[name] = Terminal.new(name, source_line)
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns true if a rule for the given name has already been assigned.
|
55
|
+
#
|
56
|
+
# @param [String] name
|
57
|
+
# @return [TrueClass|FalseClass]
|
58
|
+
#
|
59
|
+
def has_rule?(name)
|
60
|
+
return @rules.key?(name)
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Returns true if a rule already exists for a given name _and_ has at least
|
65
|
+
# 1 branch.
|
66
|
+
#
|
67
|
+
# @see [#has_rule?]
|
68
|
+
#
|
69
|
+
def has_rule_with_branches?(name)
|
70
|
+
return has_rule?(name) && !@rules[name].branches.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# @param [LL::Rule] rule
|
75
|
+
# @return [LL::Rule]
|
76
|
+
#
|
77
|
+
def add_rule(rule)
|
78
|
+
return @rules[rule.name] = rule
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# @param [String] name
|
83
|
+
# @return [LL::Rule]
|
84
|
+
#
|
85
|
+
def lookup_rule(name)
|
86
|
+
return @rules[name]
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Looks up an identifier from the list of terminals and/or rules. Rules take
|
91
|
+
# precedence over terminals.
|
92
|
+
#
|
93
|
+
# If no rule/terminal could be found nil is returned instead.
|
94
|
+
#
|
95
|
+
# @param [String] name
|
96
|
+
# @return [LL::Rule|LL::Terminal|NilClass]
|
97
|
+
#
|
98
|
+
def lookup_identifier(name)
|
99
|
+
if has_rule?(name)
|
100
|
+
ident = lookup_rule(name)
|
101
|
+
elsif has_terminal?(name)
|
102
|
+
ident = @terminals[name]
|
103
|
+
else
|
104
|
+
ident = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
return ident
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# @return [Array]
|
112
|
+
#
|
113
|
+
def rules
|
114
|
+
return @rules.values
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# @return [Hash]
|
119
|
+
#
|
120
|
+
def rule_indices
|
121
|
+
return rules.each_with_index.each_with_object({}) do |(rule, idx), h|
|
122
|
+
h[rule] = idx
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# @return [Array]
|
128
|
+
#
|
129
|
+
def terminals
|
130
|
+
return @terminals.values
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# @return [Hash]
|
135
|
+
#
|
136
|
+
def terminal_indices
|
137
|
+
return terminals.each_with_index.each_with_object({}) do |(term, idx), h|
|
138
|
+
h[term] = idx
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# @return [TrueClass|FalseClass]
|
144
|
+
#
|
145
|
+
def valid?
|
146
|
+
return @errors.empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Displays all warnings and errors.
|
151
|
+
#
|
152
|
+
def display_messages
|
153
|
+
[:errors, :warnings].each do |type|
|
154
|
+
send(type).each do |msg|
|
155
|
+
output.puts(msg.to_s)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# @return [IO]
|
162
|
+
#
|
163
|
+
def output
|
164
|
+
return STDERR
|
165
|
+
end
|
166
|
+
end # CompiledGrammar
|
167
|
+
end # LL
|