From 158664a8efc339382a7ebca9905dfb3f7ab3cac9 Mon Sep 17 00:00:00 2001 From: Noah Graf Date: Sat, 25 Apr 2026 05:17:17 +0000 Subject: [PATCH 1/4] Added the ability to output an object file with the main signature and that literally it --- src/emitter.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++++- src/parser.rs | 4 +-- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/emitter.rs b/src/emitter.rs index d326428..347d69f 100644 --- a/src/emitter.rs +++ b/src/emitter.rs @@ -1,11 +1,34 @@ //! The emitter for turning the abstract syntax tree into Cranelift IR. +use std::{fs::File, io::Write}; + +use cranelift::prelude::*; use cranelift_codegen::{isa, settings}; use cranelift_frontend::FunctionBuilderContext; -use cranelift_module::default_libcall_names; +use cranelift_module::{Linkage, Module, default_libcall_names}; use cranelift_object::{ObjectBuilder, ObjectModule}; use target_lexicon::HOST; +use crate::parser::ast::Program; + +fn main_signature(isa: &dyn isa::TargetIsa) -> Signature { + // The `CallConv` defines how primitives in parameters and return values are handled. + // Mainly which registers are used and when stack spills are used. + // + // In general, it's best to use `CallConv::Fast`. + // + // However, since the function we define is invoked from our targeted OS, we need to use + // the calling convention the OS expects. + let call_conv = isa.default_call_conv(); + + Signature { + call_conv, + params: vec![], + // Since we're linking to libc, we can return the exit code from main. + returns: vec![AbiParam::new(types::I32)], + } +} + /// The emitter state. #[allow(dead_code)] pub struct Emitter { @@ -26,4 +49,65 @@ impl Emitter { function_context: FunctionBuilderContext::new(), }) } + + pub fn emit_program(mut self, program: &Program) -> anyhow::Result> { + let main_func_id = { + let sig = main_signature(self.module.isa()); + + // Add this function to our Module. + self.module + .declare_function("main", Linkage::Export, &sig)? + }; + // These contain the context needed for generating code for a function. + // + // It's a lot more efficient to construct them once, and then re-use them for all functions. + let mut ctx = codegen::Context::new(); + + let mut builder = FunctionBuilder::new(&mut ctx.func, &mut self.function_context); + builder.func.signature = main_signature(self.module.isa()); + + // Create the functions entry block. + let block0 = builder.create_block(); + builder.switch_to_block(block0); + + // When we know that there are no more blocks to be written which may jump to this block, we want to seal + // it. This improves the quality of code generation. + builder.seal_block(block0); + + let one = builder.ins().iconst(types::I32, 1); + let two = builder.ins().iadd(one, one); + + // Use the result of the addition as an exit code + builder.ins().return_(&[two]); + + if let Err(err) = codegen::verify_function(builder.func, self.module.isa()) { + panic!("verifier error: {err}"); + } + + builder.finalize(); + + println!("fn main:\n{}", &ctx.func); + + self.module.define_function(main_func_id, &mut ctx)?; + + ctx.clear(); + + // Finalize the module to generate our `Product`. + // + // If we have additional information such as unwind information or DWARF debug information, + // they can be added to `Product`. For this example, we'll skip such optional additions. + let product = self.module.finish(); + + // Generate the object file. + + let bytes = product.emit()?; + + let fname = "output-a-binary.o"; + let mut f = File::create(fname)?; + f.write_all(&bytes)?; + + println!(" wrote output to {fname}"); + + Ok(bytes) + } } diff --git a/src/parser.rs b/src/parser.rs index 9850eee..fa54674 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,8 +9,8 @@ use crate::{ expression::Expression, }, }; -mod ast; -mod expression; +pub mod ast; +pub mod expression; #[allow(dead_code)] pub struct Parser { From dd69b1fa555d394d5d4172c77de22a0231ae8609 Mon Sep 17 00:00:00 2001 From: Noah Graf Date: Sat, 25 Apr 2026 05:31:16 +0000 Subject: [PATCH 2/4] Added expression emission logic --- src/emitter.rs | 181 ++++++++++++++++++++++++++++++++++----- src/parser/expression.rs | 26 ++++++ 2 files changed, 186 insertions(+), 21 deletions(-) diff --git a/src/emitter.rs b/src/emitter.rs index 347d69f..492d191 100644 --- a/src/emitter.rs +++ b/src/emitter.rs @@ -1,15 +1,16 @@ //! The emitter for turning the abstract syntax tree into Cranelift IR. -use std::{fs::File, io::Write}; - use cranelift::prelude::*; -use cranelift_codegen::{isa, settings}; +use cranelift_codegen::{Context, isa, settings}; use cranelift_frontend::FunctionBuilderContext; use cranelift_module::{Linkage, Module, default_libcall_names}; use cranelift_object::{ObjectBuilder, ObjectModule}; use target_lexicon::HOST; -use crate::parser::ast::Program; +use crate::parser::{ + ast::{Program, Statement}, + expression::{BinaryOperator, Expression, LiteralExpression}, +}; fn main_signature(isa: &dyn isa::TargetIsa) -> Signature { // The `CallConv` defines how primitives in parameters and return values are handled. @@ -34,6 +35,7 @@ fn main_signature(isa: &dyn isa::TargetIsa) -> Signature { pub struct Emitter { module: ObjectModule, function_context: FunctionBuilderContext, + context: Context, } #[allow(unused_variables, dead_code)] @@ -47,6 +49,7 @@ impl Emitter { Ok(Self { module: ObjectModule::new(builder), function_context: FunctionBuilderContext::new(), + context: Context::new(), }) } @@ -56,14 +59,13 @@ impl Emitter { // Add this function to our Module. self.module - .declare_function("main", Linkage::Export, &sig)? + .declare_function("_start", Linkage::Export, &sig)? }; // These contain the context needed for generating code for a function. // // It's a lot more efficient to construct them once, and then re-use them for all functions. - let mut ctx = codegen::Context::new(); - let mut builder = FunctionBuilder::new(&mut ctx.func, &mut self.function_context); + let mut builder = FunctionBuilder::new(&mut self.context.func, &mut self.function_context); builder.func.signature = main_signature(self.module.isa()); // Create the functions entry block. @@ -74,11 +76,15 @@ impl Emitter { // it. This improves the quality of code generation. builder.seal_block(block0); - let one = builder.ins().iconst(types::I32, 1); - let two = builder.ins().iadd(one, one); + for line in &program.lines { + for statement in &line.statements { + Self::emit_statement(&mut builder, statement); + } + } - // Use the result of the addition as an exit code - builder.ins().return_(&[two]); + // returns 0 as a good return value for our program + let zero = builder.ins().iconst(types::I32, 0); + builder.ins().return_(&[zero]); if let Err(err) = codegen::verify_function(builder.func, self.module.isa()) { panic!("verifier error: {err}"); @@ -86,11 +92,10 @@ impl Emitter { builder.finalize(); - println!("fn main:\n{}", &ctx.func); - - self.module.define_function(main_func_id, &mut ctx)?; + self.module + .define_function(main_func_id, &mut self.context)?; - ctx.clear(); + self.context.clear(); // Finalize the module to generate our `Product`. // @@ -99,15 +104,149 @@ impl Emitter { let product = self.module.finish(); // Generate the object file. - let bytes = product.emit()?; + Ok(bytes) + } - let fname = "output-a-binary.o"; - let mut f = File::create(fname)?; - f.write_all(&bytes)?; + fn emit_statement(builder: &mut FunctionBuilder, stmt: &Statement) { + if let Statement::Expression(expr) = stmt { + Self::emit_expression_inner(builder, expr); + } + } - println!(" wrote output to {fname}"); + fn emit_expression_inner(builder: &mut FunctionBuilder, expr: &Expression) -> Value { + match expr { + Expression::Literal(literal) => Self::emit_literal(builder, literal), + Expression::Binary(binary) => { + let left = Self::emit_expression_inner(builder, binary.left()); + let right = Self::emit_expression_inner(builder, binary.right()); + + match binary.operator() { + BinaryOperator::Plus => builder.ins().iadd(left, right), + BinaryOperator::Minus => builder.ins().isub(left, right), + BinaryOperator::Multiply => builder.ins().imul(left, right), + BinaryOperator::Divide => builder.ins().sdiv(left, right), + BinaryOperator::Equal => builder.ins().icmp(IntCC::Equal, left, right), + BinaryOperator::Less => builder.ins().icmp(IntCC::SignedLessThan, left, right), + BinaryOperator::Greater => { + builder.ins().icmp(IntCC::SignedGreaterThan, left, right) + } + BinaryOperator::LessEqual => { + builder + .ins() + .icmp(IntCC::SignedLessThanOrEqual, left, right) + } + BinaryOperator::GreaterEqual => { + builder + .ins() + .icmp(IntCC::SignedGreaterThanOrEqual, left, right) + } + BinaryOperator::NotEqual => builder.ins().icmp(IntCC::NotEqual, left, right), + } + } + Expression::Grouping(grouping) => { + Self::emit_expression_inner(builder, grouping.expression()) + } + Expression::Identifier(identifier) => { + panic!( + "identifier expressions are not supported yet: {}", + identifier.name() + ) + } + } + } - Ok(bytes) + fn emit_literal(builder: &mut FunctionBuilder, literal: &LiteralExpression) -> Value { + match literal { + LiteralExpression::Integer(value) => builder.ins().iconst(types::I32, *value), + LiteralExpression::Char(value) => { + builder.ins().iconst(types::I32, i64::from(*value as u32)) + } + LiteralExpression::Float(_) | LiteralExpression::String(_) => { + panic!("only integer and char literals are supported by the emitter") + } + } + } +} + +#[cfg(test)] +mod tests { + use cranelift::prelude::*; + use cranelift_module::Module; + + use crate::{ + emitter::{Emitter, main_signature}, + parser::expression::{BinaryOperator, Expression}, + }; + + fn emit_expression_ir(expr: &Expression) -> anyhow::Result { + let mut context = cranelift_codegen::Context::new(); + let mut function_context = cranelift_frontend::FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut context.func, &mut function_context); + builder.func.signature = main_signature(Emitter::new()?.module.isa()); + + let block = builder.create_block(); + builder.switch_to_block(block); + builder.seal_block(block); + + let value = Emitter::emit_expression_inner(&mut builder, expr); + builder.ins().return_(&[value]); + builder.finalize(); + + Ok(context.func.to_string()) + } + + #[test] + fn emit_integer_expression() -> anyhow::Result<()> { + let ir = emit_expression_ir(&Expression::integer(42))?; + + assert!(ir.contains("iconst.i32 42")); + assert!(ir.contains("return v0")); + + Ok(()) + } + + #[test] + fn emit_char_expression() -> anyhow::Result<()> { + let ir = emit_expression_ir(&Expression::char('A'))?; + + assert!(ir.contains("iconst.i32 65")); + + Ok(()) + } + + #[test] + fn emit_add_expression() -> anyhow::Result<()> { + let ir = emit_expression_ir(&Expression::binary( + Expression::integer(1), + BinaryOperator::Plus, + Expression::integer(2), + ))?; + + assert!(ir.contains("iadd")); + + Ok(()) + } + + #[test] + fn emit_multiply_expression() -> anyhow::Result<()> { + let ir = emit_expression_ir(&Expression::binary( + Expression::integer(3), + BinaryOperator::Multiply, + Expression::integer(4), + ))?; + + assert!(ir.contains("imul")); + + Ok(()) + } + + #[test] + fn emit_grouping_expression() -> anyhow::Result<()> { + let ir = emit_expression_ir(&Expression::grouping(Expression::integer(7)))?; + + assert!(ir.contains("iconst.i32 7")); + + Ok(()) } } diff --git a/src/parser/expression.rs b/src/parser/expression.rs index bab5a48..2973835 100644 --- a/src/parser/expression.rs +++ b/src/parser/expression.rs @@ -118,6 +118,32 @@ impl Expression { } } +impl IdentifierExpression { + pub fn name(&self) -> &str { + &self.name + } +} + +impl BinaryExpression { + pub fn left(&self) -> &Expression { + &self.left + } + + pub fn operator(&self) -> &BinaryOperator { + &self.operator + } + + pub fn right(&self) -> &Expression { + &self.right + } +} + +impl GroupingExpression { + pub fn expression(&self) -> &Expression { + &self.expression + } +} + impl BinaryOperator { /// Returns the binding precedence for this operator. pub fn precedence(&self) -> u8 { From 0cd5b25cd5af95ae2a9c3829f86e70d5188c7ff2 Mon Sep 17 00:00:00 2001 From: Noah Graf Date: Sat, 25 Apr 2026 05:34:15 +0000 Subject: [PATCH 3/4] Added return emission --- src/emitter.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/emitter.rs b/src/emitter.rs index 492d191..259c746 100644 --- a/src/emitter.rs +++ b/src/emitter.rs @@ -109,8 +109,16 @@ impl Emitter { } fn emit_statement(builder: &mut FunctionBuilder, stmt: &Statement) { - if let Statement::Expression(expr) = stmt { - Self::emit_expression_inner(builder, expr); + match stmt { + // emits return logic if we hit something like this + Statement::End | Statement::Return => { + let zero = builder.ins().iconst(types::I32, 0); + builder.ins().return_(&[zero]); + } + Statement::Expression(expr) => { + Self::emit_expression_inner(builder, expr); + } + _ => todo!(), } } From 019d3171199bbebf8cedd3ba9444b9711f0171d7 Mon Sep 17 00:00:00 2001 From: Noah Graf Date: Sat, 25 Apr 2026 05:43:00 +0000 Subject: [PATCH 4/4] Overhauled main entrypoint to use clap to actually be able to emit code --- Cargo.lock | 144 +++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/main.rs | 103 ++++++++++++++++++++++++-- src/parser.rs | 8 +-- tests/input/basic_math.bsc | 1 + 5 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 tests/input/basic_math.bsc diff --git a/Cargo.lock b/Cargo.lock index 74cffee..4f47f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,56 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -25,6 +75,7 @@ name = "bcomp" version = "0.1.0" dependencies = [ "anyhow", + "clap", "cranelift", "cranelift-codegen", "cranelift-frontend", @@ -49,6 +100,52 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "cranelift" version = "0.131.0" @@ -284,6 +381,12 @@ dependencies = [ "hashbrown 0.17.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "libm" version = "0.2.16" @@ -347,6 +450,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -358,18 +467,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -435,6 +544,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -486,6 +601,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasmtime-internal-core" version = "44.0.0" @@ -496,6 +617,21 @@ dependencies = [ "libm", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "winnow" version = "0.7.12" diff --git a/Cargo.toml b/Cargo.toml index 17b5c4d..26631a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] anyhow = "1.0.100" +clap = { version = "4.6.1", features = ["derive"] } ntest = "0.9.3" cranelift = "0.131.0" cranelift-codegen = "0.131.0" diff --git a/src/main.rs b/src/main.rs index 0244f19..5f5e12c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,14 +5,109 @@ //! A compiler for the basic language -use crate::parser::Parser; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use anyhow::Context; +use clap::Parser as ClapParser; + +use crate::{emitter::Emitter, parser::Parser}; mod emitter; mod lexer; mod parser; -fn main() { - let mut parser = Parser::from_input("1"); +fn main() -> anyhow::Result<()> { + let options = CompileOptions::parse(); + compile_file(&options)?; + + println!("wrote {}", options.output_path().to_string_lossy()); + + Ok(()) +} + +#[derive(ClapParser)] +#[command(version, about)] +struct CompileOptions { + /// BASIC source file to compile. + input_path: PathBuf, + + /// Object file to write. + #[arg(short, long)] + output: Option, +} + +impl CompileOptions { + fn output_path(&self) -> PathBuf { + self.output + .clone() + .unwrap_or_else(|| default_output_path(&self.input_path)) + } +} + +fn default_output_path(input_path: &Path) -> PathBuf { + input_path.with_extension("o") +} + +fn compile_file(options: &CompileOptions) -> anyhow::Result<()> { + let source = fs::read_to_string(&options.input_path).with_context(|| { + format!( + "failed to read input file {}", + options.input_path.to_string_lossy() + ) + })?; + + let mut parser = Parser::from_input(&source); + let program = parser.parse_program(); + let bytes = Emitter::new()?.emit_program(&program)?; + + let output_path = options.output_path(); + fs::write(&output_path, bytes).with_context(|| { + format!( + "failed to write output file {}", + output_path.to_string_lossy() + ) + })?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use clap::Parser; + + use crate::{CompileOptions, default_output_path}; + + #[test] + fn default_output_replaces_extension() { + assert_eq!( + default_output_path(PathBuf::from("tests/input/print.bsc").as_path()), + PathBuf::from("tests/input/print.o") + ); + } + + #[test] + fn compile_options_use_default_output() -> anyhow::Result<()> { + let options = CompileOptions::try_parse_from(["bcomp", "program.bsc"])?; + + assert_eq!(options.input_path, PathBuf::from("program.bsc")); + assert_eq!(options.output_path(), PathBuf::from("program.o")); + + Ok(()) + } + + #[test] + fn compile_options_accept_output_flag() -> anyhow::Result<()> { + let options = + CompileOptions::try_parse_from(["bcomp", "program.bsc", "-o", "build/program.o"])?; + + assert_eq!(options.input_path, PathBuf::from("program.bsc")); + assert_eq!(options.output_path(), PathBuf::from("build/program.o")); - let _program = parser.parse_program(); + Ok(()) + } } diff --git a/src/parser.rs b/src/parser.rs index fa54674..8335d7f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,14 +13,14 @@ pub mod ast; pub mod expression; #[allow(dead_code)] -pub struct Parser { - lexer: Lexer<'static>, +pub struct Parser<'a> { + lexer: Lexer<'a>, position: usize, } #[allow(dead_code)] -impl Parser { - pub fn from_input(input: &'static str) -> Self { +impl<'a> Parser<'a> { + pub fn from_input(input: &'a str) -> Self { let lexer = Lexer::new(input); Self { lexer, position: 0 } } diff --git a/tests/input/basic_math.bsc b/tests/input/basic_math.bsc new file mode 100644 index 0000000..6912615 --- /dev/null +++ b/tests/input/basic_math.bsc @@ -0,0 +1 @@ +1; \ No newline at end of file