diff --git a/README.md b/README.md index e2e5879..fb35c7b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A very cool language * No libc required! * Produces tiny static executables (11KB for `hello.zr`) * Has type inference, [UFCS](https://en.wikipedia.org/wiki/Uniform_function_call_syntax), variadics, dynamic arrays, hashmaps, DNS resolver, etc. -* Growing [standard library](https://git.ton1.dev/toni/zern/src/branch/main/src/std) +* Growing [standard library](https://git.ton1.dev/toni/zern/src/branch/main/std) ## Syntax ```rust diff --git a/examples/curl.zr b/examples/curl.zr index 68f474e..4114b80 100644 --- a/examples/curl.zr +++ b/examples/curl.zr @@ -1,3 +1,5 @@ +include "std/net.zr" + func main[argc: i64, argv: ptr] : i64 if argc < 2 io.println("ERROR: url is missing") diff --git a/examples/tcp_server.zr b/examples/tcp_server.zr index 461384a..7f52814 100644 --- a/examples/tcp_server.zr +++ b/examples/tcp_server.zr @@ -1,3 +1,5 @@ +include "std/net.zr" + func main[] : i64 ~s, ok := net.listen("127.0.0.1", 8000) if !ok diff --git a/examples/udp_server.zr b/examples/udp_server.zr index c5f721f..f5ba8ca 100644 --- a/examples/udp_server.zr +++ b/examples/udp_server.zr @@ -1,3 +1,5 @@ +include "std/net.zr" + func main[] : i64 ~s, ok := net.create_udp_server("127.0.0.1", 8000) if !ok diff --git a/src/main.rs b/src/main.rs index 161a605..125b484 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,15 +12,6 @@ use std::{ use tokenizer::ZernError; -macro_rules! parse_std_file { - ($statements:expr, $filename:expr) => { - let source: String = include_str!($filename).into(); - let tokenizer = tokenizer::Tokenizer::new($filename.to_owned(), source); - let parser = parser::Parser::new(tokenizer.tokenize()?); - $statements.extend(parser.parse()?); - }; -} - fn compile_file(args: Args) -> Result<(), ZernError> { let source = match fs::read_to_string(&args.path) { Ok(x) => x, @@ -32,17 +23,10 @@ fn compile_file(args: Args) -> Result<(), ZernError> { let filename = Path::new(&args.path).file_name().unwrap().to_str().unwrap(); - let mut statements = Vec::new(); - - if args.include_stdlib { - parse_std_file!(statements, "std/std.zr"); - parse_std_file!(statements, "std/net.zr"); - parse_std_file!(statements, "std/linux_constants.zr"); - } - - let tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source); + let mut tokenizer = tokenizer::Tokenizer::new(filename.to_owned(), source); + tokenizer.include_file("std/std.zr".into())?; let parser = parser::Parser::new(tokenizer.tokenize()?); - statements.extend(parser.parse()?); + let statements = parser.parse()?; let mut symbol_table = symbol_table::SymbolTable::new(); for stmt in &statements { @@ -114,7 +98,6 @@ struct Args { out: Option, emit_only: bool, emit_debug: bool, - include_stdlib: bool, run_exe: bool, use_crt: bool, cflags: String, @@ -129,7 +112,6 @@ impl Args { out: None, emit_only: false, emit_debug: false, - include_stdlib: true, run_exe: false, use_crt: false, cflags: String::new(), @@ -146,8 +128,6 @@ impl Args { } } 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" { @@ -163,9 +143,7 @@ impl Args { } } } else if arg == "-h" || arg == "--help" { - println!( - "Usage: zern [-o path] [-r] [-m] [-g] [-C cflags] [--emit-only] [--no-stdlib] path" - ); + println!("Usage: zern [-o path] [-r] [-m] [-g] [-C cflags] [--emit-only] path"); process::exit(0); } else if arg.starts_with('-') { eprintln!("\x1b[91mERROR\x1b[0m: unrecognized option: {arg}"); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 7aab9a7..c91f413 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, fmt}; +use std::{cmp::Ordering, fmt, fs, path::Path}; #[derive(Debug, Clone, PartialEq)] pub enum TokenType { @@ -378,6 +378,11 @@ impl Tokenizer { } let lexeme: String = self.source[self.start..self.current].iter().collect(); + + if lexeme == "include" { + return self.scan_include(); + } + self.add_token(match lexeme.as_str() { "const" => TokenType::KeywordConst, "if" => TokenType::KeywordIf, @@ -401,6 +406,49 @@ impl Tokenizer { }) } + fn scan_include(&mut self) -> Result<(), ZernError> { + if !self.match_char(' ') { + return error!(self.loc, "expected a space after 'include'"); + } + + if self.peek() != '"' { + return error!(self.loc, "expected '#' after 'include '"); + } + self.advance(); + + let path_start = self.current; + while !self.eof() && self.peek() != '"' { + self.advance(); + } + + if self.eof() { + return error!(self.loc, "unterminated string after 'include'"); + } + + let path: String = self.source[path_start..self.current].iter().collect(); + self.advance(); // consume closing quote + + self.include_file(path) + } + + // TODO: circular includes lead to "fatal runtime error: stack overflow, aborting" + pub fn include_file(&mut self, path: String) -> Result<(), ZernError> { + let source = match fs::read_to_string(&path) { + Ok(x) => x, + Err(_) => { + return error!(self.loc, format!("failed to include {}", path)); + } + }; + + let filename = Path::new(&path).file_name().unwrap().to_str().unwrap(); + + let tokenizer = Tokenizer::new(filename.to_owned(), source); + self.tokens.extend(tokenizer.tokenize()?); + self.tokens.pop(); // remove inner Eof + + Ok(()) + } + fn match_char(&mut self, expected: char) -> bool { if self.eof() || self.peek() != expected { false diff --git a/src/std/linux_constants.zr b/std/linux_constants.zr similarity index 100% rename from src/std/linux_constants.zr rename to std/linux_constants.zr diff --git a/src/std/net.zr b/std/net.zr similarity index 100% rename from src/std/net.zr rename to std/net.zr diff --git a/src/std/std.zr b/std/std.zr similarity index 99% rename from src/std/std.zr rename to std/std.zr index 016123e..a7ba1c5 100644 --- a/src/std/std.zr +++ b/std/std.zr @@ -1,3 +1,5 @@ +include "std/linux_constants.zr" + func panic[msg: str] : void io.print("PANIC: ") io.println(msg)