brotorift 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/brotorift +12 -0
- data/lib/ast.rb +169 -0
- data/lib/brotorift.rb +102 -0
- data/lib/case_helper.rb +13 -0
- data/lib/compiler.rb +288 -0
- data/lib/compiler_error.rb +191 -0
- data/lib/generators/elixir_server_generator.ex.erb +162 -0
- data/lib/generators/elixir_server_generator.rb +435 -0
- data/lib/generators/scala_server_generator.rb +137 -0
- data/lib/generators/scala_server_generator.scala.erb +92 -0
- data/lib/generators/unity_client_generator.cs.erb +103 -0
- data/lib/generators/unity_client_generator.rb +188 -0
- data/lib/lexer.rb +42 -0
- data/lib/parser.rb +105 -0
- data/lib/runtime.rb +345 -0
- data/lib/sequence_diagram_generator.rb +47 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ffeb983c09de3b7835c52798bfe30d658f6f035bd337a000051cbba4a5e0bc31
|
4
|
+
data.tar.gz: 6b963b0f2f00c5d167780b5f2c1d3a4ec63e8aa51b05e98cbd386a15678ce03e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ad9317a9da0207ebcbf9caa079447968115f87e59e71158c815a7f126e6e20f5369465b5ea2c3ff160c690b412f1ecd9184c99a26d91040acaff1f005f78807c
|
7
|
+
data.tar.gz: 595c9295e383cde70247d1f4c06f207a8b81b4ec08bbbf130a7d9456198edca8831c0304c25eb5672de88d5fc25eb4bf1355e3a9f427a5c9a65ea7725216c09f
|
data/bin/brotorift
ADDED
data/lib/ast.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'rltk/ast'
|
2
|
+
require_relative 'lexer'
|
3
|
+
|
4
|
+
|
5
|
+
module RLTK
|
6
|
+
class StreamPosition
|
7
|
+
def to_s
|
8
|
+
"#{file_name}(#{line_number})"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
class Position
|
15
|
+
attr_reader :start, :stop
|
16
|
+
|
17
|
+
def initialize start, stop
|
18
|
+
raise "Filename should be the same." if start.file_name != stop.file_name
|
19
|
+
@start = start
|
20
|
+
@stop = stop
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@start.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class ASTNode < RLTK::ASTNode
|
30
|
+
value :position, Position
|
31
|
+
|
32
|
+
def doc_str
|
33
|
+
if doc == ''
|
34
|
+
return ''
|
35
|
+
else
|
36
|
+
return " # #{doc}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TopDecl < ASTNode
|
42
|
+
end
|
43
|
+
|
44
|
+
class IncludeDecl < TopDecl
|
45
|
+
value :filename, String
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"include '#{filename}'"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class NodeDecl < TopDecl
|
53
|
+
value :name, String
|
54
|
+
value :language, String
|
55
|
+
value :nickname, String
|
56
|
+
value :namespace, String
|
57
|
+
value :doc, String
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
nickname_str = ''
|
61
|
+
nickname_str = " as #{nickname}" if nickname != name
|
62
|
+
namespace_str = ''
|
63
|
+
namespace_str = " namespace #{namespace}" if namespace != ''
|
64
|
+
"node #{language} #{name}#{nickname_str}#{namespace_str}#{doc_str}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TypeDecl < ASTNode
|
69
|
+
value :name, String
|
70
|
+
child :params, [TypeDecl]
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
params_str = ''
|
74
|
+
if params.length > 0
|
75
|
+
params_inner_str = params.map { |p| p.to_s } .join ','
|
76
|
+
params_str = '<' + params_inner_str + '>'
|
77
|
+
end
|
78
|
+
"#{name}#{params_str}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class MemberDecl < ASTNode
|
83
|
+
value :type, TypeDecl
|
84
|
+
value :name, String
|
85
|
+
value :doc, String
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
"#{type} #{name}#{doc_str}\n"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class StructDecl < TopDecl
|
93
|
+
value :name, String
|
94
|
+
value :doc, String
|
95
|
+
child :members, [MemberDecl]
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
members_str = members.join ''
|
99
|
+
"struct #{name}#{doc_str}\n#{members_str}end"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class EnumElementDecl < ASTNode
|
104
|
+
value :name, String
|
105
|
+
value :value, Integer
|
106
|
+
value :doc, String
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
"#{name} = #{value}#{doc_str}\n"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class EnumDecl < TopDecl
|
114
|
+
value :name, String
|
115
|
+
value :doc, String
|
116
|
+
child :elements, [EnumElementDecl]
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
elements_str = elements.join ''
|
120
|
+
"enum #{name}#{doc_str}\n#{elements_str}end"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class MessageDecl < ASTNode
|
125
|
+
value :name, String
|
126
|
+
value :doc, String
|
127
|
+
child :members, [MemberDecl]
|
128
|
+
|
129
|
+
def to_s
|
130
|
+
members_str = members.join ''
|
131
|
+
"message #{name}#{doc_str}\n#{members_str}end\n"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class DirectionDecl < TopDecl
|
136
|
+
value :client, String
|
137
|
+
value :direction, Symbol
|
138
|
+
value :server, String
|
139
|
+
value :doc, String
|
140
|
+
child :messages, [MessageDecl]
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
messages_str = messages.join ''
|
144
|
+
"direction #{client} #{direction} #{server}#{doc_str}\n#{messages_str}end"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class StepDecl < ASTNode
|
149
|
+
value :client, String
|
150
|
+
value :direction, Symbol
|
151
|
+
value :server, String
|
152
|
+
value :message, String
|
153
|
+
value :doc, String
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
"#{client} #{direction} #{server}: #{message}#{doc_str}\n"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class SequenceDecl < TopDecl
|
161
|
+
value :name, String
|
162
|
+
value :doc, String
|
163
|
+
child :steps, [StepDecl]
|
164
|
+
|
165
|
+
def to_s
|
166
|
+
steps_str = steps.join ''
|
167
|
+
"sequence #{name}#{doc_str}\n#{steps_str}end"
|
168
|
+
end
|
169
|
+
end
|
data/lib/brotorift.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require_relative 'compiler'
|
3
|
+
require_relative 'sequence_diagram_generator'
|
4
|
+
|
5
|
+
|
6
|
+
class Generator
|
7
|
+
attr_reader :language, :side
|
8
|
+
|
9
|
+
@@generators = []
|
10
|
+
|
11
|
+
def initialize language, side
|
12
|
+
@language = language
|
13
|
+
@side = side
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add generator
|
17
|
+
@@generators.push generator
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.generators
|
21
|
+
@@generators
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
class Brotorift
|
27
|
+
def initialize
|
28
|
+
@generated_nodes = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_generators
|
32
|
+
root_folder = File.expand_path File.dirname __FILE__
|
33
|
+
generators_pattern = root_folder + '/generators/*.rb'
|
34
|
+
Dir.glob generators_pattern do |generator_file|
|
35
|
+
require generator_file
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def print_compile_errors errors
|
41
|
+
errors.each do |e|
|
42
|
+
puts e
|
43
|
+
end
|
44
|
+
puts 'Your brotorift files contain errors. Please fix them before go on.'.red
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def generate_all_code runtime
|
49
|
+
runtime.directions.each do |direction|
|
50
|
+
generate_code runtime, direction.client, :client
|
51
|
+
generate_code runtime, direction.server, :server
|
52
|
+
end
|
53
|
+
|
54
|
+
generate_sequence_diagrams runtime
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def generate_sequence_diagrams runtime
|
59
|
+
begin
|
60
|
+
Dir.mkdir 'docs'
|
61
|
+
Dir.mkdir 'docs/diagrams'
|
62
|
+
rescue
|
63
|
+
end
|
64
|
+
SequenceDiagramGenerator.generate runtime
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def generate_code runtime, node, side
|
69
|
+
old_sides = @generated_nodes[node.name]
|
70
|
+
return if old_sides != nil and old_sides.include? side
|
71
|
+
|
72
|
+
generator = find_generator node.language, side
|
73
|
+
if generator == nil
|
74
|
+
puts "Generator for language '#{node.language}' and side #{side} is not found.".red
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
puts "Generating #{node.language} #{side} code for node '#{node.name}'..."
|
79
|
+
generator.generate node, runtime
|
80
|
+
if old_sides != nil
|
81
|
+
old_sides.push side
|
82
|
+
else
|
83
|
+
@generated_nodes[node.name] = [side]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_generator language, side
|
88
|
+
Generator.generators.find { |g| g.language == language and g.side == side }
|
89
|
+
end
|
90
|
+
|
91
|
+
def run file_name
|
92
|
+
load_generators
|
93
|
+
compiler = Compiler.new
|
94
|
+
compiler.compile file_name
|
95
|
+
if compiler.errors.length > 0
|
96
|
+
print_compile_errors compiler.errors
|
97
|
+
exit 1
|
98
|
+
else
|
99
|
+
generate_all_code compiler.runtime
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/case_helper.rb
ADDED
data/lib/compiler.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
require_relative 'parser'
|
2
|
+
require_relative 'compiler_error'
|
3
|
+
require_relative 'runtime'
|
4
|
+
|
5
|
+
|
6
|
+
class String
|
7
|
+
def char_case
|
8
|
+
if self == self.upcase
|
9
|
+
return :upper
|
10
|
+
else
|
11
|
+
return :lower
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class Compiler
|
18
|
+
attr_reader :errors, :runtime
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@errors = []
|
22
|
+
@message_base_id = 0
|
23
|
+
@message_id = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def compile filename
|
27
|
+
@runtime = self.compile_file filename
|
28
|
+
end
|
29
|
+
|
30
|
+
def compile_file filename
|
31
|
+
content = File.read filename, encoding: 'utf-8'
|
32
|
+
tokens = Lexer::lex content, filename
|
33
|
+
begin
|
34
|
+
ast = Parser::parse tokens
|
35
|
+
rescue RLTK::NotInLanguage => e
|
36
|
+
add_error UnexpectedTokenError.new e.current
|
37
|
+
return
|
38
|
+
end
|
39
|
+
runtime = Runtime.new filename
|
40
|
+
self.compile_ast runtime, ast
|
41
|
+
runtime
|
42
|
+
end
|
43
|
+
|
44
|
+
def compile_ast runtime, ast
|
45
|
+
@runtime = runtime
|
46
|
+
ast.each do |decl|
|
47
|
+
begin
|
48
|
+
case decl
|
49
|
+
when IncludeDecl
|
50
|
+
compile_include decl
|
51
|
+
when EnumDecl
|
52
|
+
compile_enum decl
|
53
|
+
when NodeDecl
|
54
|
+
compile_node decl
|
55
|
+
when StructDecl
|
56
|
+
compile_struct decl
|
57
|
+
when DirectionDecl
|
58
|
+
compile_direction decl
|
59
|
+
when SequenceDecl
|
60
|
+
compile_sequence decl
|
61
|
+
end
|
62
|
+
rescue CompilerError => e
|
63
|
+
@errors.push e
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def compile_include include_ast
|
69
|
+
filename = include_ast.filename + '.b'
|
70
|
+
if not File.exists? filename
|
71
|
+
add_error IncludeFileNotFoundError.new filename, include_ast.position
|
72
|
+
return
|
73
|
+
end
|
74
|
+
include_runtime = self.compile_file filename
|
75
|
+
@runtime.add_include include_runtime
|
76
|
+
end
|
77
|
+
|
78
|
+
def compile_enum enum_ast
|
79
|
+
self.check_case 'enum', :upper, enum_ast
|
80
|
+
self.check_type_unique enum_ast
|
81
|
+
enum_def = EnumTypeDef.new enum_ast
|
82
|
+
enum_ast.elements.each do |element_ast|
|
83
|
+
self.check_case 'enum element', :upper, element_ast
|
84
|
+
self.check_unique 'enum element', enum_def.elements[element_ast.name], element_ast
|
85
|
+
element_def = EnumElementDef.new element_ast
|
86
|
+
enum_def.add_element element_def
|
87
|
+
end
|
88
|
+
@runtime.add_enum enum_def
|
89
|
+
end
|
90
|
+
|
91
|
+
def compile_node node_ast
|
92
|
+
self.check_case 'node', :upper, node_ast
|
93
|
+
self.check_case 'node_nick', :upper, node_ast
|
94
|
+
self.check_unique 'node', @runtime.nodes[node_ast.name], node_ast
|
95
|
+
self.check_unique 'node', @runtime.nodes[node_ast.nickname], node_ast
|
96
|
+
node_def = NodeDef.new node_ast
|
97
|
+
@runtime.add_node node_def
|
98
|
+
end
|
99
|
+
|
100
|
+
def compile_struct struct_ast
|
101
|
+
self.check_case 'struct', :upper, struct_ast
|
102
|
+
self.check_type_unique struct_ast
|
103
|
+
struct_def = StructTypeDef.new struct_ast
|
104
|
+
struct_ast.members.each do |member_ast|
|
105
|
+
self.check_case 'struct member', :lower, member_ast
|
106
|
+
self.check_unique 'struct member', struct_def.get_member(member_ast.name), member_ast
|
107
|
+
member_type_def = self.get_member_type @runtime.nodes.values, member_ast.type
|
108
|
+
member_def = MemberDef.new member_ast, member_type_def
|
109
|
+
struct_def.add_member member_def
|
110
|
+
end
|
111
|
+
@runtime.add_struct struct_def
|
112
|
+
end
|
113
|
+
|
114
|
+
def compile_direction direction_ast
|
115
|
+
client = @runtime.nodes[direction_ast.client]
|
116
|
+
if client == nil
|
117
|
+
add_error NodeNotFoundError.new direction_ast, direction_ast.client
|
118
|
+
return
|
119
|
+
end
|
120
|
+
|
121
|
+
server = @runtime.nodes[direction_ast.server]
|
122
|
+
if server == nil
|
123
|
+
add_error NodeNotFoundError.new direction_ast, direction_ast.server
|
124
|
+
return
|
125
|
+
end
|
126
|
+
|
127
|
+
if client == server
|
128
|
+
add_error ClientServerSameError.new direction_ast
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
@message_base_id += 1000
|
133
|
+
@message_id = 0
|
134
|
+
|
135
|
+
old_direction = @runtime.get_direction client, direction_ast.direction, server
|
136
|
+
self.check_unique 'direction', old_direction, direction_ast
|
137
|
+
|
138
|
+
direction_def = DirectionDef.new direction_ast, client, server
|
139
|
+
direction_ast.messages.each do |message_ast|
|
140
|
+
self.check_case 'message', :upper, message_ast
|
141
|
+
self.check_unique 'message', direction_def.messages[message_ast.name], message_ast
|
142
|
+
direction_def.add_message self.compile_message direction_def, message_ast
|
143
|
+
end
|
144
|
+
@runtime.add_direction direction_def
|
145
|
+
end
|
146
|
+
|
147
|
+
def compile_message direction_def, message_ast
|
148
|
+
@message_id += 1
|
149
|
+
message_def = MessageDef.new message_ast, @message_base_id + @message_id
|
150
|
+
message_ast.members.each do |member_ast|
|
151
|
+
self.check_case 'message member', :lower, member_ast
|
152
|
+
self.check_unique 'message member', message_def.get_member(member_ast.name), member_ast
|
153
|
+
|
154
|
+
nodes_to_check = [direction_def.client, direction_def.server]
|
155
|
+
member_type_def = self.get_member_type nodes_to_check, member_ast.type
|
156
|
+
member_def = MemberDef.new member_ast, member_type_def
|
157
|
+
message_def.add_member member_def
|
158
|
+
end
|
159
|
+
message_def
|
160
|
+
end
|
161
|
+
|
162
|
+
def compile_sequence sequence_ast
|
163
|
+
self.check_case 'sequence', :upper, sequence_ast
|
164
|
+
self.check_unique 'sequence', @runtime.get_sequence(sequence_ast.name), sequence_ast
|
165
|
+
sequence_def = SequenceDef.new sequence_ast
|
166
|
+
sequence_ast.steps.each do |step_ast|
|
167
|
+
sequence_def.add_step self.compile_step step_ast
|
168
|
+
end
|
169
|
+
@runtime.add_sequence sequence_def
|
170
|
+
end
|
171
|
+
|
172
|
+
def compile_step step_ast
|
173
|
+
client = @runtime.nodes[step_ast.client]
|
174
|
+
if client == nil
|
175
|
+
add_error NodeNotFoundError.new step_ast, step_ast.client
|
176
|
+
return nil
|
177
|
+
end
|
178
|
+
|
179
|
+
server = @runtime.nodes[step_ast.server]
|
180
|
+
if server == nil
|
181
|
+
add_error NodeNotFoundError.new step_ast, step_ast.server
|
182
|
+
return nil
|
183
|
+
end
|
184
|
+
|
185
|
+
if client == server
|
186
|
+
add_error ClientServerSameError.new step_ast
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
|
190
|
+
direction_def = @runtime.get_direction client, step_ast.direction, server
|
191
|
+
if direction_def == nil
|
192
|
+
add_error DirectionNotFoundError.new step_ast, client, step_ast.direction, server
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
|
196
|
+
message_def = direction_def.messages[step_ast.message]
|
197
|
+
if message_def == nil
|
198
|
+
add_error MessageNotFoundError.new step_ast, direction_def
|
199
|
+
return nil
|
200
|
+
end
|
201
|
+
|
202
|
+
StepDef.new step_ast, direction_def, message_def
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_member_type nodes_to_check, member_type_ast
|
206
|
+
type_def, runtime = self.get_type member_type_ast, true
|
207
|
+
return nil if type_def == nil
|
208
|
+
|
209
|
+
params = []
|
210
|
+
case type_def.name
|
211
|
+
when 'List'
|
212
|
+
self.check_type_param_count member_type_ast, 1
|
213
|
+
params.push self.get_member_type nodes_to_check, member_type_ast.params[0]
|
214
|
+
when 'Set'
|
215
|
+
self.check_type_param_count member_type_ast, 1
|
216
|
+
params.push self.get_member_type nodes_to_check, member_type_ast.params[0]
|
217
|
+
when 'Map'
|
218
|
+
self.check_type_param_count member_type_ast, 2
|
219
|
+
params.push self.get_member_type nodes_to_check, member_type_ast.params[0]
|
220
|
+
params.push self.get_member_type nodes_to_check, member_type_ast.params[1]
|
221
|
+
else
|
222
|
+
self.check_type_param_count member_type_ast, 0
|
223
|
+
end
|
224
|
+
|
225
|
+
member_type_def = TypeInstanceDef.new member_type_ast, type_def, params, runtime
|
226
|
+
self.check_type_nodes member_type_ast, member_type_def
|
227
|
+
member_type_def
|
228
|
+
end
|
229
|
+
|
230
|
+
def check_type_nodes member_type_ast, member_type_def
|
231
|
+
return if @runtime == member_type_def.runtime
|
232
|
+
|
233
|
+
@runtime.nodes.each do |n|
|
234
|
+
external_node = member_type_def.runtime.nodes.find { |node| n.name == node.name }
|
235
|
+
if external_node == nil
|
236
|
+
add_error CorrespondingNodeNotFoundError.new member_type_def, n.name
|
237
|
+
return
|
238
|
+
end
|
239
|
+
|
240
|
+
if external_node.language != n.language
|
241
|
+
add_error NodeLanguageMismatchError.new member_type_def, n, external_node
|
242
|
+
return
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def get_type ast, raise_error
|
248
|
+
return nil if ast == nil
|
249
|
+
|
250
|
+
type_def, runtime = @runtime.get_type ast.name
|
251
|
+
return type_def, runtime if type_def != nil
|
252
|
+
|
253
|
+
add_error TypeNotFoundError.new ast, ast.name if raise_error
|
254
|
+
return type_def, runtime
|
255
|
+
end
|
256
|
+
|
257
|
+
def check_case type, initial_case, ast
|
258
|
+
if type == 'node_nick' and initial_case != ast.nickname[0].char_case
|
259
|
+
add_error InitialCaseError.new type, initial_case, ast
|
260
|
+
elsif initial_case != ast.name[0].char_case
|
261
|
+
add_error InitialCaseError.new type, initial_case, ast
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def check_type_unique ast
|
266
|
+
type_def, _ = self.get_type ast, false
|
267
|
+
return if type_def == nil
|
268
|
+
|
269
|
+
if type_def.is_a? BuiltinTypeDef
|
270
|
+
add_error BuiltinNameConflictError.new ast
|
271
|
+
else
|
272
|
+
self.check_unique 'type', type_def, ast
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def check_unique type, old_def, new_ast
|
277
|
+
return if old_def == nil
|
278
|
+
add_error DuplicateDefError.new type, old_def.name, old_def.ast, new_ast
|
279
|
+
end
|
280
|
+
|
281
|
+
def check_type_param_count ast, expected_count
|
282
|
+
add_error TypeParamCountMismatchError.new ast, expected_count if ast.params.length != expected_count
|
283
|
+
end
|
284
|
+
|
285
|
+
def add_error error
|
286
|
+
@errors.push error
|
287
|
+
end
|
288
|
+
end
|