1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.
# frozen_string_literal: true
require 'open3'
require 'pathname'
require 'rake/clean'
require 'asciidoctor-pdf'
STAGES = Dir.glob('boot/stage*')
.collect { |stage| File.basename stage }
.sort { |a, b| a.delete_prefix('stage').to_i <=> b.delete_prefix('stage').to_i }
.drop(1) # First assembly stage does not count.
CLEAN.include 'build/boot', 'build/valid'
CLEAN.include 'doc/*.pdf'
CLOBBER.include 'build'
def run(exe)
ENV.fetch('QEMU', '').split << exe
end
task default: :boot
desc 'Final stage'
task boot: "build/valid/#{STAGES.last}/cl"
task boot: "boot/#{STAGES.last}/cl.elna" do |t|
groupped = t.prerequisites.group_by { |stage| File.extname stage }.transform_values(&:first)
exe = groupped['']
expected = groupped[''] + '.s'
source = groupped['.elna']
cat_arguments = ['cat', source]
diff_arguments = ['diff', '-Nur', '--text', expected, '-']
Open3.pipeline(cat_arguments, run(exe), diff_arguments)
end
desc 'Convert previous stage language into the current stage language'
task :convert do
File.open('boot/stage19/cl.elna', 'w') do |current_stage|
File.readlines('boot/stage18/cl.elna').each do |line|
current_stage << "\tf();\n" if line.include? "if _compile() then"
current_stage << line
if line == "type\n"
current_stage << <<-RECORD
Pair = record
first: Word;
second: Word
end;
R1 = record
field1: Word;
field2: [4]Word;
field3: Word
end;
R2 = record
field1: Word;
field3: Word;
field4: Word;
field5: Word;
field6: Word;
field2: Word
end;
RECORD
end
end
current_stage << <<~EPILOGUE
proc f();
var
v1: Word;
r: ^R1;
begin
r := malloc(#size(R1));
v1 := r^.field2[2];
printf("# %i\\n\\0", v1)
end;
EPILOGUE
end
end
file "build/valid/#{STAGES.last}/cl" => 'build/build.ninja' do |t|
sh 'ninja', '-f', t.prerequisites.first
end
file 'build/build.ninja' => ['build'] do |t|
File.open t.name, 'w' do |f|
f << <<~NINJA
builddir = build
cflags = -fpie -g
rule cc
command = gcc $cflags -nostdlib -o $out $in
rule as
command = gcc $cflags -c -o $out $in
rule link1
command = ld -o $out $in
rule link2
command = ld -o $out --dynamic-linker /lib32/ld-linux-riscv32-ilp32d.so.1 /usr/lib/crt1.o /usr/lib/crti.o -lc $in /usr/lib/crtn.o
rule bootstrap
command = $bootstrap < \$in > \$out
NINJA
f << <<~NINJA
build build/boot/stage1/cl: cc boot/stage1.s
build build/valid/stage1/cl.s: bootstrap boot/stage1.s | build/boot/stage1/cl
bootstrap = build/boot/stage1/cl
build build/valid/stage1/cl.o: as build/valid/stage1/cl.s
build build/valid/stage1/cl: link1 build/valid/stage1/cl.o
NINJA
STAGES.each do |stage|
stage_number = stage.delete_prefix('stage').to_i
arguments_path = Pathname.new('boot') + stage + 'linker.arg'
if arguments_path.exist?
link = 'link2'
else
link = 'link1'
end
boot_stage = "build/boot/stage#{stage_number}"
valid_stage = "build/valid/stage#{stage_number}"
f << <<~NINJA
build #{boot_stage}/cl.s: bootstrap boot/stage#{stage_number}/cl.elna | build/valid/stage#{stage_number.pred}/cl
bootstrap = build/valid/stage#{stage_number.pred}/cl
build #{boot_stage}/cl.o: as #{boot_stage}/cl.s
build #{boot_stage}/cl: #{link} #{boot_stage}/cl.o
build #{valid_stage}/cl.s: bootstrap boot/stage#{stage_number}/cl.elna | #{boot_stage}/cl
bootstrap = build/boot/stage#{stage_number}/cl
build #{valid_stage}/cl.o: as #{valid_stage}/cl.s
build #{valid_stage}/cl: #{link} #{valid_stage}/cl.o
NINJA
end
f << <<~NINJA
default build/valid/#{STAGES.last}/cl
NINJA
end
end
rule '.pdf' => '.adoc' do |t|
Asciidoctor.convert_file t.source, backend: 'pdf', safe: :safe
end
desc 'Generate documentation'
task doc: 'doc/language.pdf'
|