# 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 'rake/clean' STAGES = Dir.glob('boot/stage*.elna').collect { |stage| File.basename stage, '.elna' }.sort CLEAN.include 'build/boot', 'build/valid' directory 'build/boot' directory 'build/valid' def compile(input, output) sh ENV.fetch('CC', 'gcc'), '-nostdlib', '-o', output, input end def run(exe) ENV.fetch('QEMU', '').split << exe end task default: :boot desc 'Final stage' task boot: "build/valid/#{STAGES.last}" task boot: "build/valid/#{STAGES.last}.s" task boot: "boot/#{STAGES.last}.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/stage8.elna', 'w') do |current_stage| File.readlines('boot/stage7.elna').each do |line| if line == ".section .bss\n" current_stage << <<~SECTION const symbol_builtin_name_int := "Int"; symbol_builtin_name_word := "Word"; symbol_builtin_name_pointer := "Pointer"; symbol_builtin_name_char := "Char"; symbol_builtin_name_bool := "Bool"; # Every type info starts with a word describing what type it is. # # PRIMITIVE_TYPE = 1 # # Primitive types have only type size. symbol_builtin_type_int := S(1, 4); symbol_builtin_type_word := S(1, 4); symbol_builtin_type_pointer := S(1, 4); symbol_builtin_type_char := S(1, 1); symbol_builtin_type_bool := S(1, 1); # Info objects start with a word describing its type. # # INFO_TYPE = 1 # # Type info has the type it belongs to. symbol_type_info_int := S(1, @symbol_builtin_type_int); symbol_type_info_word := S(1, @symbol_builtin_type_word); symbol_type_info_pointer := S(1, @symbol_builtin_type_pointer); symbol_type_info_char := S(1, @symbol_builtin_type_char); symbol_type_info_bool := S(1, @symbol_builtin_type_bool); SECTION elsif line == ".section .data\n" current_stage << "var\n" elsif !(line == ".section .text\n" || line == ".globl _start\n") current_stage << line end end end end rule /^build\/[[:alpha:]]+\/stage[[:digit:]]+$/ => ->(match) { "#{match}.s" } do |t| compile(*t.prerequisites, t.name) end STAGES.each do |stage| previous = stage.delete_prefix('stage').to_i.pred file "build/valid/#{stage}.s" => ["build/boot/#{stage}", "boot/#{stage}.elna"] do |t| exe, source = t.prerequisites cat_arguments = ['cat', source] last_stdout, wait_threads = Open3.pipeline_r(cat_arguments, run(exe)) IO.copy_stream last_stdout, t.name end file "build/boot/#{stage}.s" => ["build/valid/stage#{previous}", "boot/#{stage}.elna"] do |t| exe, source = t.prerequisites cat_arguments = ['cat', source] last_stdout, wait_threads = Open3.pipeline_r(cat_arguments, run(exe)) IO.copy_stream last_stdout, t.name end end # # Stage 1. # file 'build/valid/stage1.s' => ['build/boot/stage1', 'boot/stage1.s', 'build/valid'] do |t| source, exe, = t.prerequisites.partition { |prerequisite| prerequisite.end_with? '.s' } cat_arguments = ['cat', *source] last_stdout, wait_threads = Open3.pipeline_r(cat_arguments, run(exe.first)) IO.copy_stream last_stdout, t.name end file 'build/boot/stage1' => ['build/boot', 'boot/stage1.s'] do |t| source = t.prerequisites.select { |prerequisite| prerequisite.end_with? '.s' } compile(*source, t.name) end