brotorift 0.1.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/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 %>
|