burn 0.1.3 → 0.2.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.
- data/README.md +56 -569
- data/lib/burn.rb +5 -3
- data/lib/burn/cli.rb +160 -199
- data/lib/burn/config.rb +4 -0
- data/lib/burn/config/app.rb +15 -0
- data/lib/burn/config/config_base.rb +16 -0
- data/lib/burn/config/loader.rb +23 -0
- data/lib/burn/config/server.rb +11 -0
- data/lib/burn/fuel.rb +11 -0
- data/lib/burn/{dsl → fuel}/dsl_base.rb +1 -1
- data/lib/burn/fuel/rom/channel_stream.rb +106 -0
- data/lib/burn/fuel/rom/declare.rb +43 -0
- data/lib/burn/fuel/rom/music.rb +37 -0
- data/lib/burn/fuel/rom/note.rb +126 -0
- data/lib/burn/fuel/rom/scene.rb +279 -0
- data/lib/burn/fuel/rom/sound.rb +31 -0
- data/lib/burn/fuel/rom/sound_effect.rb +92 -0
- data/lib/burn/fuel/telnet/declare.rb +26 -0
- data/lib/burn/fuel/telnet/scene.rb +112 -0
- data/lib/burn/generator.rb +10 -3
- data/lib/burn/generator/rom/assembly_music.rb +49 -0
- data/lib/burn/generator/rom/assembly_sound_effect.rb +35 -0
- data/lib/burn/generator/rom/c_source.rb +55 -0
- data/lib/burn/generator/{template → rom/template}/mus_template.s +0 -0
- data/lib/burn/generator/{template → rom/template}/template.c +0 -0
- data/lib/burn/generator/rom_builder.rb +67 -0
- data/lib/burn/generator/telnet/jit_compiler.rb +41 -0
- data/lib/burn/generator/telnet/screen.rb +86 -0
- data/lib/burn/generator/telnet/sprite.rb +16 -0
- data/lib/burn/generator/telnet/user_input.rb +25 -0
- data/lib/burn/generator/telnet_vm.rb +50 -0
- data/lib/burn/pxes.rb +3 -0
- data/lib/burn/{util/pxes.rb → pxes/cc65_transpiler.rb} +8 -14
- data/lib/burn/pxes/cruby_transpiler.rb +189 -0
- data/lib/burn/pxes/transpiler_base.rb +14 -0
- data/lib/burn/server.rb +2 -0
- data/lib/burn/server/rom.rb +24 -0
- data/lib/burn/server/telnet.rb +120 -0
- data/lib/burn/util.rb +1 -2
- data/lib/burn/util/logo.rb +68 -0
- data/lib/burn/util/os.rb +1 -1
- data/lib/burn/version.rb +1 -1
- metadata +53 -19
- data/lib/burn/builder.rb +0 -65
- data/lib/burn/dsl.rb +0 -8
- data/lib/burn/dsl/channel_stream.rb +0 -105
- data/lib/burn/dsl/declare.rb +0 -42
- data/lib/burn/dsl/music.rb +0 -36
- data/lib/burn/dsl/note.rb +0 -125
- data/lib/burn/dsl/scene.rb +0 -278
- data/lib/burn/dsl/sound.rb +0 -30
- data/lib/burn/dsl/sound_effect.rb +0 -91
- data/lib/burn/generator/assembly_music.rb +0 -47
- data/lib/burn/generator/assembly_sound_effect.rb +0 -33
- data/lib/burn/generator/c_source.rb +0 -54
- data/lib/burn/util/server.rb +0 -21
@@ -0,0 +1,41 @@
|
|
1
|
+
module Burn
|
2
|
+
module Generator
|
3
|
+
module Telnet
|
4
|
+
class JitCompiler
|
5
|
+
include Fuel::Telnet
|
6
|
+
include Debug
|
7
|
+
attr_reader :opcodes
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@opcodes = Array.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_context
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def compile(source)
|
18
|
+
instance_eval source
|
19
|
+
@opcodes
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method_symbol, *args, &block)
|
23
|
+
log :method_symbol=>method_symbol, :args=>args, :block=>block
|
24
|
+
if method_symbol == :scene then
|
25
|
+
# preprocess
|
26
|
+
args[0] = "main" if args[0].nil?
|
27
|
+
|
28
|
+
# execute dsls
|
29
|
+
@opcodes << "##{args[0]}"
|
30
|
+
Scene.new(args[0], self).instance_eval(&block)
|
31
|
+
@opcodes << "#END"
|
32
|
+
|
33
|
+
elsif method_symbol == :declare then
|
34
|
+
Declare.new(args[0], self).instance_eval(&block)
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Burn
|
2
|
+
module Generator
|
3
|
+
module Telnet
|
4
|
+
class Screen
|
5
|
+
include Debug
|
6
|
+
ESC = 27.chr # equal to "\033"
|
7
|
+
CLEARSCREEN = ESC + "[2J"
|
8
|
+
JUMPTOHOME = ESC + "[H"
|
9
|
+
RESETALLATTR = ESC + "[0m"
|
10
|
+
CRLF = 13.chr + 10.chr
|
11
|
+
|
12
|
+
attr_accessor :display, :fg_color, :bg_color, :activated_sprite_objects
|
13
|
+
|
14
|
+
def initialize(conf)
|
15
|
+
@fg_color = 37
|
16
|
+
@bg_color = 40
|
17
|
+
@activated_sprite_objects = [] # activated sprite objects by #sprite method call during main_loop will be contained
|
18
|
+
@conf = conf
|
19
|
+
flush_screen
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_terminal
|
23
|
+
#CLEARSCREEN + escape_color(@fg_color) + escape_color(@bg_color) + @display.join("\r\n") + JUMPTOHOME + RESETALLATTR
|
24
|
+
#JUMPTOHOME + RESETALLATTR + CLEARSCREEN + escape_color(@fg_color) + escape_color(@bg_color) + @display.join("\r\n") + ESC + "[#{@conf.app.height};2H" + RESETALLATTR
|
25
|
+
#CLEARSCREEN + escape_color(@fg_color) + escape_color(@bg_color) + @display.join("\r\n") + ESC + "[#{@conf.app.height};2H" + RESETALLATTR
|
26
|
+
#JUMPTOHOME + crlf + crlf + crlf + crlf + crlf + crlf + @display.join(crlf)
|
27
|
+
#JUMPTOHOME + crlf + crlf + crlf + crlf + crlf + crlf + @display.join(crlf) + crlf
|
28
|
+
#JUMPTOHOME + @display.join(CRLF) + CRLF
|
29
|
+
|
30
|
+
JUMPTOHOME + escape_color(@fg_color) + escape_color(@bg_color) + ppu_emulate + CRLF + RESETALLATTR
|
31
|
+
#JUMPTOHOME + @display.join(CRLF) + CRLF
|
32
|
+
end
|
33
|
+
|
34
|
+
def flush_screen
|
35
|
+
@display = Array.new
|
36
|
+
@conf.app.height.times{ @display << Array.new(@conf.app.width){' '}.join }
|
37
|
+
@activated_sprite_objects = []
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_pressed(key, user_input)
|
41
|
+
log " ** program is waiting for:" + key.to_s
|
42
|
+
if !user_input.nil? then
|
43
|
+
log " **** confiremd user input:" + user_input.class.to_s, user_input.chr, user_input.to_s
|
44
|
+
if [*'0'..'9', *'a'..'z', *'A'..'Z'].include?(user_input.chr) && key.to_s == user_input.chr then
|
45
|
+
log "****** is_pressed() returned true"
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def escape_color(num)
|
55
|
+
ESC + "[" + num.to_s + ";1m"
|
56
|
+
end
|
57
|
+
|
58
|
+
def ppu_emulate
|
59
|
+
if @activated_sprite_objects.count == 0 then
|
60
|
+
@display.join(CRLF)
|
61
|
+
else
|
62
|
+
canvas = @display.map(&:dup) # deep copy
|
63
|
+
@activated_sprite_objects.each do |obj|
|
64
|
+
obj.tile.split("\n").each_with_index do |line, i|
|
65
|
+
canvas[(obj.y+ i) % @conf.app.height][obj.x % @conf.app.width,line.length] = line.chomp
|
66
|
+
end
|
67
|
+
|
68
|
+
# struct_var = "static sprite_schema #{key}={0, 0, {"
|
69
|
+
# @pattern_table_index[key.to_sym] = @pattern_table_pointer
|
70
|
+
# patternizer.patterns.each_with_index do |p, i|
|
71
|
+
# log p, i, patternizer.height, "aaaaaaaaaaaaaaaaaaa"
|
72
|
+
# struct_var += sprintf("%d, %d, %#x, 0,", (i % patternizer.width)*8 , (i / (patternizer.patterns.size / patternizer.height) )*8 , @pattern_table_pointer)
|
73
|
+
# @pattern_tables << p
|
74
|
+
# @pattern_table_pointer+=1
|
75
|
+
# end
|
76
|
+
# struct_var += "128}};"
|
77
|
+
|
78
|
+
end
|
79
|
+
canvas.join(CRLF)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Burn
|
2
|
+
module Generator
|
3
|
+
module Telnet
|
4
|
+
class UserInput
|
5
|
+
attr_reader :val
|
6
|
+
|
7
|
+
def initialize(conf)
|
8
|
+
@conf = conf
|
9
|
+
@signal = nil
|
10
|
+
@val = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def receive_signal(signal)
|
14
|
+
@signal = signal if @conf.app.user_input ==:enable
|
15
|
+
end
|
16
|
+
|
17
|
+
def init_for_next_loop
|
18
|
+
@val = @signal
|
19
|
+
@signal = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Burn
|
2
|
+
module Generator
|
3
|
+
class TelnetVm
|
4
|
+
include Generator::Telnet
|
5
|
+
include Debug
|
6
|
+
attr_reader :screen
|
7
|
+
attr_accessor :wait
|
8
|
+
|
9
|
+
def initialize(source, conf)
|
10
|
+
@pc = 0 # Program Counter
|
11
|
+
@screen = Screen.new(conf)
|
12
|
+
@wait = 10
|
13
|
+
@opcodes = JitCompiler.new.compile(source) # compiled methods
|
14
|
+
log @opcodes.join(":::")
|
15
|
+
@user_input = UserInput.new(conf)
|
16
|
+
@conf = conf
|
17
|
+
@finish = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_frame
|
21
|
+
log @pc
|
22
|
+
if @wait > 0 then
|
23
|
+
@wait = @wait - 1
|
24
|
+
elsif !@finish then
|
25
|
+
log @user_input
|
26
|
+
if @opcodes[@pc] != "#END" && (@opcodes.count-1) > @pc then
|
27
|
+
@pc = @pc+1
|
28
|
+
# skipping comment out to speed up
|
29
|
+
while @opcodes[@pc-1][0] == '#' do
|
30
|
+
@pc = @pc+1
|
31
|
+
end
|
32
|
+
log @opcodes[@pc-1]
|
33
|
+
self.instance_eval @opcodes[@pc-1]
|
34
|
+
else
|
35
|
+
log "Program counter reached at the end of program. #END"
|
36
|
+
@finish = true
|
37
|
+
end
|
38
|
+
else # if @finish == true then
|
39
|
+
# do nothing
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def interrupt(signal)
|
44
|
+
log "INTTERUPT_SETTING:" + @conf.app.user_input.to_s
|
45
|
+
@user_input.receive_signal(signal)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/burn/pxes.rb
ADDED
@@ -1,14 +1,6 @@
|
|
1
1
|
module Burn
|
2
|
-
module
|
3
|
-
class
|
4
|
-
include Debug
|
5
|
-
attr_reader :sexp
|
6
|
-
|
7
|
-
def initialize(sexp, context=nil, resource_name=nil)
|
8
|
-
@sexp = sexp
|
9
|
-
@context = context || self
|
10
|
-
@resource_name = resource_name
|
11
|
-
end
|
2
|
+
module Pxes
|
3
|
+
class Cc65Transpiler < TranspilerBase
|
12
4
|
|
13
5
|
def to_c
|
14
6
|
parse_sexp(@sexp)
|
@@ -27,9 +19,10 @@ module Burn
|
|
27
19
|
# end
|
28
20
|
#end
|
29
21
|
|
22
|
+
|
30
23
|
def invoke_dsl_processing(exp)
|
31
|
-
generator = Generator::CSource.new(self)
|
32
|
-
|
24
|
+
generator = Generator::Rom::CSource.new(self)
|
25
|
+
Fuel::Rom::Scene.new(@resource_name, generator).instance_eval exp
|
33
26
|
# return the result
|
34
27
|
@context.instance_exec(generator.global) do |generator_global|
|
35
28
|
@global.concat generator_global
|
@@ -100,14 +93,14 @@ module Burn
|
|
100
93
|
|
101
94
|
# this is why you can't use pre-defined dsl name as a variable name. e.g) you are not allowed to declare variables like show or label or stop. these all are defined dsl.
|
102
95
|
when :vcall
|
103
|
-
if !
|
96
|
+
if !Fuel::Rom::Scene.new(@resource_name,self).methods.index(s[1][1].to_sym).nil? then
|
104
97
|
invoke_dsl_processing parse_sexp(s[1])
|
105
98
|
else
|
106
99
|
parse_sexp(s[1])
|
107
100
|
end
|
108
101
|
|
109
102
|
when :command
|
110
|
-
if !
|
103
|
+
if !Fuel::Rom::Scene.new(@resource_name,self).methods.index(s[1][1].to_sym).nil? then
|
111
104
|
#invoke_dsl_processing parse_sexp(s[1]) + "(" + parse_sexp( replace_vcall_to_symbol(s[2]) ) + ")"
|
112
105
|
invoke_dsl_processing parse_sexp(s[1]) + "(" + parse_sexp( s[2] ) + ")"
|
113
106
|
else
|
@@ -175,6 +168,7 @@ module Burn
|
|
175
168
|
log s[0].class.to_s
|
176
169
|
end
|
177
170
|
end
|
171
|
+
|
178
172
|
end
|
179
173
|
end
|
180
174
|
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Burn
|
2
|
+
module Pxes
|
3
|
+
class CrubyTranspiler < TranspilerBase
|
4
|
+
|
5
|
+
def to_rb
|
6
|
+
parse_sexp(@sexp)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
#def replace_vcall_to_symbol(sexp_array)
|
11
|
+
# sexp_array.map do |a|
|
12
|
+
# if a.instance_of?(Array) then
|
13
|
+
# replace_vcall_to_symbol(a)
|
14
|
+
# elsif a==:vcall then
|
15
|
+
# :symbol
|
16
|
+
# else
|
17
|
+
# a
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#end
|
21
|
+
|
22
|
+
def invoke_dsl_processing(exp)
|
23
|
+
generator = Generator::Telnet::JitCompiler.new
|
24
|
+
# class(@@)|instance(@) variable inside exp need to be stringified before the #instance_eval.
|
25
|
+
# Without this, variables will be evaluated immediately now (and will get disappered as a result of this) by #instance_eval
|
26
|
+
Fuel::Telnet::Scene.new(@resource_name, generator).instance_eval exp.gsub(/(@[@\w]*)/, "\"\\1\"")
|
27
|
+
generator.opcodes.join "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_sexp(s)
|
31
|
+
if s.instance_of?(String) then
|
32
|
+
s
|
33
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Symbol) then
|
34
|
+
log "--array and symbol--"
|
35
|
+
log s[0]
|
36
|
+
case s[0]
|
37
|
+
when :assign
|
38
|
+
parse_sexp(s[1]) + "=" + parse_sexp(s[2])
|
39
|
+
|
40
|
+
when :var_field, :var_ref
|
41
|
+
if s[1][0] == :@ident then
|
42
|
+
var_scope = "@___"
|
43
|
+
else
|
44
|
+
var_scope = ""
|
45
|
+
end
|
46
|
+
var_scope + s[1][1]
|
47
|
+
|
48
|
+
when :void_stmt
|
49
|
+
"" # this happens when rip "if true then # do nothing end" type of code
|
50
|
+
|
51
|
+
when :binary
|
52
|
+
case s[2]
|
53
|
+
when :or
|
54
|
+
operator = "||"
|
55
|
+
when :and
|
56
|
+
operator = "&&"
|
57
|
+
else
|
58
|
+
operator = s[2].to_s
|
59
|
+
end
|
60
|
+
parse_sexp(s[1]) + operator + parse_sexp(s[3])
|
61
|
+
|
62
|
+
when :opassign
|
63
|
+
parse_sexp(s[1]) + parse_sexp(s[2]) + parse_sexp(s[3])
|
64
|
+
|
65
|
+
when :unary
|
66
|
+
case s[1]
|
67
|
+
when :not, :!
|
68
|
+
"!" + parse_sexp(s[2])
|
69
|
+
else
|
70
|
+
parse_sexp(s[2])
|
71
|
+
end
|
72
|
+
|
73
|
+
when :paren
|
74
|
+
"(" + parse_sexp(s[1][0]) + ")"
|
75
|
+
|
76
|
+
when :symbol
|
77
|
+
':' + s[1][1]
|
78
|
+
|
79
|
+
when :field, :call
|
80
|
+
parse_sexp(s[1]) + "." + parse_sexp(s[3])
|
81
|
+
|
82
|
+
when :@ident, :@int, :@kw, :@op
|
83
|
+
s[1]
|
84
|
+
|
85
|
+
when :@tstring_content
|
86
|
+
'"' + s[1] + '"'
|
87
|
+
|
88
|
+
# this is why you can't use pre-defined dsl name as a variable name. e.g) you are not allowed to declare variables like show or label or stop. these all are defined dsl.
|
89
|
+
when :vcall
|
90
|
+
if s[1][0] == :@ident then
|
91
|
+
var_scope = "@___"
|
92
|
+
else
|
93
|
+
var_scope = ""
|
94
|
+
end
|
95
|
+
s[1][1] = var_scope + s[1][1]
|
96
|
+
parse_sexp(s[1])
|
97
|
+
|
98
|
+
# this is why you can't use pre-defined dsl name as a variable name. e.g) you are not allowed to declare variables like show or label or stop. these all are defined dsl.
|
99
|
+
when :command
|
100
|
+
if !Fuel::Telnet::Scene.new(@resource_name,@context).methods.index(s[1][1].to_sym).nil? then
|
101
|
+
#invoke_dsl_processing parse_sexp(s[1]) + "(" + parse_sexp( replace_vcall_to_symbol(s[2]) ) + ")"
|
102
|
+
invoke_dsl_processing parse_sexp(s[1]) + "(" + parse_sexp( s[2] ) + ")"
|
103
|
+
else
|
104
|
+
parse_sexp(s[1]) + "(" + parse_sexp(s[2]) + ")"
|
105
|
+
end
|
106
|
+
|
107
|
+
when :method_add_arg
|
108
|
+
command = s[1][1][1]
|
109
|
+
# check if it matches dsl.
|
110
|
+
case command
|
111
|
+
when "is_pressed"
|
112
|
+
"@screen.is_pressed(" + parse_sexp(s[2]) + ", @user_input.val)"
|
113
|
+
#when "rand"
|
114
|
+
# parse_sexp(s[1]) + "8()"
|
115
|
+
else
|
116
|
+
parse_sexp(s[1]) + "(" + parse_sexp(s[2]) + ")"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Currently pssing block is not supported
|
120
|
+
#when :method_add_block
|
121
|
+
|
122
|
+
when :args_add_block
|
123
|
+
a = Array.new
|
124
|
+
s[1].each_with_index do |sexp,i|
|
125
|
+
log "<<<<<<<<<<<<<<<<<<NUM #{i}>>>>>>"
|
126
|
+
log sexp
|
127
|
+
a << parse_sexp(sexp)
|
128
|
+
end
|
129
|
+
a.join(",")
|
130
|
+
|
131
|
+
when :while
|
132
|
+
"while(" + parse_sexp(s[1]) + "){" + parse_sexp(s[2]) + "}"
|
133
|
+
|
134
|
+
when :if, :if_mod, :elsif
|
135
|
+
if s[0]==:elsif then
|
136
|
+
keyword = "else if"
|
137
|
+
else
|
138
|
+
keyword = "if"
|
139
|
+
end
|
140
|
+
additional_condition = parse_sexp(s[3]) if !s[3].nil?
|
141
|
+
#"#{keyword} (" + parse_sexp(s[1]) + "){" + parse_sexp(s[2]) + "} #{additional_condition}"
|
142
|
+
label = "##{@resource_name}-" + [*0-9, *'a'..'z', *'A'..'Z'].sample(10).join
|
143
|
+
#"@pc = @opcodes.index(\"#{label}\") if !(" + parse_sexp(s[1]) + ")\n" + parse_sexp(s[2]) + "\n#{label}"
|
144
|
+
if additional_condition then
|
145
|
+
# jump to else
|
146
|
+
# + script for if clause
|
147
|
+
# jump to end
|
148
|
+
# label for else
|
149
|
+
# + script for else
|
150
|
+
# label for end
|
151
|
+
"@pc = @opcodes.index(\"#{label}\") if !(" + parse_sexp(s[1]) + ")\n" \
|
152
|
+
+ parse_sexp(s[2]) + "\n" \
|
153
|
+
+ "@pc = @opcodes.index(\"#{label}-else\")\n" \
|
154
|
+
+ "#{label}\n" \
|
155
|
+
+ additional_condition + "\n" \
|
156
|
+
+ "#{label}-else"
|
157
|
+
else
|
158
|
+
"@pc = @opcodes.index(\"#{label}\") if !(" + parse_sexp(s[1]) + ")\n" + parse_sexp(s[2]) + "\n#{label}"
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
#when :def
|
163
|
+
# is not supported for to_c as return data type cannot be defined
|
164
|
+
|
165
|
+
when :else
|
166
|
+
parse_sexp(s[1])
|
167
|
+
|
168
|
+
else
|
169
|
+
parse_sexp(s[1])
|
170
|
+
|
171
|
+
end
|
172
|
+
# Safety net
|
173
|
+
elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
|
174
|
+
a = Array.new
|
175
|
+
s.each_with_index do |sexp,i|
|
176
|
+
log "<<<<<<<<<<<<<<<<<<NUM #{i}>>>>>>"
|
177
|
+
log sexp
|
178
|
+
a << parse_sexp(sexp)
|
179
|
+
end
|
180
|
+
a.join("\n")
|
181
|
+
else
|
182
|
+
log "--else--"
|
183
|
+
log s.class.to_s
|
184
|
+
log s[0].class.to_s
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|