Compare commits
1 Commits
main
...
3c5a29633b
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c5a29633b |
@@ -45,5 +45,5 @@ func main[] : i64
|
||||
## Quickstart
|
||||
```
|
||||
cargo install --git https://github.com/antpiasecki/zern
|
||||
zern -m -r hello.zr
|
||||
zern -r hello.zr
|
||||
```
|
||||
|
||||
@@ -97,7 +97,7 @@ impl<'a> CodegenX86_64<'a> {
|
||||
format!("section .data\n{}{}", self.data_section, self.output)
|
||||
}
|
||||
|
||||
pub fn emit_prologue(&mut self, use_gcc: bool) -> Result<(), ZernError> {
|
||||
pub fn emit_prologue(&mut self, use_crt: bool) -> Result<(), ZernError> {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
"section .note.GNU-stack
|
||||
@@ -151,10 +151,16 @@ _builtin_syscall:
|
||||
mov r9, [rsp+8]
|
||||
syscall
|
||||
ret
|
||||
|
||||
section .text._builtin_cvttsd2si
|
||||
_builtin_cvttsd2si:
|
||||
movq xmm0, rdi
|
||||
cvttsd2si rax, xmm0
|
||||
ret
|
||||
"
|
||||
);
|
||||
|
||||
if !use_gcc {
|
||||
if !use_crt {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
"
|
||||
@@ -220,7 +226,7 @@ _builtin_environ:
|
||||
Some(t) => t.lexeme.clone(),
|
||||
None => match &initializer {
|
||||
Expr::Literal(token) => {
|
||||
if token.token_type == TokenType::Number {
|
||||
if token.token_type == TokenType::IntLiteral {
|
||||
"i64".into()
|
||||
} else {
|
||||
return error!(&name.loc, "unable to infer variable type");
|
||||
@@ -504,17 +510,27 @@ _builtin_environ:
|
||||
}
|
||||
Expr::Grouping(expr) => self.compile_expr(env, expr)?,
|
||||
Expr::Literal(token) => match token.token_type {
|
||||
TokenType::Number => {
|
||||
TokenType::IntLiteral => {
|
||||
emit!(&mut self.output, " mov rax, {}", token.lexeme);
|
||||
}
|
||||
TokenType::Char => {
|
||||
TokenType::FloatLiteral => {
|
||||
let value = token.lexeme.parse::<f64>().unwrap();
|
||||
let bits = value.to_bits();
|
||||
let high = (bits >> 32) as u32;
|
||||
let low = bits as u32;
|
||||
emit!(&mut self.output, " mov eax, {}", high);
|
||||
emit!(&mut self.output, " shl rax, 32");
|
||||
emit!(&mut self.output, " mov ebx, {}", low);
|
||||
emit!(&mut self.output, " or rax, rbx");
|
||||
}
|
||||
TokenType::CharLiteral => {
|
||||
emit!(
|
||||
&mut self.output,
|
||||
" mov rax, {}",
|
||||
token.lexeme.chars().nth(1).unwrap() as u8
|
||||
);
|
||||
}
|
||||
TokenType::String => {
|
||||
TokenType::StringLiteral => {
|
||||
// TODO: actual string parsing in the tokenizer
|
||||
let value = &token.lexeme[1..token.lexeme.len() - 1];
|
||||
|
||||
|
||||
41
src/main.rs
41
src/main.rs
@@ -34,9 +34,11 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
|
||||
|
||||
let mut statements = Vec::new();
|
||||
|
||||
if args.include_stdlib {
|
||||
parse_std_file!(statements, "std/syscalls.zr");
|
||||
parse_std_file!(statements, "std/std.zr");
|
||||
parse_std_file!(statements, "std/net.zr");
|
||||
}
|
||||
|
||||
let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source);
|
||||
let parser = parser::Parser::new(tokenizer.tokenize()?);
|
||||
@@ -53,21 +55,21 @@ fn compile_file(args: Args) -> Result<(), ZernError> {
|
||||
}
|
||||
|
||||
let mut codegen = codegen_x86_64::CodegenX86_64::new(&symbol_table);
|
||||
codegen.emit_prologue(args.use_gcc)?;
|
||||
codegen.emit_prologue(args.use_crt)?;
|
||||
for stmt in statements {
|
||||
codegen.compile_stmt(&mut codegen_x86_64::Env::new(), &stmt)?;
|
||||
}
|
||||
|
||||
if !args.output_asm {
|
||||
if !args.emit_only {
|
||||
let out = args.out.unwrap_or_else(|| "out".into());
|
||||
|
||||
fs::write(format!("{}.s", out), codegen.get_output()).unwrap();
|
||||
|
||||
run_command(format!("nasm -f elf64 -o {}.o {}.s", out, out));
|
||||
|
||||
if args.use_gcc {
|
||||
if args.use_crt {
|
||||
run_command(format!(
|
||||
"gcc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
|
||||
"cc -no-pie -o {} {}.o -flto -Wl,--gc-sections {}",
|
||||
out, out, args.cflags
|
||||
));
|
||||
} else {
|
||||
@@ -110,9 +112,10 @@ fn run_command(cmd: String) {
|
||||
struct Args {
|
||||
path: String,
|
||||
out: Option<String>,
|
||||
output_asm: bool,
|
||||
emit_only: bool,
|
||||
include_stdlib: bool,
|
||||
run_exe: bool,
|
||||
use_gcc: bool,
|
||||
use_crt: bool,
|
||||
cflags: String,
|
||||
}
|
||||
|
||||
@@ -121,9 +124,10 @@ impl Args {
|
||||
let mut out = Args {
|
||||
path: String::new(),
|
||||
out: None,
|
||||
output_asm: false,
|
||||
emit_only: false,
|
||||
include_stdlib: true,
|
||||
run_exe: false,
|
||||
use_gcc: false,
|
||||
use_crt: false,
|
||||
cflags: String::new(),
|
||||
};
|
||||
|
||||
@@ -132,26 +136,30 @@ impl Args {
|
||||
match args.next() {
|
||||
Some(s) => out.out = Some(s),
|
||||
None => {
|
||||
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a name");
|
||||
eprintln!("\x1b[91mERROR\x1b[0m: -o option requires a path");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
} else if arg == "-S" {
|
||||
out.output_asm = true;
|
||||
} else if arg == "--emit-only" {
|
||||
out.emit_only = true;
|
||||
} else if arg == "--no-stdlib" {
|
||||
out.include_stdlib = false;
|
||||
} else if arg == "-r" {
|
||||
out.run_exe = true;
|
||||
} else if arg == "-m" {
|
||||
out.use_gcc = true;
|
||||
out.use_crt = true;
|
||||
} else if arg == "-C" {
|
||||
match args.next() {
|
||||
Some(s) => out.cflags = s,
|
||||
None => {
|
||||
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a name");
|
||||
eprintln!("\x1b[91mERROR\x1b[0m: -C option requires a value");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
} else if arg == "-h" || arg == "--help" {
|
||||
println!("Usage: zern [-o path] [-S] [-r] [-m] [-C cflags] path");
|
||||
println!(
|
||||
"Usage: zern [-o path] [-r] [-m] [-C cflags] [--emit-only] [--no-stdlib] path"
|
||||
);
|
||||
process::exit(0);
|
||||
} else if arg.starts_with('-') {
|
||||
eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {}", arg);
|
||||
@@ -169,9 +177,8 @@ impl Args {
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if !out.use_gcc && !out.cflags.is_empty() {
|
||||
// no "ERROR:" since its not an error
|
||||
eprintln!("You can't set CFLAGS if you're not using gcc. Add the -m flag.");
|
||||
if !out.use_crt && !out.cflags.is_empty() {
|
||||
eprintln!("You can't set CFLAGS if you're not using the C runtime. Add the -m flag.");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ impl Parser {
|
||||
fn const_declaration(&mut self) -> Result<Stmt, ZernError> {
|
||||
let name = self.consume(TokenType::Identifier, "expected const name")?;
|
||||
self.consume(TokenType::Equal, "expected '=' after const name")?;
|
||||
let value = self.consume(TokenType::Number, "expected a number after '='")?;
|
||||
let value = self.consume(TokenType::IntLiteral, "expected a number after '='")?;
|
||||
Ok(Stmt::Const { name, value })
|
||||
}
|
||||
|
||||
@@ -576,9 +576,10 @@ impl Parser {
|
||||
|
||||
fn primary(&mut self) -> Result<Expr, ZernError> {
|
||||
if self.match_token(&[
|
||||
TokenType::Number,
|
||||
TokenType::Char,
|
||||
TokenType::String,
|
||||
TokenType::IntLiteral,
|
||||
TokenType::CharLiteral,
|
||||
TokenType::StringLiteral,
|
||||
TokenType::FloatLiteral,
|
||||
TokenType::True,
|
||||
TokenType::False,
|
||||
]) {
|
||||
|
||||
@@ -53,6 +53,7 @@ impl SymbolTable {
|
||||
"_builtin_set64".into(),
|
||||
FnType::new("void", vec!["ptr", "i64"]),
|
||||
),
|
||||
("_builtin_cvttsd2si".into(), FnType::new("i64", vec!["f64"])),
|
||||
("_builtin_syscall".into(), FnType::new_variadic("i64")),
|
||||
("_builtin_environ".into(), FnType::new("ptr", vec![])),
|
||||
("_var_arg".into(), FnType::new("any", vec!["i64"])),
|
||||
|
||||
@@ -34,9 +34,10 @@ pub enum TokenType {
|
||||
LessEqual,
|
||||
|
||||
Identifier,
|
||||
String,
|
||||
Char,
|
||||
Number,
|
||||
StringLiteral,
|
||||
CharLiteral,
|
||||
IntLiteral,
|
||||
FloatLiteral,
|
||||
True,
|
||||
False,
|
||||
|
||||
@@ -240,7 +241,7 @@ impl Tokenizer {
|
||||
if !self.match_char('\'') {
|
||||
return error!(self.loc, "expected ' after char literal");
|
||||
}
|
||||
self.add_token(TokenType::Char)?
|
||||
self.add_token(TokenType::CharLiteral)?
|
||||
}
|
||||
'"' => {
|
||||
let start_loc = self.loc.clone();
|
||||
@@ -265,7 +266,7 @@ impl Tokenizer {
|
||||
}
|
||||
|
||||
self.advance();
|
||||
self.add_token(TokenType::String)?
|
||||
self.add_token(TokenType::StringLiteral)?
|
||||
}
|
||||
' ' | '\r' => {}
|
||||
'\n' => {
|
||||
@@ -333,17 +334,28 @@ impl Tokenizer {
|
||||
while self.peek().is_ascii_hexdigit() {
|
||||
self.advance();
|
||||
}
|
||||
self.add_token(TokenType::IntLiteral)
|
||||
} else if self.match_char('o') {
|
||||
while matches!(self.peek(), '0'..='7') {
|
||||
self.advance();
|
||||
}
|
||||
self.add_token(TokenType::IntLiteral)
|
||||
} else {
|
||||
while self.peek().is_ascii_digit() {
|
||||
self.advance();
|
||||
}
|
||||
if self.current + 2 < self.source.len()
|
||||
&& self.source[self.current + 1].is_ascii_digit()
|
||||
&& self.match_char('.')
|
||||
{
|
||||
while self.peek().is_ascii_digit() {
|
||||
self.advance();
|
||||
}
|
||||
self.add_token(TokenType::FloatLiteral)
|
||||
} else {
|
||||
self.add_token(TokenType::IntLiteral)
|
||||
}
|
||||
}
|
||||
|
||||
self.add_token(TokenType::Number)
|
||||
}
|
||||
|
||||
fn scan_identifier(&mut self) -> Result<(), ZernError> {
|
||||
@@ -392,7 +404,7 @@ impl Tokenizer {
|
||||
fn add_token(&mut self, token_type: TokenType) -> Result<(), ZernError> {
|
||||
let mut lexeme: String = self.source[self.start..self.current].iter().collect();
|
||||
|
||||
if token_type == TokenType::Char || token_type == TokenType::String {
|
||||
if token_type == TokenType::CharLiteral || token_type == TokenType::StringLiteral {
|
||||
lexeme = self.unescape(&lexeme)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ macro_rules! expect_types {
|
||||
};
|
||||
}
|
||||
|
||||
static BUILTIN_TYPES: [&str; 8] = ["void", "u8", "i64", "str", "bool", "ptr", "fnptr", "any"];
|
||||
static BUILTIN_TYPES: [&str; 9] = [
|
||||
"void", "u8", "i64", "f64", "str", "bool", "ptr", "fnptr", "any",
|
||||
];
|
||||
|
||||
pub struct Env {
|
||||
scopes: Vec<HashMap<String, Type>>,
|
||||
@@ -321,9 +323,10 @@ impl<'a> TypeChecker<'a> {
|
||||
}
|
||||
Expr::Grouping(expr) => self.typecheck_expr(env, expr),
|
||||
Expr::Literal(token) => match token.token_type {
|
||||
TokenType::Number => Ok("i64".into()),
|
||||
TokenType::Char => Ok("u8".into()),
|
||||
TokenType::String => Ok("str".into()),
|
||||
TokenType::IntLiteral => Ok("i64".into()),
|
||||
TokenType::CharLiteral => Ok("u8".into()),
|
||||
TokenType::StringLiteral => Ok("str".into()),
|
||||
TokenType::FloatLiteral => Ok("f64".into()),
|
||||
TokenType::True => Ok("bool".into()),
|
||||
TokenType::False => Ok("bool".into()),
|
||||
_ => unreachable!(),
|
||||
|
||||
2
test.zr
2
test.zr
@@ -28,8 +28,6 @@ func run_test[x: str] : void
|
||||
let run_cmd: str = "./out"
|
||||
if str.equal(x, "examples/curl.zr")
|
||||
run_cmd = str.concat(run_cmd, " http://example.com")
|
||||
else if str.equal(x, "examples/tokenizer.zr")
|
||||
run_cmd = str.concat(run_cmd, " test.zr")
|
||||
|
||||
let run_start_time: i64 = os.time()
|
||||
if must(os.run_shell_command?(run_cmd)) != 0
|
||||
|
||||
Reference in New Issue
Block a user