antlr4-native 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.
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: []