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