From 17c51e0ab95c76891c70622f0e5375201be21e93 Mon Sep 17 00:00:00 2001 From: David Ellis Date: Mon, 4 Mar 2024 14:30:52 -0600 Subject: [PATCH] Implement basic method syntax and i16 math functions (#668) --- src/compile.rs | 52 +++++++++++++---------------------- src/program.rs | 39 +++++++++++++++++++++++++-- src/std/root.ln | 11 ++++++++ src/std/root.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 36 deletions(-) diff --git a/src/compile.rs b/src/compile.rs index 8aa6d27fa..397531bd4 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -30,8 +30,8 @@ pub fn compile(source_file: String) -> Result<(), Box> { Ok(()) } -/// The `tors` function is an even thinner wrapper on top of `lntors` that shoves the output into a -/// `.rs` file. +/// The `to_rs` function is an even thinner wrapper on top of `lntors` that shoves the output into +/// a `.rs` file. pub fn to_rs(source_file: String) -> Result<(), Box> { // Generate the rust code to compile let rs_str = lntors(source_file.clone())?; @@ -214,66 +214,50 @@ test!(int8_max => r#" ); test!(int16_add => r#" - from @std/app import start, print, exit - on start { - print(add(toInt16(1), toInt16(2))); - emit exit 0; + export fn main { + print(add(i16(1), i16(2))); }"#; stdout "3\n"; ); test!(int16_sub => r#" - from @std/app import start, print, exit - on start { - print(sub(toInt16(2), toInt16(1))); - emit exit 0; + export fn main { + print(sub(i16(2), i16(1))); }"#; stdout "1\n"; ); test!(int16_mul => r#" - from @std/app import start, print, exit - on start { - print(mul(toInt16(2), toInt16(1))); - emit exit 0; + export fn main { + print(mul(i16(2), i16(1))); }"#; stdout "2\n"; ); test!(int16_div => r#" - from @std/app import start, print, exit - on start { - print(div(toInt16(6), toInt16(2))); - emit exit 0; + export fn main { + print(div(i16(6), i16(2))); }"#; stdout "3\n"; ); test!(int16_mod => r#" - from @std/app import start, print, exit - on start { - print(mod(toInt16(6), toInt16(4))); - emit exit 0; + export fn main{ + print(mod(i16(6), i16(4))); }"#; stdout "2\n"; ); test!(int16_pow => r#" - from @std/app import start, print, exit - on start { - print(pow(toInt16(6), toInt16(2))); - emit exit 0; + export fn main { + print(pow(i16(6), i16(2))); }"#; stdout "36\n"; ); test!(int16_min => r#" - from @std/app import start, print, exit - on start { - min(3.toInt16(), 5.toInt16()).print(); - emit exit 0; + export fn main { + min(3.i16(), 5.i16()).print(); }"#; stdout "3\n"; ); test!(int16_max => r#" - from @std/app import start, print, exit - on start { - max(3.toInt16(), 5.toInt16()).print(); - emit exit 0; + export fn main { + max(3.i16(), 5.i16()).print(); }"#; stdout "5\n"; ); diff --git a/src/program.rs b/src/program.rs index df0bec5a3..17080866a 100644 --- a/src/program.rs +++ b/src/program.rs @@ -455,6 +455,15 @@ fn baseassignablelist_to_microstatements( // it's a syntax error. match baseassignable { parse::BaseAssignable::Variable(var) => { + // If this is not the first portion of the baseassignablelist, then this is either + // a property access or a method call. So we need a reference to last + // microstatement in that case + let prior_value = if let parse::VarSegment::MethodSep(_) = var[0] { + // TODO: Also support array access + microstatements.pop() + } else { + None + }; // The behavior of a variable depends on if there's // anything following after it. Many things following are // invalid syntax, but `FnCall` and `MethodSep` are valid @@ -466,6 +475,10 @@ fn baseassignablelist_to_microstatements( // function that will be called, and populate an array of arg // microstatements for the eventual function call let mut args = Vec::new(); + // If this is a method call, grab the prior value and shove it in first + if let Some(prior_val) = prior_value { + args.push(prior_val); + } for arg in &call.assignablelist { microstatements = withoperatorslist_to_microstatements( arg, @@ -494,8 +507,20 @@ fn baseassignablelist_to_microstatements( } } } + // TODO: Support more than direct method access in the future, probably + // with a separate resolving function + let fn_name = if let parse::VarSegment::MethodSep(_) = var[0] { + let mut out = "".to_string(); + for (i, segment) in var.iter().enumerate() { + if i == 0 { continue; } + out = format!("{}{}", out, segment.to_string()).to_string(); + } + out + } else { + var.iter().map(|segment| segment.to_string()).collect::>().join("").to_string() // TODO: Support method/property/array access eventually + }; microstatements.push(Microstatement::FnCall { - function: var.iter().map(|segment| segment.to_string()).collect::>().join("").to_string(), // TODO: Support method/property/array access eventually + function: fn_name, args, }); // Increment `i` an extra time to skip this entry on the main loop @@ -535,7 +560,7 @@ fn baseassignablelist_to_microstatements( }); } } - parse::BaseAssignable::FnCall(_) => { + parse::BaseAssignable::FnCall(g) => { // If we hit this path, it wasn't consumed by a `variable` path. This is valid only // if it's the first base assignable, in which case it should be treated like // parens for grouping and have only one "argument". All other situations are @@ -543,6 +568,16 @@ fn baseassignablelist_to_microstatements( if i != 0 { return Err(format!("Unexpected grouping {} following {}", baseassignable.to_string(), baseassignablelist[i - 1].to_string()).into()); } + if g.assignablelist.len() != 1 { + return Err(format!("Multiple statements found in {}. Perhaps you should remove that comma?", baseassignable.to_string()).into()); + } + // Happy path, let's get the microstatements from this assignable list + microstatements = withoperatorslist_to_microstatements( + &g.assignablelist[0], + scope, + program, + microstatements, + )?; } parse::BaseAssignable::Constants(c) => { match c { diff --git a/src/std/root.ln b/src/std/root.ln index 5d3a51f17..5fd188f4b 100644 --- a/src/std/root.ln +++ b/src/std/root.ln @@ -13,6 +13,15 @@ export fn mod(a: i8, b: i8): Result binds modi8; export fn pow(a: i8, b: i8): Result binds powi8; export fn min(a: i8, b: i8): i8 binds mini8; export fn max(a: i8, b: i8): i8 binds maxi8; +export fn i16(i: i64): i16 binds i64toi16; +export fn add(a: i16, b: i16): Result binds addi16; +export fn sub(a: i16, b: i16): Result binds subi16; +export fn mul(a: i16, b: i16): Result binds muli16; +export fn div(a: i16, b: i16): Result binds divi16; +export fn mod(a: i16, b: i16): Result binds modi16; +export fn pow(a: i16, b: i16): Result binds powi16; +export fn min(a: i16, b: i16): i16 binds mini16; +export fn max(a: i16, b: i16): i16 binds maxi16; // Process exit-related bindings export type ExitCode binds std::process::ExitCode; @@ -24,6 +33,8 @@ export fn getOrExit(a: Result): i8 binds get_or_exit; // TODO: Support real // Stdout/stderr-related bindings export fn print(str: String) binds println; export fn print(i: i8) binds println; +export fn print(i: i16) binds println; +export fn print(i: Result) binds println_result; export fn print(i: i64) binds println; export event stdout: String; diff --git a/src/std/root.rs b/src/std/root.rs index 204571904..76e9c7a88 100644 --- a/src/std/root.rs +++ b/src/std/root.rs @@ -76,6 +76,70 @@ fn maxi8(a: i8, b: i8) -> i8 { if a > b { a } else { b } } +/// `i64toi16` casts an i64 to an i16. +fn i64toi16(i: i64) -> i16 { + i as i16 +} + +/// `addi16` safely adds two i16s together, returning a Result-wrapped i16 (or an error on overflow) +fn addi16(a: i16, b: i16) -> Result> { + match a.checked_add(b) { + Some(c) => Ok(c), + None => Err("Overflow".into()), + } +} + +/// `subi16` safely subtracts two i16s, returning a Result-wrapped i16 (or an error on underflow) +fn subi16(a: i16, b: i16) -> Result> { + match a.checked_sub(b) { + Some(c) => Ok(c), + None => Err("Underflow".into()), + } +} + +/// `muli16` safely multiplies two i16s, returning a Result-wrapped i16 (or an error on under/overflow) +fn muli16(a: i16, b: i16) -> Result> { + match a.checked_mul(b) { + Some(c) => Ok(c), + None => Err("Underflow or Overflow".into()), + } +} + +/// `divi16` safely divides two i16s, returning a Result-wrapped i16 (or an error on divide-by-zero) +fn divi16(a: i16, b: i16) -> Result> { + match a.checked_div(b) { + Some(c) => Ok(c), + None => Err("Divide-by-zero".into()), + } +} + +/// `modi16` safely divides two i16s, returning a Result-wrapped remainder in i16 (or an error on divide-by-zero) +fn modi16(a: i16, b: i16) -> Result> { + match a.checked_rem(b) { + Some(c) => Ok(c), + None => Err("Divide-by-zero".into()), + } +} + +/// `powi16` safely raises the first i16 to the second i16, returning a Result-wrapped i16 (or an error on under/overflow) +fn powi16(a: i16, b: i16) -> Result> { + // TODO: Support b being negative correctly + match a.checked_pow(b as u32) { + Some(c) => Ok(c), + None => Err("Underflow or Overflow".into()), + } +} + +/// `mini16` returns the smaller of the two i16 values +fn mini16(a: i16, b: i16) -> i16 { + if a < b { a } else { b } +} + +/// `maxi16` returns the larger of the two i16 values +fn maxi16(a: i16, b: i16) -> i16 { + if a > b { a } else { b } +} + /// `get_or_exit` is basically an alias to `unwrap`, but as a function instead of a method fn get_or_exit(a: Result>) -> A { a.unwrap() @@ -86,6 +150,14 @@ fn println(a: A) { println!("{}", a); } +/// `println_result` is a small wrapper function that makes printing Result types easy +fn println_result(a: Result>) { + match a { + Ok(o) => println!("{}", o), + Err(e) => println!("{:?}", e), + }; +} + /// `stdout` is a simple function that prints basically anything without a newline attached fn stdout(a: A) { print!("{}", a);