Auto-generate ciabatta.c

This commit is contained in:
flysand7 2023-07-26 16:54:37 +11:00
parent 4c2ed6e7e4
commit 13ba60e338
11 changed files with 432 additions and 22 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
bin
/a
/src/ciabatta.c
a.out
*.a
*.so

View File

@ -2,6 +2,7 @@
local path = require 'path'
local argparse = require 'argparse'
local mf_parser = require 'scripts.manifest-parser'
-- Parse command line arguments
local parser = argparse('build.lua', 'Ciabatta build script')
@ -109,14 +110,16 @@ tinyrt_iface_hdr:write('// This file is AUTO-GENERATED\n')
tinyrt_iface_hdr:write('// See tinyrt.mf\n')
tinyrt_iface_hdr:write('\n')
n = 1
tinyrt_apis = {}
for line in io.lines(tinyrt_manifest_path) do
if line:len() ~= 0 and line:sub(1,1) ~= '#' and line:gsub('%s+', '') ~= '' then
local line_it = line:gmatch('[_a-zA-Z0-9]+')
local api_name = line_it():upper()
local api_name = line_it()
local has_impl = line_it()
if has_impl == '0' or has_impl == '1' then
local api_define = '#define ' .. api_name .. ' '..has_impl..'\n'
local api_define = '#define ' .. (api_name:upper()) .. ' '..has_impl..'\n'
tinyrt_iface_hdr:write(api_define)
table.insert(tinyrt_apis, api_name)
else
print('SYNTAX ERROR AT LINE '..n..': Expected 1 or 0 for the value')
end
@ -125,6 +128,50 @@ for line in io.lines(tinyrt_manifest_path) do
n = n+1
end
io.close(tinyrt_iface_hdr)
-- Parse manifest and generate ciabatta.c
local function has_value(tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
print('==> Generating ciabatta.c')
cia_h = io.open(path.join('src', 'ciabatta.c'), 'wb')
mf = parse_mf(path.join('src', 'library.mf'))
cia_h:write('\n')
cia_h:write('// THIS FILE IS AUTO-GENERATED. SEE library.mf FOR DETAILS\n')
cia_h:write('\n// global includes\n')
for index,include in ipairs(mf.includes) do
cia_h:write('#include <'..include..'>\n')
end
for index,decl_platform in ipairs(mf.platforms) do
if decl_platform.name == platform then
cia_h:write(('\n// platform %s\n'):format(decl_platform.name))
for index,include in ipairs(decl_platform.includes) do
cia_h:write('#include "'..include..'"\n')
end
end
end
for index, api in ipairs(mf.apis) do
supported = true
for index, dep in ipairs(api.deps) do
if not has_value(tinyrt_apis, dep) then
supported = false
end
end
if supported then
cia_h:write(('\n// module %s\n'):format(api.name))
print(' - Exporting module: ' .. api.name)
for index, include in ipairs(api.includes) do
cia_h:write('#include "'..include..'"\n')
end
end
end
io.close(cia_h)
-- Figure out compiler flags
local cflags = table.concat(compiler_flags, ' ')..' '..
@ -165,12 +212,12 @@ end
path.mkdir('lib')
path.mkdir('bin')
assemble('src/linux/crt_entry.asm', 'bin/crt_entry.o')
compile({'src/linux/crt_ctors.c'}, 'bin/crt_ctors.o', '-fpic -c')
assemble('src/linux/crt-entry.asm', 'bin/crt-entry.o')
compile({'src/linux/crt-ctors.c'}, 'bin/crt-ctors.o', '-fpic -c')
compile({'src/ciabatta.c'}, 'bin/ciabatta.o', '-fpic -c')
if library_type == 'static' then
archive({'bin/ciabatta.o', 'bin/crt_ctors.o', 'bin/crt_entry.o'}, 'lib/'..ciabatta_lib)
archive({'bin/ciabatta.o', 'bin/crt-ctors.o', 'bin/crt-entry.o'}, 'lib/'..ciabatta_lib)
elseif library_type == 'shared' then
print('SHARED OBJECTS NOT SUPPORTED YET')
os.exit(1)

11
include/stdlib.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <cia-def.h>
int atexit(void (*func)(void));
int at_quick_exit(void (*func)(void));
noreturn void abort(void);
noreturn void exit(int code);
noreturn void _Exit(int code);
noreturn void quick_exit(int code);

265
scripts/manifest-parser.lua Executable file
View File

@ -0,0 +1,265 @@
#!/usr/bin/env lua
local path = require 'path'
levels = {}
parser_obj = {}
parser_cur_ind = 0
parser_ind_lv = 0
-- Tokens
Token = {}
function Token.new(kind, value)
return {kind = kind, value = value}
end
function Token.keyword(kw) return Token.new('keyword', kw) end
function Token.string(str) return Token.new('string', str) end
function Token.lparen() return Token.new('(', nil) end
function Token.rparen() return Token.new(')', nil) end
function Token.lbrace() return Token.new('{', nil) end
function Token.rbrace() return Token.new('}', nil) end
function Token.colon() return Token.new(':', nil) end
function Token.comma() return Token.new(',', nil) end
function Token.eof() return Token.new('eof', nil) end
Parser = {
file = nil,
filename = nil,
row = 0,
col = 0,
ind_spaces = 0,
ind_levels = {0},
c = nil, -- Last Read Character
t = nil, -- Last Read Token
}
-- Character functions
function Parser.char_next(parser, c)
char = parser.file:read(1)
parser.col = parser.col + 1
if char == '\n' then
parser.row = parser.row + 1
parser.col = 0
end
parser.c = char
end
function Parser.char(parser)
return parser.c
end
function Parser.char_is(parser, pattern)
match = parser:char():match(pattern)
if match then
return true
else
return false
end
end
function Parser.char_match(parser, pattern)
if parser:char_is(pattern) then
parser:char_next()
return true
end
return false
end
-- Token functions
function Parser.token(parser)
return parser.t
end
function Parser.token_next(parser)
-- Skip over the whitespace
while true do
if parser.c == nil then
parser.t = Token.eof()
return
elseif parser:char_match('#') then
while not parser:char_match('\n') do
parser:char_next()
end
elseif parser:char_is('%s') then
parser:char_next()
else
break
end
end
-- print(1+parser.row, 1+parser.col, parser:char())
-- Keyword/identifier
if parser:char_is('[%a-_]') then
ident = ''
while parser:char_is('[%a%d-_]') do
ident = ident .. parser:char()
parser:char_next()
end
parser.t = Token.keyword(ident)
return
-- String
elseif parser:char_match('"') then
string = ''
while not parser:char_match('"') do
string = string .. parser:char()
parser:char_next()
end
parser.t = Token.string(string)
return
-- Single-char tokens
elseif parser:char_match('%(') then
parser.t = Token.lparen()
elseif parser:char_match('%)') then
parser.t = Token.rparen()
elseif parser:char_match('{') then
parser.t = Token.lbrace()
elseif parser:char_match('}') then
parser.t = Token.rbrace()
elseif parser:char_match(':') then
parser.t = Token.colon()
elseif parser:char_match(',') then
parser.t = Token.comma()
end
end
function Parser.token_is(parser, kind)
return parser:token().kind == kind
end
function Parser.token_kw_is(parser, name)
return parser:token().kind == 'keyword' and parser:token().value == name
end
function Parser.token_match(parser, kind)
if parser:token().kind == kind then
parser:token_next()
return true
end
return false
end
function Parser.token_kw_match(parser, name)
if parser:token().kind == 'keyword' and parser:token().value == name then
parser:token_next()
return true
end
return false
end
function Parser.new(filename)
file = io.open(filename, 'rb')
parser = Parser
parser.file = file
parser.filename = filename
parser:char_next()
parser:token_next()
return parser;
end
function parse_mf(mf_path)
parser = Parser.new(mf_path)
includes = {}
platforms = {}
apis = {}
while parser:token().kind ~= 'eof' do
if parser:token().kind ~= 'keyword' then
print(('%s(%d, %d): Expected keyword'):format(parser.filename, 1+parser.row, 1+parser.col))
end
-- TODO: add error handling
if parser:token_kw_match('include') then
parser:token_match('{')
while not parser:token_match('}') do
inc = parser:token().value
table.insert(includes, inc)
parser:token_next()
parser:token_match(',')
end
elseif parser:token_kw_match 'platform' then
platform_name = parser:token().value
parser:token_next()
parser:token_match('(')
platform_path = parser:token().value
if platform_path:sub(1,1) == '/' then
platform_path = platform_path:sub(2, -1)
end
parser:token_next()
parser:token_match(')')
parser:token_match('{')
platform_includes = {}
while not parser:token_match('}') do
if parser:token_kw_match('tinyrt') then
table.insert(platform_includes, path.join(platform_path, 'tinyrt.iface.h'))
table.insert(platform_includes, 'tinyrt.h')
parser:token_match('{')
while not parser:token_match('}') do
inc = parser:token().value
table.insert(platform_includes, path.join(platform_path, inc))
parser:token_next()
end
else
inc = parser:token().value
table.insert(platform_includes, path.join(platform_path, inc))
parser:token_next()
end
parser:token_match(',')
end
table.insert(platforms, {
name = platform_name,
includes = platform_includes
})
elseif parser:token_kw_match 'api' then
api_name = parser:token().value
parser:token_next()
parser:token_match('(')
api_path = parser:token().value
if api_path:sub(1,1) == '/' then
api_path = api_path:sub(2, -1)
end
parser:token_next()
parser:token_match(')')
parser:token_match('{')
api_includes = {}
rt_deps = {}
while not parser:token_match('}') do
if parser:token_kw_match('tinyrt') then
parser:token_match('{')
while not parser:token_match('}') do
dep = parser:token().value
table.insert(rt_deps, dep)
parser:token_next()
end
else
inc = parser:token().value
table.insert(api_includes, path.join(api_path, inc))
parser:token_next()
end
parser:token_match(',')
end
table.insert(apis, {
name = api_name,
deps = rt_deps,
includes = api_includes
})
else
print(('%s(%d, %d): Unknown directive: %s'):format(parser.filename, 1+parser.row, 1+parser.col, parser:token().value))
end
end
io.close(parser.file)
return {
includes = includes,
platforms = platforms,
apis = apis,
}
end
function print_r(arr, indentLevel)
local str = ""
local indentStr = ""
if(indentLevel == nil) then
print(print_r(arr, 0))
return
end
for i = 0, indentLevel do
indentStr = indentStr.." "
end
for index,value in pairs(arr) do
if type(value) == "table" then
str = str..indentStr..index..": \n"..print_r(value, (indentLevel + 1))
else
str = str..indentStr..index..": "..value.."\n"
end
end
return str
end
-- mf = parse_mf('src/library.mf')
-- print(print_r(mf, 0))

View File

@ -1,14 +0,0 @@
#include <cia_definitions.h>
#if _CIA_OS_LINUX()
#include "linux/syscall.c"
#include "linux/errno.c"
#include "linux/entry.c"
// TinyRT interface
#include "linux/tinyrt.iface.h"
#include "tinyrt.h"
#include "linux/tinyrt.c"
#elif _CIA_OS_WINDOWS()
#error "Not implemented yet"
#endif

View File

@ -0,0 +1,54 @@
#define MAX_ATEXIT_HANDLERS 32
#define MAX_AT_QUICK_EXIT_HANDLERS 32
static void (*atexit_handlers[MAX_ATEXIT_HANDLERS])(void);
static void (*at_quick_exit_handlers[MAX_AT_QUICK_EXIT_HANDLERS])(void);
static u64 n_atexit_handlers = 0;
static u64 n_at_quick_exit_handlers = 0;
int atexit(void (*func)(void)) {
if(n_atexit_handlers == MAX_ATEXIT_HANDLERS) {
return MAX_ATEXIT_HANDLERS;
}
atexit_handlers[n_atexit_handlers] = func;
n_atexit_handlers += 1;
return 0;
}
int at_quick_exit(void (*func)(void)) {
if(n_at_quick_exit_handlers == MAX_AT_QUICK_EXIT_HANDLERS) {
return MAX_AT_QUICK_EXIT_HANDLERS;
}
at_quick_exit_handlers[n_at_quick_exit_handlers] = func;
n_at_quick_exit_handlers += 1;
return 0;
}
noreturn void abort(void) {
// TODO: Ideally do a debug trap if the process is being debugged
_Exit(-1);
}
noreturn void exit(int code) {
for(u64 i = n_atexit_handlers-1; i-- != 0; ) {
void (*handler)(void) = atexit_handlers[i];
handler();
}
// TODO(bumbread): flush all the unflushed file streams
// TODO(bumbread): close all file streams and delete temporary files
rt_program_exit(code);
}
noreturn void _Exit(int code) {
rt_program_exit(code);
}
noreturn void quick_exit(int code) {
for(u64 i = n_at_quick_exit_handlers-1; i-- != 0; ) {
void (*handler)(void) = at_quick_exit_handlers[i];
handler();
}
rt_program_exit(code);
}

48
src/library.mf Normal file
View File

@ -0,0 +1,48 @@
# Manifest file for the ciabatta build.
# Platform sections describe available platforms and
# which files they need to include to function
# API sections describe API subsets and their TinyRT
# dependencies. If an API requires TinyRT module that
# isn't implemented by the platform that API won't be included
# in the final build
# This file is used to auto-generate ciabatta.c
# `include` section specifies the header files that ciabatta
# implements
# `platform` sections describe platforms, the directories
# where the source code for these platforms resides and the
# files that should be included in the build for that platform
# (relative to "src" dir) as well as the location of TinyRT
# implementation.
# `api` sections describe modules, where the implementation resides,
# the source files and the TinyRT modules, on which this module
# depends. If the platform doesn't implement one of the dependencies
# this module will not be included in the final build
include {
"cia-def.h",
"stdlib.h"
}
platform linux("/linux") {
"syscall.c",
"errno.c",
"entry.c",
tinyrt {
"tinyrt.c"
}
}
platform windows("/windows") {
tinyrt {},
}
api c11_program("/impl/c11-program") {
"program.c",
tinyrt {
rt_api_program,
}
}

View File

@ -1,5 +1,5 @@
#include <cia_definitions.h>
#include <cia-def.h>
// NOTE: These symbols are provided by the linker
#define attribute_hidden __attribute__((__visibility__("hidden")))

View File

@ -1,8 +1,6 @@
#pragma once
#include <cia_definitions.h>
// Common errors
#define RT_STATUS_OK 0 // No errors
#define RT_ERROR_NOT_IMPLEMENTED -1 // Function not implemented