antlr4-native 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 51499ca3096bdcbe0ce2e2231e9574b170a96acedeebc813f0e2c69134aa04f1
4
+ data.tar.gz: 485568b468bbaf64646344e239d58191e7025238c824e21b639e8641d953b118
5
+ SHA512:
6
+ metadata.gz: 65ccf82166ff334ac080850448093501c6a371a04f2084a820e3a8298af061af50330fda845815173011c66c08a0ad3e8b2b1026b201d5b419c21d82db8d2b4c
7
+ data.tar.gz: fb9a2983e52969af587ccb1e6a401825fade06ed13f437a86f5cb8b6289848ee13c7c9284c13c558e17a5e24a60b7eaecf24854de4465ca7d4ef4bbddd10e0c7
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'pry-byebug'
7
+ end
8
+
9
+ group :development, :test do
10
+ gem 'rake'
11
+ end
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,18 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'antlr4-native/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'antlr4-native'
6
+ s.version = ::Antlr4Native::VERSION
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'http://github.com/camertron/antlr4-native-rb'
10
+
11
+ s.description = s.summary = 'Create a Ruby native extension from any ANTLR4 grammar.'
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+ s.has_rdoc = true
15
+
16
+ s.require_path = 'lib'
17
+ s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'README.md', 'Rakefile', 'antlr4-native.gemspec']
18
+ end
@@ -0,0 +1,193 @@
1
+ module Antlr4Native
2
+ class Context
3
+ include StringHelpers
4
+
5
+ attr_reader :name, :parser_ns, :cpp_parser_source
6
+
7
+ def initialize(name, parser_ns, cpp_parser_source)
8
+ @name = name
9
+ @parser_ns = parser_ns
10
+ @cpp_parser_source = cpp_parser_source
11
+ end
12
+
13
+ def each_context_method
14
+ return to_enum(__method__) unless block_given?
15
+
16
+ mtds.each do |mtd|
17
+ yield mtd if mtd.context_method?
18
+ end
19
+ end
20
+
21
+ def each_token_method
22
+ return to_enum(__method__) unless block_given?
23
+
24
+ mtds.each do |mtd|
25
+ yield mtd if mtd.token_method?
26
+ end
27
+ end
28
+
29
+ def proxy_class_variable
30
+ @proxy_class_variable ||= "rb_c#{name}"
31
+ end
32
+
33
+ def proxy_class_header
34
+ @proxy_class_header ||= begin
35
+ <<~END
36
+ class #{name}Proxy : public ContextProxy {
37
+ public:
38
+ #{name}Proxy(tree::ParseTree* ctx) : ContextProxy(ctx) {};
39
+ #{method_signatures_for(each_context_method)}
40
+ #{method_signatures_for(each_token_method)}
41
+ };
42
+ END
43
+ end
44
+ end
45
+
46
+ def method_signatures_for(mtds)
47
+ mtds
48
+ .map { |mtd| " Object #{mtd.cpp_name}(#{mtd.raw_args});" }
49
+ .join("\n")
50
+ end
51
+
52
+ def conversions
53
+ @class_conversions ||= <<~END
54
+ template <>
55
+ Object to_ruby<#{parser_ns}::#{name}*>(#{parser_ns}::#{name}* const &x) {
56
+ if (!x) return Nil;
57
+ return Data_Object<#{parser_ns}::#{name}>(x, #{proxy_class_variable}, nullptr, nullptr);
58
+ }
59
+
60
+ template <>
61
+ Object to_ruby<#{name}Proxy*>(#{name}Proxy* const &x) {
62
+ if (!x) return Nil;
63
+ return Data_Object<#{name}Proxy>(x, #{proxy_class_variable}, nullptr, nullptr);
64
+ }
65
+ END
66
+ end
67
+
68
+ def proxy_class_methods
69
+ proxy_class_context_methods + proxy_class_token_methods
70
+ end
71
+
72
+ def proxy_class_context_methods
73
+ each_context_method.map do |ctx_method|
74
+ return_type = "#{capitalize(ctx_method.name)}Context"
75
+ return_proxy_type = "#{return_type}Proxy"
76
+ params = ctx_method.args.map(&:name).join(', ')
77
+
78
+ if ctx_method.returns_vector?
79
+ <<~END
80
+ Object #{name}Proxy::#{ctx_method.cpp_name}(#{ctx_method.raw_args}) {
81
+ Array a;
82
+
83
+ if (orig != nullptr) {
84
+ size_t count = ((#{parser_ns}::#{name}*)orig) -> #{ctx_method.name}(#{params}).size();
85
+
86
+ for (size_t i = 0; i < count; i ++) {
87
+ a.push(#{ctx_method.name}At(i));
88
+ }
89
+ }
90
+
91
+ return a;
92
+ }
93
+ END
94
+ else
95
+ <<~END
96
+ Object #{name}Proxy::#{ctx_method.cpp_name}(#{ctx_method.raw_args}) {
97
+ if (orig == nullptr) {
98
+ return Qnil;
99
+ }
100
+
101
+ auto ctx = ((#{parser_ns}::#{name}*)orig) -> #{ctx_method.name}(#{params});
102
+
103
+ if (ctx == nullptr) {
104
+ return Qnil;
105
+ }
106
+
107
+ for (auto child : getChildren()) {
108
+ if (ctx == from_ruby<ContextProxy>(child).getOriginal()) {
109
+ return child;
110
+ }
111
+ }
112
+
113
+ return Nil;
114
+ }
115
+ END
116
+ end
117
+ end
118
+ end
119
+
120
+ def proxy_class_token_methods
121
+ each_token_method.map do |token_mtd|
122
+ params = token_mtd.args.map(&:name).join(', ')
123
+
124
+ if token_mtd.returns_vector?
125
+ <<~END
126
+ Object #{name}Proxy::#{token_mtd.cpp_name}(#{token_mtd.raw_args}) {
127
+ Array a;
128
+
129
+ if (orig == nullptr) {
130
+ return a;
131
+ }
132
+
133
+ auto vec = ((#{parser_ns}::#{name}*)orig) -> #{token_mtd.name}(#{params});
134
+
135
+ for (auto it = vec.begin(); it != vec.end(); it ++) {
136
+ TerminalNodeProxy proxy(*it);
137
+ a.push(proxy);
138
+ }
139
+
140
+ return a;
141
+ }
142
+ END
143
+ else
144
+ <<~END
145
+ Object #{name}Proxy::#{token_mtd.cpp_name}(#{token_mtd.raw_args}) {
146
+ if (orig == nullptr) {
147
+ return Qnil;
148
+ }
149
+
150
+ auto token = ((#{parser_ns}::#{name}*)orig) -> #{token_mtd.name}(#{params});
151
+ TerminalNodeProxy proxy(token);
152
+ return to_ruby(proxy);
153
+ }
154
+ END
155
+ end
156
+ end
157
+ end
158
+
159
+ def class_wrapper(module_var)
160
+ @class_wrapper ||= begin
161
+ lines = [
162
+ "#{proxy_class_variable} = #{module_var}",
163
+ ".define_class<#{name}Proxy, ContextProxy>(\"#{name}\")"
164
+ ]
165
+
166
+ each_context_method do |ctx_method|
167
+ lines << ".define_method(\"#{underscore(ctx_method.cpp_name)}\", &#{name}Proxy::#{ctx_method.cpp_name})"
168
+ end
169
+
170
+ each_token_method do |token_method|
171
+ lines << ".define_method(\"#{token_method.cpp_name}\", &#{name}Proxy::#{token_method.name})"
172
+ end
173
+
174
+ lines[-1] << ';'
175
+
176
+ lines
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def mtds
183
+ @mtds ||= begin
184
+ puts "Finding methods for #{name}"
185
+
186
+ cpp_parser_source
187
+ .scan(/^([^\n]+) #{parser_ns}::#{name}::([^\(]*)\(([^\)]*)\)/).flat_map do |return_type, mtd_name, args|
188
+ ContextMethod.new(mtd_name, args, return_type, self)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,59 @@
1
+ module Antlr4Native
2
+ class ContextMethod
3
+ RULE_METHODS = %w(enterRule exitRule getRuleIndex).freeze
4
+ META_METHODS = %w(accept copyFrom).freeze
5
+
6
+ attr_reader :name, :raw_args, :return_type, :context
7
+
8
+ def initialize(name, raw_args, return_type, context)
9
+ @name = name
10
+ @raw_args = raw_args
11
+ @return_type = return_type
12
+ @context = context
13
+ end
14
+
15
+ def cpp_name
16
+ @cpp_name ||=
17
+ if args.size == 1 && args.first.name == 'i'
18
+ # special case
19
+ "#{name}At"
20
+ else
21
+ [name, *args.map(&:name)].join('_')
22
+ end
23
+ end
24
+
25
+ def args
26
+ @args ||= raw_args.split(',').map do |arg|
27
+ ContextMethodArg.new(arg.strip)
28
+ end
29
+ end
30
+
31
+ def returns_vector?
32
+ return_type.start_with?('std::vector')
33
+ end
34
+
35
+ # @TODO: consider revising this
36
+ def context_method?
37
+ !token_method? &&
38
+ !rule_method? &&
39
+ !meta_method? &&
40
+ !constructor?
41
+ end
42
+
43
+ def token_method?
44
+ name[0].upcase == name[0]
45
+ end
46
+
47
+ def rule_method?
48
+ RULE_METHODS.include?(name)
49
+ end
50
+
51
+ def meta_method?
52
+ META_METHODS.include?(name)
53
+ end
54
+
55
+ def constructor?
56
+ name == context.name
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ module Antlr4Native
2
+ class ContextMethodArg
3
+ attr_reader :raw_arg
4
+
5
+ def initialize(raw_arg)
6
+ @raw_arg = raw_arg
7
+ end
8
+
9
+ def name
10
+ @name ||= parts[1]
11
+ end
12
+
13
+ def type
14
+ @type ||= parts[0].gsub(' ', '')
15
+ end
16
+
17
+ def pointer?
18
+ type.end_with?('*')
19
+ end
20
+
21
+ private
22
+
23
+ def parts
24
+ @parts ||= raw_arg.scan(/([\w\d:]+\s?\*?\s?)/).flatten
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,416 @@
1
+ require 'fileutils'
2
+
3
+ module Antlr4Native
4
+ class Generator
5
+ ANTLR_VERSION = '4.8'.freeze
6
+
7
+ ANTLR_JAR = File.expand_path(
8
+ File.join('..', '..', 'vendor', 'antlr4-4.8-1-complete.jar'), __dir__
9
+ ).freeze
10
+
11
+ include StringHelpers
12
+
13
+ attr_reader :grammar_files, :output_dir, :parser_root_method
14
+
15
+ def initialize(grammar_files:, output_dir:, parser_root_method:)
16
+ @grammar_files = grammar_files
17
+ @output_dir = output_dir
18
+ @parser_root_method = parser_root_method
19
+ end
20
+
21
+ def generate
22
+ generate_antlr_code
23
+ write_interop_file
24
+ end
25
+
26
+ def gem_name
27
+ @gem_name ||= dasherize(parser_ns)
28
+ end
29
+
30
+ def antlr_ns
31
+ grammar_names['parser'] || grammar_names['default']
32
+ end
33
+
34
+ def parser_ns
35
+ @parser_ns ||= grammar_names['parser'] || "#{grammar_names['default']}Parser"
36
+ end
37
+
38
+ def lexer_ns
39
+ @lexer_ns ||= grammar_names['lexer'] || "#{grammar_names['default']}Lexer"
40
+ end
41
+
42
+ def ext_name
43
+ @ext_name ||= underscore(parser_ns)
44
+ end
45
+
46
+ private
47
+
48
+ def generate_antlr_code
49
+ FileUtils.mkdir_p(antlrgen_dir)
50
+
51
+ system(<<~END)
52
+ java -jar #{ANTLR_JAR} \
53
+ -o #{antlrgen_dir} \
54
+ -Dlanguage=Cpp \
55
+ -visitor \
56
+ #{grammar_files.join(' ')}
57
+ END
58
+ end
59
+
60
+ def write_interop_file
61
+ File.write(interop_file, interop_code)
62
+ end
63
+
64
+ def interop_code
65
+ <<~END
66
+ #include "iostream"
67
+
68
+ #include "antlr4-runtime.h"
69
+
70
+ #include "#{parser_ns}.h"
71
+ #include "#{antlr_ns}BaseVisitor.h"
72
+ #include "#{lexer_ns}.h"
73
+
74
+ #include "rice/Array.hpp"
75
+ #include "rice/Class.hpp"
76
+ #include "rice/Constructor.hpp"
77
+ #include "rice/Director.hpp"
78
+
79
+ using namespace std;
80
+ using namespace Rice;
81
+ using namespace antlr4;
82
+
83
+ #{proxy_class_declarations}
84
+
85
+ class ContextProxy {
86
+ public:
87
+ ContextProxy(tree::ParseTree* orig) {
88
+ this -> orig = orig;
89
+ }
90
+
91
+ tree::ParseTree* getOriginal() {
92
+ return orig;
93
+ }
94
+
95
+ std::string getText() {
96
+ return orig -> getText();
97
+ }
98
+
99
+ Array getChildren() {
100
+ if (children == nullptr) {
101
+ children = new Array();
102
+
103
+ if (orig != nullptr) {
104
+ for (auto it = orig -> children.begin(); it != orig -> children.end(); it ++) {
105
+ Object parseTree = ContextProxy::wrapParseTree(*it);
106
+
107
+ if (parseTree != Nil) {
108
+ children -> push(parseTree);
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ return *children;
115
+ }
116
+
117
+ Object getParent() {
118
+ if (parent == Nil) {
119
+ if (orig != nullptr) {
120
+ parent = ContextProxy::wrapParseTree(orig -> parent);
121
+ }
122
+ }
123
+
124
+ return parent;
125
+ }
126
+
127
+ size_t childCount() {
128
+ if (orig == nullptr) {
129
+ return 0;
130
+ }
131
+
132
+ return getChildren().size();
133
+ }
134
+
135
+ bool doubleEquals(Object other) {
136
+ if (other.is_a(rb_cContextProxy)) {
137
+ return from_ruby<ContextProxy*>(other) -> getOriginal() == getOriginal();
138
+ } else {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ private:
144
+
145
+ static Object wrapParseTree(tree::ParseTree* node);
146
+
147
+ protected:
148
+ tree::ParseTree* orig = nullptr;
149
+ Array* children = nullptr;
150
+ Object parent = Nil;
151
+ };
152
+
153
+ class TerminalNodeProxy : public ContextProxy {
154
+ public:
155
+ TerminalNodeProxy(tree::ParseTree* tree) : ContextProxy(tree) { }
156
+ };
157
+
158
+ #{proxy_class_headers}
159
+
160
+ #{conversions}
161
+
162
+ #{proxy_class_methods}
163
+
164
+ #{visitor_generator.visitor_proxy}
165
+
166
+ #{parser_class}
167
+
168
+ #{context_proxy_methods}
169
+
170
+ #{init_function}
171
+ END
172
+ end
173
+
174
+ def proxy_class_headers
175
+ @proxy_class_headers ||= contexts
176
+ .map(&:proxy_class_header)
177
+ .join("\n")
178
+ end
179
+
180
+ def proxy_class_declarations
181
+ @proxy_class_declarations ||= contexts
182
+ .map { |ctx| "Class #{ctx.proxy_class_variable};" }
183
+ .concat([
184
+ 'Class rb_cToken;',
185
+ 'Class rb_cParser;',
186
+ 'Class rb_cParseTree;',
187
+ 'Class rb_cTerminalNode;',
188
+ 'Class rb_cContextProxy;'
189
+ ])
190
+ .join("\n")
191
+ end
192
+
193
+ def conversions
194
+ @conversions ||= begin
195
+ context_conversions = contexts.map(&:conversions).join("\n")
196
+
197
+ <<~END
198
+ template <>
199
+ Object to_ruby<Token*>(Token* const &x) {
200
+ if (!x) return Nil;
201
+ return Data_Object<Token>(x, rb_cToken, nullptr, nullptr);
202
+ }
203
+
204
+ template <>
205
+ Object to_ruby<tree::ParseTree*>(tree::ParseTree* const &x) {
206
+ if (!x) return Nil;
207
+ return Data_Object<tree::ParseTree>(x, rb_cParseTree, nullptr, nullptr);
208
+ }
209
+
210
+ template <>
211
+ Object to_ruby<tree::TerminalNode*>(tree::TerminalNode* const &x) {
212
+ if (!x) return Nil;
213
+ return Data_Object<tree::TerminalNode>(x, rb_cTerminalNode, nullptr, nullptr);
214
+ }
215
+
216
+ #{context_conversions}
217
+ END
218
+ end
219
+ end
220
+
221
+ def proxy_class_methods
222
+ @proxy_class_methods ||= contexts.flat_map(&:proxy_class_methods).join("\n")
223
+ end
224
+
225
+ def parser_class
226
+ @parser_class ||= <<~END
227
+ class ParserProxy {
228
+ public:
229
+ static ParserProxy* parse(string code) {
230
+ auto input = new ANTLRInputStream(code);
231
+ return parseStream(input);
232
+ }
233
+
234
+ static ParserProxy* parseFile(string file) {
235
+ ifstream stream;
236
+ stream.open(file);
237
+
238
+ auto input = new ANTLRInputStream(stream);
239
+ auto parser = parseStream(input);
240
+
241
+ stream.close();
242
+
243
+ return parser;
244
+ }
245
+
246
+ VALUE visit(VisitorProxy* visitor) {
247
+ visitor -> visit(this -> parser -> #{parser_root_method}());
248
+
249
+ // reset for the next visit call
250
+ this -> lexer -> reset();
251
+ this -> parser -> reset();
252
+
253
+ return Qnil;
254
+ }
255
+
256
+ ~ParserProxy() {
257
+ delete this -> parser;
258
+ delete this -> tokens;
259
+ delete this -> lexer;
260
+ delete this -> input;
261
+ }
262
+
263
+ private:
264
+ static ParserProxy* parseStream(ANTLRInputStream* input) {
265
+ ParserProxy* parser = new ParserProxy();
266
+
267
+ parser -> input = input;
268
+ parser -> lexer = new #{lexer_ns}(parser -> input);
269
+ parser -> tokens = new CommonTokenStream(parser -> lexer);
270
+ parser -> parser = new #{parser_ns}(parser -> tokens);
271
+
272
+ return parser;
273
+ }
274
+
275
+ ParserProxy() {};
276
+
277
+ ANTLRInputStream* input;
278
+ #{lexer_ns}* lexer;
279
+ CommonTokenStream* tokens;
280
+ #{parser_ns}* parser;
281
+ };
282
+
283
+ template <>
284
+ Object to_ruby<ParserProxy*>(ParserProxy* const &x) {
285
+ if (!x) return Nil;
286
+ return Data_Object<ParserProxy>(x, rb_cParser, nullptr, nullptr);
287
+ }
288
+ END
289
+ end
290
+
291
+ def init_function
292
+ <<~END
293
+ extern "C"
294
+ void Init_#{ext_name}() {
295
+ Module rb_m#{parser_ns} = define_module("#{parser_ns}");
296
+
297
+ rb_cToken = rb_m#{parser_ns}
298
+ .define_class<Token>("Token")
299
+ .define_method("text", &Token::getText);
300
+
301
+ rb_cParser = rb_m#{parser_ns}
302
+ .define_class<ParserProxy>("Parser")
303
+ .define_singleton_method("parse", &ParserProxy::parse)
304
+ .define_singleton_method("parse_file", &ParserProxy::parseFile)
305
+ .define_method("visit", &ParserProxy::visit);
306
+
307
+ rb_cParseTree = rb_m#{parser_ns}
308
+ .define_class<tree::ParseTree>("ParseTree");
309
+
310
+ rb_cContextProxy = rb_m#{parser_ns}
311
+ .define_class<ContextProxy>("Context")
312
+ .define_method("children", &ContextProxy::getChildren)
313
+ .define_method("child_count", &ContextProxy::childCount)
314
+ .define_method("text", &ContextProxy::getText)
315
+ .define_method("parent", &ContextProxy::getParent)
316
+ .define_method("==", &ContextProxy::doubleEquals);
317
+
318
+ rb_cTerminalNode = rb_mPython3Parser
319
+ .define_class<TerminalNodeProxy, ContextProxy>("TerminalNodeImpl");
320
+
321
+ rb_m#{parser_ns}
322
+ .define_class<#{visitor_generator.cpp_class_name}>("#{visitor_generator.class_name}")
323
+ .define_director<#{visitor_generator.cpp_class_name}>()
324
+ .define_constructor(Constructor<#{visitor_generator.cpp_class_name}, Object>())
325
+ .define_method("visit", &#{visitor_generator.cpp_class_name}::ruby_visit)
326
+ .define_method("visit_children", &#{visitor_generator.cpp_class_name}::ruby_visitChildren)
327
+ #{visitor_generator.visitor_proxy_methods(' ').join("\n")};
328
+
329
+ #{class_wrappers_str(' ')}
330
+ }
331
+ END
332
+ end
333
+
334
+ def context_proxy_methods
335
+ @context_proxy_methods ||= begin
336
+ wrapper_branches = contexts.flat_map.with_index do |context, idx|
337
+ [
338
+ " #{idx == 0 ? 'if' : 'else if'} (antlrcpp::is<#{parser_ns}::#{context.name}*>(node)) {",
339
+ " #{context.name}Proxy proxy((#{parser_ns}::#{context.name}*)node);",
340
+ " return to_ruby(proxy);",
341
+ " }"
342
+ ]
343
+ end
344
+
345
+ <<~END
346
+ Object ContextProxy::wrapParseTree(tree::ParseTree* node) {
347
+ #{wrapper_branches.join("\n")}
348
+ else if (antlrcpp::is<tree::TerminalNodeImpl*>(node)) {
349
+ TerminalNodeProxy proxy(node);
350
+ return to_ruby(proxy);
351
+ } else {
352
+ return Nil;
353
+ }
354
+ }
355
+ END
356
+ end
357
+ end
358
+
359
+ def class_wrappers_str(indent)
360
+ class_wrappers.map do |cw|
361
+ ["#{indent}#{cw[0]}", *cw[1..-1].map { |line| "#{indent} #{line}" }].join("\n")
362
+ end.join("\n\n")
363
+ end
364
+
365
+ def class_wrappers
366
+ @class_wrappers ||= contexts.map do |ctx|
367
+ ctx.class_wrapper("rb_m#{parser_ns}")
368
+ end
369
+ end
370
+
371
+ def contexts
372
+ @contexts ||= cpp_parser_source
373
+ .scan(/#{parser_ns}::([^\s:\(\)]+Context)/)
374
+ .flatten
375
+ .uniq
376
+ .reject { |c| c == '_sharedContext' }
377
+ .map { |name| Context.new(name, parser_ns, cpp_parser_source) }
378
+ end
379
+
380
+ def visitor_methods
381
+ @visitor_methods ||= cpp_visitor_source
382
+ .scan(/visit[A-Z][^\(\s]*/)
383
+ .flatten
384
+ .uniq
385
+ end
386
+
387
+ def visitor_generator
388
+ @visitor_generator ||= VisitorGenerator.new(visitor_methods, antlr_ns, parser_ns)
389
+ end
390
+
391
+ def antlrgen_dir
392
+ @antlrgen_dir ||= File.join(output_dir, gem_name, 'antlrgen')
393
+ end
394
+
395
+ def interop_file
396
+ @interop_file ||= File.join(output_dir, gem_name, "#{ext_name}.cpp")
397
+ end
398
+
399
+ def grammar_names
400
+ @grammar_names ||= begin
401
+ grammar_files.each_with_object({}) do |grammar_file, ret|
402
+ kind, name = File.read(grammar_file).scan(/^(parser|lexer)?\s*grammar\s*([^;]+);/).flatten
403
+ ret[kind&.strip || 'default'] = name
404
+ end
405
+ end
406
+ end
407
+
408
+ def cpp_parser_source
409
+ @cpp_parser_source ||= File.read(File.join(antlrgen_dir, "#{parser_ns}.cpp"))
410
+ end
411
+
412
+ def cpp_visitor_source
413
+ @cpp_visitor_source ||= File.read(File.join(antlrgen_dir, "#{antlr_ns}BaseVisitor.h"))
414
+ end
415
+ end
416
+ end
@@ -0,0 +1,21 @@
1
+ module Antlr4Native
2
+ module StringHelpers
3
+ def capitalize(str)
4
+ str.sub(/\A(.)/) { $1.upcase }
5
+ end
6
+
7
+ def underscore(str)
8
+ str
9
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
10
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
11
+ .gsub('-', '_')
12
+ .downcase
13
+ end
14
+
15
+ def dasherize(str)
16
+ underscore(str).gsub('_', '-')
17
+ end
18
+ end
19
+
20
+ StringHelpers.extend(StringHelpers)
21
+ end
@@ -0,0 +1,3 @@
1
+ module Antlr4Native
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,69 @@
1
+ module Antlr4Native
2
+ class VisitorGenerator
3
+ VISITOR_METHOD_BLACKLIST = %w(visit visitChildren).freeze
4
+
5
+ include StringHelpers
6
+
7
+ attr_reader :visitor_methods, :antlr_ns, :parser_ns
8
+
9
+ def initialize(visitor_methods, antlr_ns, parser_ns)
10
+ @visitor_methods = visitor_methods
11
+ @antlr_ns = antlr_ns
12
+ @parser_ns = parser_ns
13
+ end
14
+
15
+ def class_name
16
+ @class_name ||= 'Visitor'
17
+ end
18
+
19
+ def cpp_class_name
20
+ @cpp_class_name ||= 'VisitorProxy'
21
+ end
22
+
23
+ def each_visitor_method
24
+ return to_enum(__method__) unless block_given?
25
+
26
+ visitor_methods.each do |visitor_method|
27
+ yield visitor_method unless VISITOR_METHOD_BLACKLIST.include?(visitor_method)
28
+ end
29
+ end
30
+
31
+ def visitor_proxy
32
+ vms = each_visitor_method.flat_map do |visitor_method|
33
+ context = "#{capitalize(visitor_method.sub(/\Avisit/, ''))}Context"
34
+
35
+ [
36
+ " virtual antlrcpp::Any #{visitor_method}(#{parser_ns}::#{context} *ctx) override {",
37
+ " #{context}Proxy proxy(ctx);",
38
+ " return getSelf().call(\"#{underscore(visitor_method)}\", &proxy);",
39
+ " }\n"
40
+ ]
41
+ end
42
+
43
+ <<~END
44
+ class #{cpp_class_name} : public #{antlr_ns}BaseVisitor, public Director {
45
+ public:
46
+ #{cpp_class_name}(Object self) : Director(self) { }
47
+
48
+ Object ruby_visit(ContextProxy* proxy) {
49
+ visit(proxy -> getOriginal());
50
+ return Nil;
51
+ }
52
+
53
+ Object ruby_visitChildren(ContextProxy* proxy) {
54
+ visitChildren(proxy -> getOriginal());
55
+ return Nil;
56
+ }
57
+
58
+ #{vms.join("\n")}
59
+ };
60
+ END
61
+ end
62
+
63
+ def visitor_proxy_methods(indent)
64
+ @visitor_proxy_methods ||= each_visitor_method.map do |visitor_method|
65
+ "#{indent}.define_method(\"#{underscore(visitor_method)}\", &#{cpp_class_name}::ruby_visitChildren)"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,8 @@
1
+ module Antlr4Native
2
+ autoload :Context, 'antlr4-native/context'
3
+ autoload :ContextMethod, 'antlr4-native/context_method'
4
+ autoload :ContextMethodArg, 'antlr4-native/context_method_arg'
5
+ autoload :Generator, 'antlr4-native/generator'
6
+ autoload :StringHelpers, 'antlr4-native/string_helpers'
7
+ autoload :VisitorGenerator, 'antlr4-native/visitor_generator'
8
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: antlr4-native
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Create a Ruby native extension from any ANTLR4 grammar.
14
+ email:
15
+ - camertron@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - Rakefile
22
+ - antlr4-native.gemspec
23
+ - lib/antlr4-native.rb
24
+ - lib/antlr4-native/context.rb
25
+ - lib/antlr4-native/context_method.rb
26
+ - lib/antlr4-native/context_method_arg.rb
27
+ - lib/antlr4-native/generator.rb
28
+ - lib/antlr4-native/string_helpers.rb
29
+ - lib/antlr4-native/version.rb
30
+ - lib/antlr4-native/visitor_generator.rb
31
+ homepage: http://github.com/camertron/antlr4-native-rb
32
+ licenses: []
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.7.6.2
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Create a Ruby native extension from any ANTLR4 grammar.
54
+ test_files: []