diff --git a/README.md b/README.md index 435a8f4..8278de2 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index 21de96e..ef98508 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -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::().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]; diff --git a/src/main.rs b/src/main.rs index 24c969c..ecbc00e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,9 +34,11 @@ fn compile_file(args: Args) -> Result<(), ZernError> { let mut statements = Vec::new(); - parse_std_file!(statements, "std/syscalls.zr"); - parse_std_file!(statements, "std/std.zr"); - parse_std_file!(statements, "std/net.zr"); + 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, - 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); } diff --git a/src/parser.rs b/src/parser.rs index 3c96b89..a39822b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -255,7 +255,7 @@ impl Parser { fn const_declaration(&mut self) -> Result { 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 { if self.match_token(&[ - TokenType::Number, - TokenType::Char, - TokenType::String, + TokenType::IntLiteral, + TokenType::CharLiteral, + TokenType::StringLiteral, + TokenType::FloatLiteral, TokenType::True, TokenType::False, ]) { diff --git a/src/symbol_table.rs b/src/symbol_table.rs index 2f1abd0..f93d314 100644 --- a/src/symbol_table.rs +++ b/src/symbol_table.rs @@ -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"])), diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 445c93d..f1db5ce 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -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)?; } diff --git a/src/typechecker.rs b/src/typechecker.rs index e9f9494..4d1070d 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -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>, @@ -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!(), diff --git a/test.zr b/test.zr index de7f608..3c3f06e 100644 --- a/test.zr +++ b/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