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
@@ -0,0 +1,191 @@
|
|
1
|
+
|
2
|
+
class CompilerError
|
3
|
+
def initialize position
|
4
|
+
@position = position
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
"#{@position}: #{info}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
class InitialCaseError < CompilerError
|
14
|
+
def initialize type, initial_case, ast
|
15
|
+
super ast.position
|
16
|
+
@type = type
|
17
|
+
@initial_case = initial_case
|
18
|
+
@ast = ast
|
19
|
+
end
|
20
|
+
|
21
|
+
def info
|
22
|
+
if @type == 'node_nick' then
|
23
|
+
return "'#{@ast.nickname}' : nickname of nodes should be #{@initial_case} case"
|
24
|
+
else
|
25
|
+
return "'#{@ast.name}' : name of #{@type}s should be #{@initial_case} case"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
class BuiltinNameConflictError < CompilerError
|
32
|
+
def initialize ast
|
33
|
+
super ast.position
|
34
|
+
@name = ast.name
|
35
|
+
end
|
36
|
+
|
37
|
+
def info
|
38
|
+
"'#{@name}' : type name conflict with built-in type"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
class DuplicateDefError < CompilerError
|
44
|
+
def initialize type, name, old_ast, new_ast
|
45
|
+
super new_ast.position
|
46
|
+
@type = type
|
47
|
+
@name = name
|
48
|
+
@old_pos = old_ast.position
|
49
|
+
@new_pos = new_ast.position
|
50
|
+
end
|
51
|
+
|
52
|
+
def info
|
53
|
+
"'#{@name}' : #{@type} redefinition\n #{@old_pos} : see previous #{@type} definition of '#{@name}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class IncludeFileNotFoundError < CompilerError
|
59
|
+
def initialize filename, position
|
60
|
+
super position
|
61
|
+
@filename = filename
|
62
|
+
end
|
63
|
+
|
64
|
+
def info
|
65
|
+
"'#{@filename}' : include file not found: "
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
class TypeParamCountMismatchError < CompilerError
|
71
|
+
def initialize ast, expected_count
|
72
|
+
super ast.position
|
73
|
+
@expected_count = expected_count
|
74
|
+
@actual_count = ast.params.length
|
75
|
+
end
|
76
|
+
|
77
|
+
def info
|
78
|
+
"generic parameter count mismatch: expected #{@expected_count}, provided #{@actual_count}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class TypeNotFoundError < CompilerError
|
84
|
+
def initialize ast, name
|
85
|
+
super ast.position
|
86
|
+
@name = name
|
87
|
+
end
|
88
|
+
|
89
|
+
def info
|
90
|
+
"'#{@name}' : undefined type"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
class NodeNotFoundError < CompilerError
|
96
|
+
def initialize ast, name
|
97
|
+
super ast.position
|
98
|
+
@name = name
|
99
|
+
end
|
100
|
+
|
101
|
+
def info
|
102
|
+
"'#{@name}' : undefined node"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
class CorrespondingNodeNotFoundError < CompilerError
|
108
|
+
def initialize member_type_def, node_name
|
109
|
+
super member_type_def.ast.position
|
110
|
+
@type = member_type_def.type
|
111
|
+
@node_name = node_name
|
112
|
+
end
|
113
|
+
|
114
|
+
def info
|
115
|
+
"'#{@type.name}' : cannot find corresponding node '#{@node_name}'\n #{@type.ast.position} : see type definition of '#{@type.name}'"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
class NodeLanguageMismatchError < CompilerError
|
121
|
+
def initialize member_type_def, node_def, external_node_def
|
122
|
+
super member_type_def.ast.position
|
123
|
+
@type = member_type_def.type
|
124
|
+
@node_name = node_def.name
|
125
|
+
@node_position = node_def.ast.position
|
126
|
+
@external_node_position = external_node_def.ast.position
|
127
|
+
end
|
128
|
+
|
129
|
+
def info
|
130
|
+
"'#{@type.name}' : node language mismatch\n" +
|
131
|
+
" #{@node_position} : see node definition of '#{@node_name}'\n" +
|
132
|
+
" #{@external_node_position} : see external node definition of '#{@node_name}'\n" +
|
133
|
+
" #{@type.ast.position} : see type definition of '#{@type.name}'\n"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
class DirectionNotFoundError < CompilerError
|
139
|
+
def initialize ast, client, direction, server
|
140
|
+
super ast.position
|
141
|
+
@client = client
|
142
|
+
@direction = direction
|
143
|
+
@server = server
|
144
|
+
end
|
145
|
+
|
146
|
+
def info
|
147
|
+
case @direction
|
148
|
+
when :left
|
149
|
+
"'#{@client.name} -> #{@server.name}' : undefined direction"
|
150
|
+
when :right
|
151
|
+
"'#{@client.name} <- #{@server.name}' : undefined direction"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
class MessageNotFoundError < CompilerError
|
158
|
+
def initialize ast, direction
|
159
|
+
super ast.position
|
160
|
+
@name = ast.message
|
161
|
+
@direction = direction
|
162
|
+
end
|
163
|
+
|
164
|
+
def info
|
165
|
+
"'#{@name}' undefined message in direction '#{@direction.name}'\n #{@direction.ast.position} : see previous direction definition of '#{@direction.name}'"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
class ClientServerSameError < CompilerError
|
171
|
+
def initialize ast
|
172
|
+
super ast.position
|
173
|
+
end
|
174
|
+
|
175
|
+
def info
|
176
|
+
"client and server of a direction should not be the same"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
class UnexpectedTokenError < CompilerError
|
182
|
+
def initialize token
|
183
|
+
super token.position
|
184
|
+
@type = token.type
|
185
|
+
@value = token.value
|
186
|
+
end
|
187
|
+
|
188
|
+
def info
|
189
|
+
"unexpected #{@type.to_s.downcase} : '#{@value}'"
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
defmodule <%= node.namespace %>.Enums do<% for e in runtime.enums.values %>
|
2
|
+
def <%= e.elixir_read_name %>(data) do
|
3
|
+
<<value::32-little, data::binary>> = data
|
4
|
+
case value do<% for v in e.elements.values %>
|
5
|
+
<%= v.value %> -> {data, <%= v.elixir_name %>}<% end %>
|
6
|
+
_ -> :error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def <%= e.elixir_write_name %>(data, value) do
|
11
|
+
case value do<% for v in e.elements.values %>
|
12
|
+
<%= v.elixir_name %> -> <<data::binary, <%= v.value %>::32-little>><% end %>
|
13
|
+
_ -> :error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
<% end %>end
|
17
|
+
|
18
|
+
<% for s in runtime.structs.values %>
|
19
|
+
defmodule <%= node.namespace %>.<%= s.name %> do
|
20
|
+
defstruct [<%= s.elixir_members %>]
|
21
|
+
|
22
|
+
@typedoc """
|
23
|
+
<%= s.doc %><% for m in s.members %>
|
24
|
+
`:<%= m.elixir_name %>`: <%= m.doc %><% end %>
|
25
|
+
"""
|
26
|
+
@type t :: %<%= node.namespace %>.<%= s.name %>{<%= s.elixir_members_with_types node %>}
|
27
|
+
|
28
|
+
def read(data) do<% for m in s.members %>
|
29
|
+
{data, <%= m.elixir_name %>} = <%= m.type.elixir_read node %><% end %>
|
30
|
+
{data, %{<%= s.elixir_members_with_values %>}}
|
31
|
+
end
|
32
|
+
|
33
|
+
def write(data, value) do<% for m in s.members %>
|
34
|
+
data = <%= m.type.elixir_write node, 'value.' + m.elixir_name %><% end %>
|
35
|
+
data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
<% end %>
|
39
|
+
<% for n in runtime.get_node_directions node, :server %>
|
40
|
+
defmodule <%= node.namespace %>.<%= node.elixir_connection %> do
|
41
|
+
use GenServer, restart: :temporary
|
42
|
+
|
43
|
+
import <%= node.namespace %>.Enums
|
44
|
+
|
45
|
+
@behaviour Brotorift.ConnectionBehaviour
|
46
|
+
<% for m in n.in_direction.messages.values %>
|
47
|
+
<%= m.elixir_header_name %> <%= m.id %><% end %>
|
48
|
+
<% for m in n.out_direction.messages.values %>
|
49
|
+
<%= m.elixir_header_name %> <%= m.id %><% end %>
|
50
|
+
<% for m in n.out_direction.messages.values %>
|
51
|
+
@doc """
|
52
|
+
<%= m.doc %>
|
53
|
+
|
54
|
+
## Parameters
|
55
|
+
|
56
|
+
- `connection`: <%= node.elixir_connection %> Pid<% for p in m.members %>
|
57
|
+
- `<%= p.elixir_name %>`: <%= p.doc %>
|
58
|
+
<% end %>
|
59
|
+
"""
|
60
|
+
@spec <%= m.elixir_name %>(connection :: pid()<%= m.elixir_params_with_types node %>) :: :ok
|
61
|
+
def <%= m.elixir_name %>(connection<%= m.elixir_params %>) do
|
62
|
+
GenServer.cast(connection, {:<%= m.elixir_name %><%= m.elixir_params %>})
|
63
|
+
end
|
64
|
+
<% end %>
|
65
|
+
def start_link(args) do
|
66
|
+
GenServer.start_link(__MODULE__, args)
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_data(pid, data) do
|
70
|
+
GenServer.cast(pid, {:handle_data, data})
|
71
|
+
end
|
72
|
+
|
73
|
+
def stop(pid) do
|
74
|
+
GenServer.stop(pid)
|
75
|
+
end
|
76
|
+
|
77
|
+
def init({socket, transport, handler}) do
|
78
|
+
{:ok, state} = handler.open_connection(self(), socket)
|
79
|
+
{:ok, {socket, transport, handler, state}}
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_cast({:handle_data, data}, {socket, transport, handler, state}) do
|
83
|
+
{:ok, new_state} = process_packet(data, handler, state)
|
84
|
+
{:noreply, {socket, transport, handler, new_state}}
|
85
|
+
end
|
86
|
+
<% for m in n.out_direction.messages.values %>
|
87
|
+
def handle_cast({:<%= m.elixir_name %><%= m.elixir_params %>}, {socket, transport, handler, state}) do
|
88
|
+
data = <<<%= m.elixir_header_name %>::32-little>><% for p in m.members %>
|
89
|
+
data = <%= p.type.elixir_write node, p.elixir_name %><% end %>
|
90
|
+
data = <<byte_size(data)::32-little, data::binary>>
|
91
|
+
transport.send(socket, data)
|
92
|
+
{:noreply, {socket, transport, handler, state}}
|
93
|
+
end
|
94
|
+
<% end %>
|
95
|
+
def terminate(_reason, {_socket, _transport, handler, state}) do
|
96
|
+
handler.close_connection(self(), state)
|
97
|
+
end
|
98
|
+
|
99
|
+
defp process_packet(<<>>, _handler, state) do
|
100
|
+
{:ok, state}
|
101
|
+
end
|
102
|
+
defp process_packet(data, handler, state) do
|
103
|
+
<<_size::32-little, header::32-little, data::binary>> = data
|
104
|
+
case header do<% for m in n.in_direction.messages.values %>
|
105
|
+
<%= m.elixir_header_name %> -><% for p in m.members %>
|
106
|
+
{data, <%= p.elixir_name %>} = <%= p.type.elixir_read node %><% end %>
|
107
|
+
{:ok, state} = handler.<%= m.elixir_name %>(self(), state<%= m.elixir_params %>)
|
108
|
+
process_packet(data, handler, state)<% end %>
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
defmodule <%= node.namespace %>.<%= node.elixir_behaviour %> do
|
115
|
+
|
116
|
+
@doc """
|
117
|
+
Calls when the server started
|
118
|
+
"""
|
119
|
+
@callback start() :: :ok
|
120
|
+
|
121
|
+
@doc """
|
122
|
+
Calls when a new client connects
|
123
|
+
|
124
|
+
## Parameters
|
125
|
+
|
126
|
+
- `connection`: The <%= node.elixir_connection %> Pid for the client
|
127
|
+
- `socket`: The socket for the client
|
128
|
+
|
129
|
+
## Returns
|
130
|
+
|
131
|
+
{:ok, state}
|
132
|
+
|
133
|
+
"""
|
134
|
+
@callback open_connection(connection :: pid(), socket :: :gen_tcp.socket()) :: {:ok, any()}
|
135
|
+
|
136
|
+
@doc """
|
137
|
+
Calls when a client disconnects
|
138
|
+
|
139
|
+
## Parameters
|
140
|
+
|
141
|
+
- `connection`: The <%= node.elixir_connection %> Pid for the client
|
142
|
+
- `state`: The state for the connection
|
143
|
+
|
144
|
+
"""
|
145
|
+
@callback close_connection(connection :: pid(), state :: any()) :: :ok
|
146
|
+
|
147
|
+
<% for m in n.in_direction.messages.values %>
|
148
|
+
@doc """
|
149
|
+
<%= m.doc %>
|
150
|
+
|
151
|
+
## Parameters
|
152
|
+
|
153
|
+
- `connection`: The <%= node.elixir_connection %> Pid for the client
|
154
|
+
- `state`: The state for the connection<% for p in m.members %>
|
155
|
+
- `<%= p.elixir_name %>`: <%= p.doc %>
|
156
|
+
<% end %>
|
157
|
+
"""
|
158
|
+
@callback <%= m.elixir_name %>(connection :: pid(), state :: any()<%= m.elixir_params_with_types node %>) :: {:ok, any()}
|
159
|
+
<% end %>
|
160
|
+
end
|
161
|
+
|
162
|
+
<% end %>
|