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 +7 -0
- data/Gemfile +11 -0
- data/Rakefile +3 -0
- data/antlr4-native.gemspec +18 -0
- data/lib/antlr4-native/context.rb +193 -0
- data/lib/antlr4-native/context_method.rb +59 -0
- data/lib/antlr4-native/context_method_arg.rb +27 -0
- data/lib/antlr4-native/generator.rb +416 -0
- data/lib/antlr4-native/string_helpers.rb +21 -0
- data/lib/antlr4-native/version.rb +3 -0
- data/lib/antlr4-native/visitor_generator.rb +69 -0
- data/lib/antlr4-native.rb +8 -0
- metadata +54 -0
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
data/Rakefile
ADDED
@@ -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,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: []
|