ruby-ll 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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'
@@ -0,0 +1,13 @@
1
+ module LL
2
+ module AST
3
+ ##
4
+ # Class containing details of a single node in an LL grammar AST.
5
+ #
6
+ class Node < ::AST::Node
7
+ ##
8
+ # @return [LL::SourceLine]
9
+ #
10
+ attr_reader :source_line
11
+ end # Node
12
+ end # AST
13
+ end # LL
@@ -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
@@ -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