Skip to content

Commit

Permalink
Implement basic method syntax and i16 math functions (#668)
Browse files Browse the repository at this point in the history
  • Loading branch information
dfellis authored Mar 4, 2024
1 parent 8aef363 commit 17c51e0
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 36 deletions.
52 changes: 18 additions & 34 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub fn compile(source_file: String) -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
// Generate the rust code to compile
let rs_str = lntors(source_file.clone())?;
Expand Down Expand Up @@ -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";
);
Expand Down
39 changes: 37 additions & 2 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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::<Vec<String>>().join("").to_string() // TODO: Support method/property/array access eventually
};
microstatements.push(Microstatement::FnCall {
function: var.iter().map(|segment| segment.to_string()).collect::<Vec<String>>().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
Expand Down Expand Up @@ -535,14 +560,24 @@ 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
// invalid syntax.
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 {
Expand Down
11 changes: 11 additions & 0 deletions src/std/root.ln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export fn mod(a: i8, b: i8): Result<i8> binds modi8;
export fn pow(a: i8, b: i8): Result<i8> 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<i16> binds addi16;
export fn sub(a: i16, b: i16): Result<i16> binds subi16;
export fn mul(a: i16, b: i16): Result<i16> binds muli16;
export fn div(a: i16, b: i16): Result<i16> binds divi16;
export fn mod(a: i16, b: i16): Result<i16> binds modi16;
export fn pow(a: i16, b: i16): Result<i16> 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;
Expand All @@ -24,6 +33,8 @@ export fn getOrExit(a: Result<i8>): 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<i16>) binds println_result;
export fn print(i: i64) binds println;

export event stdout: String;
Expand Down
72 changes: 72 additions & 0 deletions src/std/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i16, Box<dyn std::error::Error>> {
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<i16, Box<dyn std::error::Error>> {
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<i16, Box<dyn std::error::Error>> {
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<i16, Box<dyn std::error::Error>> {
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<i16, Box<dyn std::error::Error>> {
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<i16, Box<dyn std::error::Error>> {
// 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>(a: Result<A, Box<dyn std::error::Error>>) -> A {
a.unwrap()
Expand All @@ -86,6 +150,14 @@ fn println<A: std::fmt::Display>(a: A) {
println!("{}", a);
}

/// `println_result` is a small wrapper function that makes printing Result types easy
fn println_result<A: std::fmt::Display>(a: Result<A, Box<dyn std::error::Error>>) {
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: std::fmt::Display>(a: A) {
print!("{}", a);
Expand Down

0 comments on commit 17c51e0

Please sign in to comment.