Initial commit
commit
cdc6449f53
|
@ -0,0 +1,6 @@
|
||||||
|
/target
|
||||||
|
calc
|
||||||
|
lex.yy.*
|
||||||
|
grammar.tab.*
|
||||||
|
main.o
|
||||||
|
rust.h
|
|
@ -0,0 +1,48 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anydice"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"term_size",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.148"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "term_size"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "anydice"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
term_size = "0.3.2"
|
|
@ -0,0 +1,47 @@
|
||||||
|
CC := gcc
|
||||||
|
LIBRARIES := -lfl -lm -lreadline
|
||||||
|
|
||||||
|
calc: main.o grammar.tab.o lex.yy.o target/release/libanydice.a
|
||||||
|
$(CC) -o calc $^ $(LIBRARIES)
|
||||||
|
|
||||||
|
main.o: main.c grammar.tab.h
|
||||||
|
$(CC) -c $^
|
||||||
|
|
||||||
|
lex.yy.o: lex.yy.c
|
||||||
|
$(CC) -c $^
|
||||||
|
|
||||||
|
lex.yy.c: tokens.lex
|
||||||
|
flex -o lex.yy.c $^
|
||||||
|
|
||||||
|
grammar.tab.o: grammar.tab.c
|
||||||
|
$(CC) -c $^
|
||||||
|
|
||||||
|
grammar.tab.c: grammar.y rust.h
|
||||||
|
bison -d $<
|
||||||
|
|
||||||
|
grammar.tab.h: grammar.y rust.h
|
||||||
|
bison -d $<
|
||||||
|
|
||||||
|
.PHONY: rust
|
||||||
|
rust:
|
||||||
|
cargo build -r
|
||||||
|
cbindgen -o rust.h -l c
|
||||||
|
|
||||||
|
rust.h: rust
|
||||||
|
|
||||||
|
target/release/libanydice.a: rust
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f lex.yy.*
|
||||||
|
rm -f grammar.tab.*
|
||||||
|
rm -f main.o calc
|
||||||
|
rm -f rust.h
|
||||||
|
|
||||||
|
.PHONY: sr
|
||||||
|
sr: grammar.y
|
||||||
|
bison -d -Wcounterexamples $<
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
cargo clippy --no-deps
|
|
@ -0,0 +1,157 @@
|
||||||
|
%{
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "rust.h"
|
||||||
|
#define YYSTYPE BSTNode
|
||||||
|
|
||||||
|
int yyerror(char*);
|
||||||
|
extern Frame* current_frame;
|
||||||
|
extern Frame* main_frame;
|
||||||
|
extern Functions* global_functions;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%token NUMBER IDENT ASSIGN SPACE BLOCKSTART BLOCKEND DICE RETURN FUNCTION DROP
|
||||||
|
%token PLUS MINUS MUL DIV POWER FLATMUL LESS LESSEQ EQ GREATEREQ GREATER
|
||||||
|
%token LEFT RIGHT
|
||||||
|
%token END ARGSEP
|
||||||
|
|
||||||
|
%left LESS LESSEQ EQ GREATEREQ GREATER
|
||||||
|
%left PLUS MINUS
|
||||||
|
%left MUL DIV FLATMUL
|
||||||
|
%left NEG
|
||||||
|
%left BAR FACT
|
||||||
|
%right POWER
|
||||||
|
|
||||||
|
%start Input
|
||||||
|
%%
|
||||||
|
|
||||||
|
Input:
|
||||||
|
/* empty input */
|
||||||
|
| Input Line
|
||||||
|
;
|
||||||
|
|
||||||
|
Line:
|
||||||
|
END
|
||||||
|
// echo expressions to stdout
|
||||||
|
| Expression END {
|
||||||
|
if (current_frame == main_frame)
|
||||||
|
print_node(main_frame, $1, global_functions);
|
||||||
|
else
|
||||||
|
insert_instruction(current_frame, evaluate($1));
|
||||||
|
}
|
||||||
|
// assignment
|
||||||
|
| IDENT ASSIGN Expression END {
|
||||||
|
if (current_frame == main_frame)
|
||||||
|
assign_node(main_frame, $1, $3, global_functions);
|
||||||
|
else
|
||||||
|
insert_instruction(current_frame, assign($1, $3));
|
||||||
|
}
|
||||||
|
// return something
|
||||||
|
| RETURN Expression END {
|
||||||
|
if (current_frame == main_frame)
|
||||||
|
fprintf(stderr, "cannot return from repl\n");
|
||||||
|
else
|
||||||
|
insert_instruction(current_frame, return_value($2));
|
||||||
|
}
|
||||||
|
// return nothing
|
||||||
|
| RETURN END {
|
||||||
|
if (current_frame == main_frame)
|
||||||
|
fprintf(stderr, "cannot return from repl\n");
|
||||||
|
else
|
||||||
|
insert_instruction(current_frame, return_nothing());
|
||||||
|
}
|
||||||
|
|
||||||
|
// function definition
|
||||||
|
| FUNCTION IDENT LEFT Arglist RIGHT BLOCKSTART END {
|
||||||
|
current_frame = make_frame_from_declaration($2, $4);
|
||||||
|
}
|
||||||
|
// and function finish
|
||||||
|
| BLOCKEND END {
|
||||||
|
add_function(global_functions, current_frame);
|
||||||
|
current_frame = main_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory management
|
||||||
|
| DROP IDENT END {
|
||||||
|
drop_bst($2, current_frame);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
Expression:
|
||||||
|
// terminal nodes: a literal number and a variable
|
||||||
|
NUMBER { $$ = yylval; }
|
||||||
|
| IDENT { $$ = yylval; }
|
||||||
|
| DICE { $$ = yylval; }
|
||||||
|
|
||||||
|
// term separators
|
||||||
|
| Expression PLUS Expression { $$ = bst_add($1, $3); }
|
||||||
|
| Expression MINUS Expression { $$ = bst_sub($1, $3); }
|
||||||
|
|
||||||
|
// there are a surprising number of ways to multiply
|
||||||
|
| Expression MUL Expression { $$ = bst_mul($1, $3); }
|
||||||
|
| LEFT Expression RIGHT LEFT Expression RIGHT { $$ = bst_mul($2, $5); }
|
||||||
|
| Expression FLATMUL Expression { $$ = bst_flatmul($1, $3); }
|
||||||
|
|
||||||
|
// division
|
||||||
|
| Expression DIV Expression { $$ = bst_div($1, $3); }
|
||||||
|
|
||||||
|
| Expression POWER Expression { $$ = bst_pow($1, $3); }
|
||||||
|
|
||||||
|
| MINUS Expression %prec NEG { $$ = bst_neg($2); }
|
||||||
|
|
||||||
|
// factorial, absolute value
|
||||||
|
| Expression FACT { $$ = bst_fact($1); }
|
||||||
|
| BAR Expression BAR { $$ = bst_abs($2); }
|
||||||
|
|
||||||
|
// parenthesis
|
||||||
|
| LEFT Expression RIGHT { $$ = $2; }
|
||||||
|
|
||||||
|
// function call
|
||||||
|
| IDENT LEFT Arglist RIGHT {
|
||||||
|
$$ = call_function($1, $3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// comparison operators
|
||||||
|
| Expression LESS Expression {
|
||||||
|
|
||||||
|
}
|
||||||
|
| Expression LESSEQ Expression {
|
||||||
|
|
||||||
|
}
|
||||||
|
| Expression EQ Expression {
|
||||||
|
|
||||||
|
}
|
||||||
|
| Expression GREATEREQ Expression {
|
||||||
|
|
||||||
|
}
|
||||||
|
| Expression GREATER Expression {
|
||||||
|
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
Arglist:
|
||||||
|
/* empty arg list */ {
|
||||||
|
$$ = no_arg();
|
||||||
|
}
|
||||||
|
| NonEmptyArglist {
|
||||||
|
$$ = $1;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
NonEmptyArglist:
|
||||||
|
Expression { $$ = one_arg($1); }
|
||||||
|
| NonEmptyArglist ARGSEP Expression {
|
||||||
|
$$ = add_arg($1, $3);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
int yyerror(char *s) {
|
||||||
|
printf("%s\n", s);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "rust.h"
|
||||||
|
#include "grammar.tab.h"
|
||||||
|
|
||||||
|
Frame* current_frame;
|
||||||
|
Frame* main_frame;
|
||||||
|
Functions* global_functions;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
main_frame = make_frame(yytext_from_cstr("main"));
|
||||||
|
current_frame = main_frame;
|
||||||
|
global_functions = make_global_functions();
|
||||||
|
|
||||||
|
if (yyparse() != 0)
|
||||||
|
fprintf(stderr, "error found.\n");
|
||||||
|
|
||||||
|
destroy_frame(main_frame);
|
||||||
|
destroy_global_functions(global_functions);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::mem::swap;
|
||||||
|
|
||||||
|
use crate::result::MathResult;
|
||||||
|
use crate::distribution::Distribution;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Die {
|
||||||
|
num: u32,
|
||||||
|
sides: u32,
|
||||||
|
}
|
||||||
|
impl FromStr for Die {
|
||||||
|
type Err = MathResult<Infallible>;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parts: Vec<&str> = s.split('d').collect();
|
||||||
|
let num = match parts[0].parse() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return Err(MathResult::ParseError),
|
||||||
|
};
|
||||||
|
let sides = match parts[1].parse() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return Err(MathResult::ParseError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
num,
|
||||||
|
sides,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Die {
|
||||||
|
pub fn distribution(self) -> MathResult<Distribution> {
|
||||||
|
distribute(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the distribution of a given set of dice. This returns the resulting
|
||||||
|
/// vector of counts, along with the minimum roll. The second tuple field if used
|
||||||
|
/// for displaying the results
|
||||||
|
fn distribute(d: &Die) -> MathResult<Distribution> {
|
||||||
|
// "flat dice" is the dice, but flattened. it's just useful to strip away the unnecessary
|
||||||
|
// information
|
||||||
|
let flat_dice = vec![d.sides; d.num as usize];
|
||||||
|
|
||||||
|
// the minimum value is the length of flat_dice, as that is the value
|
||||||
|
// when everything rolls a one. this is used as part of the return value
|
||||||
|
let min = flat_dice.len();
|
||||||
|
// the maximum is used for sizing the vectors. it is the sum of every
|
||||||
|
// dice's number of sides, which would be the maximum roll
|
||||||
|
let max = flat_dice.iter().sum::<u32>() as usize;
|
||||||
|
// the result and internal vectors are both initialized with counts of 0
|
||||||
|
// for every roll.
|
||||||
|
let mut result = vec![0_usize; max-min+1];
|
||||||
|
let mut internal = vec![0_usize; max-min+1];
|
||||||
|
|
||||||
|
// initialize result by putting ones in all the first die's rolls
|
||||||
|
let mut iter = flat_dice.into_iter();
|
||||||
|
let init_sides = match iter.next() {
|
||||||
|
None => return MathResult::Ok(Distribution::new()),
|
||||||
|
Some(n) => n,
|
||||||
|
};
|
||||||
|
for roll in 0..init_sides {
|
||||||
|
result[roll as usize] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the meat of the algorithm goes like this:
|
||||||
|
|
||||||
|
// for each die...
|
||||||
|
for sides in iter {
|
||||||
|
// for each previous non-zero value we had...
|
||||||
|
for (prevroll, &prevcount) in result.iter().enumerate() {
|
||||||
|
if prevcount == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each possible roll of the dice...
|
||||||
|
for roll in 1..=sides {
|
||||||
|
// increment the internal count by the previous count
|
||||||
|
let idx = prevroll + roll as usize-1;
|
||||||
|
internal[idx] = match internal[idx].checked_add(prevcount) {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return MathResult::Overflow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap the two vectors to effectively write `internal` to `result`
|
||||||
|
swap(&mut result, &mut internal);
|
||||||
|
// fill internal with zeros, ready for the next loop
|
||||||
|
internal.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iter = result.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(roll, count)| ((min + roll) as i32, count as u128));
|
||||||
|
let result = Distribution::from_iter(iter);
|
||||||
|
|
||||||
|
MathResult::Ok(result)
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hint::unreachable_unchecked;
|
||||||
|
use crate::number::{Integer, Number};
|
||||||
|
use crate::result::{MathResult, IntoMath};
|
||||||
|
|
||||||
|
/// This struct is for deliberately putting a HashMap on the heap
|
||||||
|
/// this is done so that cbindgen can represent it as a single pointer
|
||||||
|
/// and it can be used in an ffi-safe manner.
|
||||||
|
#[allow(clippy::box_collection)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct Distribution {
|
||||||
|
map: Box<HashMap<Integer, u128>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combine two distributions by mapping the original distribution through a map function
|
||||||
|
/// The map operation is ?ed to propagate errors
|
||||||
|
fn map_distribution<F>(src: Distribution, mut op: F) -> MathResult<Distribution>
|
||||||
|
where F: FnMut(Integer) -> MathResult<Integer> {
|
||||||
|
let mut result = HashMap::with_capacity(src.map.len());
|
||||||
|
|
||||||
|
for (roll, count) in src {
|
||||||
|
let key = op(roll)?;
|
||||||
|
let entry = result.entry(key).or_insert(0);
|
||||||
|
*entry = count.checked_add(*entry).into_math(MathResult::Overflow)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
MathResult::Ok( Distribution::from_map(result) )
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_distribution<F>(a: Distribution, b: Distribution, mut op: F) -> MathResult<Distribution>
|
||||||
|
where F: FnMut(Integer, Integer) -> MathResult<Integer> {
|
||||||
|
let mut result = HashMap::new(); // TODO! HashMap::with_capacity()
|
||||||
|
|
||||||
|
for (roll1, count1) in a {
|
||||||
|
for (&roll2, &count2) in &b {
|
||||||
|
let key = op(roll1, roll2)?;
|
||||||
|
let value = count1.checked_mul(count2).into_math(MathResult::Overflow)?;
|
||||||
|
let entry = result.entry(key).or_insert(0);
|
||||||
|
*entry = value.checked_add(*entry).into_math(MathResult::Overflow)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MathResult::Ok( Distribution::from_map(result) )
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Distribution {
|
||||||
|
/// Return the default: an empty distribution, i.e. one that
|
||||||
|
/// does not allocate anything on the heap. This may be undesirable,
|
||||||
|
/// such as when you are using it as an "identitiy" element, it will
|
||||||
|
/// actually act like a nullity element, and all operation involving
|
||||||
|
/// it will return the "null distribution". For an identity distribution,
|
||||||
|
/// use Distribution::identity().
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the additive identity distribution, i.e. the one where adding
|
||||||
|
/// any other distribution will return the other distribution
|
||||||
|
/// (not literally, mind you, just an equivalent distribution).
|
||||||
|
/// This *does* allocate. Specifically, it creates a distribution with
|
||||||
|
/// 0 having appeared once.
|
||||||
|
/// # A note on indirection
|
||||||
|
/// because this must be used in ffi, the HashMap (which already puts
|
||||||
|
/// its objects on the heap) must also be put in a box, meaning a single
|
||||||
|
/// read is three levels of indirection (box -> hashmap -> bucket -> object)
|
||||||
|
pub fn identitiy() -> Self {
|
||||||
|
let map = HashMap::from([(0, 1)]);
|
||||||
|
Self {
|
||||||
|
map: Box::new(map),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_map(map: HashMap<Integer, u128>) -> Self {
|
||||||
|
Self {
|
||||||
|
map: Box::new(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_int(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
// I don't want to define all of this in the second argument
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_add(rhs).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
pub fn add_distribution(self, rhs: Self) -> MathResult<Number> {
|
||||||
|
let op = |roll1: Integer, roll2: Integer| {
|
||||||
|
roll1.checked_add(roll2).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = combine_distribution(self, rhs, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_int(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_sub(rhs).into_math(MathResult::Underflow)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
pub fn sub_distribution(self, rhs: Self) -> MathResult<Number> {
|
||||||
|
let op = |roll1: Integer, roll2: Integer| {
|
||||||
|
roll1.checked_sub(roll2).into_math(MathResult::Underflow)
|
||||||
|
};
|
||||||
|
let distr = combine_distribution(self, rhs, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul_int(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_mul(rhs).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
pub fn mul_distribution(self, rhs: Self) -> MathResult<Number> {
|
||||||
|
let op = |roll1: Integer, roll2: Integer| {
|
||||||
|
roll1.checked_mul(roll2).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = combine_distribution(self, rhs, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flatmul(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
let mut result = Distribution::identitiy();
|
||||||
|
let map_op = match rhs {
|
||||||
|
Integer::MIN ..= -1 => Self::sub_distribution,
|
||||||
|
0 => return MathResult::Ok(Number::Dice(result)),
|
||||||
|
1..=Integer::MAX => Self::add_distribution,
|
||||||
|
};
|
||||||
|
let times = rhs.checked_abs().into_math(MathResult::Overflow)?;
|
||||||
|
for _ in 0..times {
|
||||||
|
result = match map_op(result, self.clone())? {
|
||||||
|
Number::Dice(d) => d,
|
||||||
|
Number::Int(_) => unsafe { unreachable_unchecked() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
MathResult::Ok(Number::Dice(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div_int(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_div(rhs).into_math(MathResult::ZeroDivision)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
pub fn div_distribution(self, rhs: Self) -> MathResult<Number> {
|
||||||
|
let op = |roll1: Integer, roll2: Integer| {
|
||||||
|
roll1.checked_div(roll2).into_math(MathResult::ZeroDivision)
|
||||||
|
};
|
||||||
|
let distr = combine_distribution(self, rhs, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pow_int(self, rhs: Integer) -> MathResult<Number> {
|
||||||
|
let exp = u32::try_from(rhs).into_math(MathResult::OutOfRange)?;
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_pow(exp).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
pub fn pow_distribution(self, rhs: Self) -> MathResult<Number> {
|
||||||
|
let op = |roll1: Integer, roll2: Integer| {
|
||||||
|
let exp = u32::try_from(roll2).into_math(MathResult::OutOfRange)?;
|
||||||
|
roll1.checked_pow(exp).into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = combine_distribution(self, rhs, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolute(self) -> MathResult<Number> {
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
roll.checked_abs().into_math(MathResult::Overflow)
|
||||||
|
};
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn factorial(self) -> MathResult<Number> {
|
||||||
|
let op = |roll: Integer| {
|
||||||
|
if roll < 0 {
|
||||||
|
return MathResult::OutOfRange;
|
||||||
|
}
|
||||||
|
let mut result: Integer = 1;
|
||||||
|
|
||||||
|
for n in 2..=roll {
|
||||||
|
result = result.checked_mul(n).into_math(MathResult::Overflow)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
MathResult::Ok(result)
|
||||||
|
};
|
||||||
|
|
||||||
|
let distr = map_distribution(self, op)?;
|
||||||
|
MathResult::Ok(Number::Dice(distr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Dispay implementation
|
||||||
|
impl Distribution {
|
||||||
|
pub fn display(&self) -> MathResult<String> {
|
||||||
|
let mut rows = Vec::with_capacity(self.map.len());
|
||||||
|
let total = self.map.values().sum::<u128>();
|
||||||
|
|
||||||
|
let just_len = self.map.keys()
|
||||||
|
.map(|n| n.to_string().len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
// width is how much space we have to play with in the bars, its the total width of stdout...
|
||||||
|
let width = match term_size::dimensions_stdout() {
|
||||||
|
Some((width, _height)) => width,
|
||||||
|
None => return MathResult::PrintError,
|
||||||
|
}
|
||||||
|
// minus the width of the justification...
|
||||||
|
- just_len
|
||||||
|
// minus the width of the percentage itself, the % sign, and the " │ " separator.
|
||||||
|
- 7;
|
||||||
|
|
||||||
|
let as_128 = width as u128;
|
||||||
|
let factor =
|
||||||
|
if let Some(&biggest_problem) = self.map.values().filter(|&&n| n > as_128).max() {
|
||||||
|
(width - just_len - 3) as f64 / biggest_problem as f64
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut distribution = self.map.iter()
|
||||||
|
.filter(|&(_, &count)| count > 0)
|
||||||
|
.map(|(&roll, &count)| (roll, count))
|
||||||
|
.collect::< Vec<_> >();
|
||||||
|
distribution.sort_unstable_by_key(|tuple| tuple.0);
|
||||||
|
|
||||||
|
for (roll, count) in distribution {
|
||||||
|
let repeat_len = count as f64 * factor;
|
||||||
|
let bar = "・".repeat(repeat_len as usize);
|
||||||
|
let percent = 100.0 * count as f64 / total as f64;
|
||||||
|
let row = format!("{percent:>5.2}% {roll:<just_len$} │ {bar}");
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
MathResult::Ok(format!("\n{}", rows.join("\n")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Distribution {
|
||||||
|
type Item = (Integer, u128);
|
||||||
|
type IntoIter = <HashMap<Integer, u128> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.map.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> IntoIterator for &'a Distribution {
|
||||||
|
type Item = (&'a Integer, &'a u128);
|
||||||
|
type IntoIter = <&'a HashMap<Integer, u128> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.map.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromIterator<(Integer, u128)> for Distribution {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where T: IntoIterator<Item = (Integer, u128)> {
|
||||||
|
Self {
|
||||||
|
map: Box::new(iter.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::mem::{forget, take};
|
||||||
|
|
||||||
|
use crate::BSTNode;
|
||||||
|
use crate::yytext::YYText;
|
||||||
|
use crate::stackframe::Frame;
|
||||||
|
|
||||||
|
pub type Functions = HashMap<YYText, Frame>;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn call_function(function_name: BSTNode, operands: BSTNode) -> BSTNode {
|
||||||
|
let args = match operands {
|
||||||
|
BSTNode::Arglist(/* box */args) => args,
|
||||||
|
other => Box::new(vec![other]),
|
||||||
|
};
|
||||||
|
let fname = unsafe { function_name.identifier().unwrap_unchecked() };
|
||||||
|
BSTNode::FunctionCall(fname, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn add_function(functions: &mut Functions, frame: *const Frame) {
|
||||||
|
let mut f = unsafe { frame.read() };
|
||||||
|
let name = take(&mut f.name);
|
||||||
|
functions.insert(name, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn make_global_functions() -> *mut Functions {
|
||||||
|
let mut b = Box::<Functions>::default();
|
||||||
|
let result = b.as_mut() as *mut Functions;
|
||||||
|
forget(b);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn destroy_global_functions(f: *mut Functions) {
|
||||||
|
drop( unsafe { Box::from_raw(f) } );
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn insert_function(global_functions: *mut Functions, identifier: YYText) {
|
||||||
|
let name = identifier.clone();
|
||||||
|
unsafe {
|
||||||
|
global_functions.as_mut().unwrap_unchecked()
|
||||||
|
}.insert(identifier, Frame::new(name));
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::BSTNode;
|
||||||
|
use crate::yytext::YYText;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Instruction {
|
||||||
|
Evaluate(BSTNode),
|
||||||
|
Assign(YYText, BSTNode),
|
||||||
|
Return(BSTNode),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn evaluate(root: BSTNode) -> Instruction {
|
||||||
|
Instruction::Evaluate(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn assign(ident: BSTNode, root: BSTNode) -> Instruction {
|
||||||
|
Instruction::Assign(
|
||||||
|
unsafe { ident.identifier().unwrap_unchecked() },
|
||||||
|
root
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn return_value(value: BSTNode) -> Instruction {
|
||||||
|
Instruction::Return(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn return_nothing() -> Instruction {
|
||||||
|
Instruction::Return(BSTNode::Void)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn add_arg(args: BSTNode, new: BSTNode) -> BSTNode {
|
||||||
|
let args = match (args, new) {
|
||||||
|
(BSTNode::Arglist(mut args1), BSTNode::Arglist(args2)) => {
|
||||||
|
args1.extend(*args2);
|
||||||
|
args1
|
||||||
|
},
|
||||||
|
(BSTNode::Arglist(mut args), other)
|
||||||
|
| (other, BSTNode::Arglist(mut args)) => {
|
||||||
|
args.push(other);
|
||||||
|
args
|
||||||
|
},
|
||||||
|
(other1, other2) => Box::new(vec![other1, other2]),
|
||||||
|
};
|
||||||
|
|
||||||
|
BSTNode::Arglist(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn no_arg() -> BSTNode {
|
||||||
|
BSTNode::Arglist(Box::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn one_arg(arg: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Arglist(Box::new(vec![arg]))
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
#![feature(try_trait_v2)]
|
||||||
|
|
||||||
|
// modules
|
||||||
|
mod dice;
|
||||||
|
mod distribution;
|
||||||
|
mod instruction;
|
||||||
|
mod stackframe;
|
||||||
|
mod yytext;
|
||||||
|
mod ops;
|
||||||
|
mod result;
|
||||||
|
mod number;
|
||||||
|
mod functions;
|
||||||
|
|
||||||
|
use dice::Die;
|
||||||
|
use functions::Functions;
|
||||||
|
use yytext::YYText;
|
||||||
|
use stackframe::Frame;
|
||||||
|
use result::MathResult;
|
||||||
|
use number::{Number, Integer};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum BSTNode {
|
||||||
|
Literal(YYText),
|
||||||
|
Variable(YYText),
|
||||||
|
// called BSTDie becaues cbindgen will generate
|
||||||
|
// an enum called Die twice otherwise. this avoids that conflict
|
||||||
|
BSTDie(YYText),
|
||||||
|
|
||||||
|
/// Unary operations like negation and factorial
|
||||||
|
Unary {
|
||||||
|
op: UnaryOperation,
|
||||||
|
operand: Box<BSTNode>,
|
||||||
|
},
|
||||||
|
/// Binary Operations like plus and subtract
|
||||||
|
Binary {
|
||||||
|
op: BinaryOperation,
|
||||||
|
left: Box<BSTNode>,
|
||||||
|
right: Box<BSTNode>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This is funky, it's meant to be an arg list
|
||||||
|
Arglist(Box<Vec<BSTNode>>),
|
||||||
|
FunctionCall(YYText, Box<Vec<BSTNode>>),
|
||||||
|
|
||||||
|
/// this is the argument to an empty return
|
||||||
|
Void,
|
||||||
|
}
|
||||||
|
impl BSTNode {
|
||||||
|
pub fn identifier(self) -> Option<YYText> {
|
||||||
|
match self {
|
||||||
|
Self::Variable(text) => Some(text),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn args(self) -> Option<Vec<BSTNode>> {
|
||||||
|
match self {
|
||||||
|
Self::Arglist(args) => Some(*args),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum UnaryOperation {
|
||||||
|
Neg,
|
||||||
|
Abs,
|
||||||
|
Fact,
|
||||||
|
}
|
||||||
|
impl UnaryOperation {
|
||||||
|
fn eval(self, operand: Number) -> MathResult<Number> {
|
||||||
|
match self {
|
||||||
|
Self::Neg => operand.checked_neg(),
|
||||||
|
Self::Abs => operand.checked_abs(),
|
||||||
|
Self::Fact => factorial(operand),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum BinaryOperation {
|
||||||
|
/// Plus
|
||||||
|
Add,
|
||||||
|
/// Minus
|
||||||
|
Sub,
|
||||||
|
/// Multiply
|
||||||
|
Mul,
|
||||||
|
/// In theory, multiplication is repeated addition, however
|
||||||
|
/// normal multiplication multiplies the rolls while keeping
|
||||||
|
/// the counts the same. Flatmul is repeated addition for dice
|
||||||
|
Flatmul,
|
||||||
|
/// Divide
|
||||||
|
Div,
|
||||||
|
/// Exponentiation
|
||||||
|
Pow,
|
||||||
|
}
|
||||||
|
impl BinaryOperation {
|
||||||
|
fn eval(self, left: Number, right: Number) -> MathResult<Number> {
|
||||||
|
match self {
|
||||||
|
Self::Add => left.checked_add(right),
|
||||||
|
Self::Sub => left.checked_sub(right),
|
||||||
|
Self::Mul => left.checked_mul(right),
|
||||||
|
Self::Flatmul => left.checked_flatmul(right),
|
||||||
|
Self::Div => left.checked_div(right),
|
||||||
|
Self::Pow => left.checked_pow(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_eval(root: BSTNode, frame: &Frame, function_list: &Functions) -> MathResult<Number> {
|
||||||
|
match root {
|
||||||
|
BSTNode::Literal(s) => match s.as_str().parse() {
|
||||||
|
Ok(n) => MathResult::Ok(Number::Int(n)),
|
||||||
|
Err(_) => MathResult::BadNumber,
|
||||||
|
},
|
||||||
|
BSTNode::BSTDie(d) => {
|
||||||
|
let die = match d.as_str().parse::<Die>() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => return MathResult::ParseError,
|
||||||
|
};
|
||||||
|
MathResult::Ok(Number::Dice(die.distribution()?))
|
||||||
|
},
|
||||||
|
BSTNode::Variable(ident) => frame.lookup(&ident),
|
||||||
|
BSTNode::Binary { op, left, right } => {
|
||||||
|
let left = bst_eval(*left, frame, function_list)?;
|
||||||
|
let right = bst_eval(*right, frame, function_list)?;
|
||||||
|
op.eval(left, right)
|
||||||
|
},
|
||||||
|
BSTNode::Unary { op, operand } => {
|
||||||
|
let operand = bst_eval(*operand, frame, function_list)?;
|
||||||
|
op.eval(operand)
|
||||||
|
},
|
||||||
|
BSTNode::Arglist(_) => panic!("never"), // TODO: std::hint::unreachable_unchecked()
|
||||||
|
BSTNode::FunctionCall(name, args) => {
|
||||||
|
let f = match function_list.get(&name) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return MathResult::FunctionNotFound,
|
||||||
|
};
|
||||||
|
f.call(*args, frame, function_list)
|
||||||
|
},
|
||||||
|
BSTNode::Void => MathResult::Void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// simple, c-accessible drop implementation
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn drop_bst(node: BSTNode, current_frame: &mut Frame) {
|
||||||
|
let ident = unsafe { node.identifier().unwrap_unchecked() };
|
||||||
|
current_frame.forget(ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// this is a really weird function because its in lib.rs
|
||||||
|
fn factorial(n: Number) -> MathResult<Number> {
|
||||||
|
let n = match n {
|
||||||
|
Number::Int(n) if n > 0 => n,
|
||||||
|
Number::Int(_) => return MathResult::OutOfRange,
|
||||||
|
Number::Dice(d) => return d.factorial(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result: Integer = 1;
|
||||||
|
for i in 2..=n {
|
||||||
|
result = match result.checked_mul(i) {
|
||||||
|
Some(next) => next,
|
||||||
|
None => return MathResult::Overflow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MathResult::Ok(Number::Int(result))
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::distribution::Distribution;
|
||||||
|
use crate::result::{MathResult, IntoMath};
|
||||||
|
|
||||||
|
pub type Integer = i32;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Number {
|
||||||
|
Int(i32),
|
||||||
|
Dice(Distribution)
|
||||||
|
}
|
||||||
|
impl Number {
|
||||||
|
pub fn checked_add(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => n1.checked_add(n2).map(Number::Int).into_math(MathResult::Overflow),
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.add_int(n),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.add_distribution(d2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn checked_sub(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => n1.checked_sub(n2).map(Number::Int).into_math(MathResult::Overflow),
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.sub_int(n),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.sub_distribution(d2),
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_mul(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => n1.checked_mul(n2).map(Number::Int).into_math(MathResult::Underflow),
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.mul_int(n),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.mul_distribution(d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn checked_flatmul(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
// flatmul isn't distinct from regular multiplication for two numbers or two dice
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => n1.checked_mul(n2).map(Number::Int).into_math(MathResult::Underflow),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.mul_distribution(d2),
|
||||||
|
|
||||||
|
// this is what flatmul really is: adding a distribution to itself `rhs` times
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.flatmul(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn checked_div(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => n1.checked_div(n2).map(Number::Int).into_math(MathResult::ZeroDivision),
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.div_int(n),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.div_distribution(d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_pow(self, rhs: Number) -> MathResult<Self> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Self::Int(n1), Self::Int(n2)) => {
|
||||||
|
let exp = match n2.try_into() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return MathResult::OutOfRange,
|
||||||
|
};
|
||||||
|
n1.checked_pow(exp).map(Number::Int).into_math(MathResult::Overflow)
|
||||||
|
},
|
||||||
|
(Self::Int(n), Self::Dice(d))
|
||||||
|
| (Self::Dice(d), Self::Int(n)) => d.pow_int(n),
|
||||||
|
(Self::Dice(d1), Self::Dice(d2)) => d1.pow_distribution(d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_abs(self) -> MathResult<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Int(this) => this.checked_abs().map(Number::Int).into_math(MathResult::Overflow),
|
||||||
|
Self::Dice(d) => d.absolute(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked_neg(self) -> MathResult<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Int(this) => this.checked_neg().map(Number::Int).into_math(MathResult::Underflow),
|
||||||
|
Self::Dice(d) => d.mul_int(-1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Number {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Int(n) => write!(f,"{n}"),
|
||||||
|
Self::Dice(d) => write!(f, "{}", d.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
use crate::{BSTNode, BinaryOperation, UnaryOperation};
|
||||||
|
use crate::yytext::yytext_from_cstr;
|
||||||
|
use std::ffi::c_char;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_number(ptr: *mut c_char) -> BSTNode {
|
||||||
|
BSTNode::Literal(yytext_from_cstr(ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_add(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Add,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_sub(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Sub,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_mul(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Mul,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_flatmul(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Flatmul,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_div(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Div,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_pow(left: BSTNode, right: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Binary {
|
||||||
|
op: BinaryOperation::Pow,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_neg(operand: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Unary {
|
||||||
|
op: UnaryOperation::Neg,
|
||||||
|
operand: Box::new(operand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_fact(operand: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Unary {
|
||||||
|
op: UnaryOperation::Fact,
|
||||||
|
operand: Box::new(operand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_abs(operand: BSTNode) -> BSTNode {
|
||||||
|
BSTNode::Unary {
|
||||||
|
op: UnaryOperation::Abs,
|
||||||
|
operand: Box::new(operand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_variable(ident: *mut c_char) -> BSTNode {
|
||||||
|
BSTNode::Variable( yytext_from_cstr(ident) )
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn bst_dice(ident: *mut c_char) -> BSTNode {
|
||||||
|
BSTNode::BSTDie( yytext_from_cstr(ident) )
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::hint::unreachable_unchecked;
|
||||||
|
/// Define the FFI-safe MathResult type
|
||||||
|
/// this would be a regular type alias, but a theoretical
|
||||||
|
/// Result<T, MathError> wouldn't be FFI-safe because
|
||||||
|
/// Result<T, E> is not #[repr(C)] even if both T
|
||||||
|
/// and E are.
|
||||||
|
|
||||||
|
use std::ops::{FromResidual, Try, ControlFlow};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum MathResult<T> {
|
||||||
|
/// Ok(T) is the Ok variation. Analagous to
|
||||||
|
/// std::result::Result::Ok.
|
||||||
|
Ok(T),
|
||||||
|
/// This is a parse error, specifically when trying to parse
|
||||||
|
/// a number.
|
||||||
|
BadNumber,
|
||||||
|
/// Returned when attempting to call a nonexistent function
|
||||||
|
FunctionNotFound,
|
||||||
|
/// An intermediate operation resulted in integer overflow.
|
||||||
|
/// Also returned when trying to calculate the negative of
|
||||||
|
/// `i32::MIN`
|
||||||
|
Overflow,
|
||||||
|
/// An operation had out of bounds operands, like the factorial
|
||||||
|
/// of a negative.
|
||||||
|
OutOfRange,
|
||||||
|
/// Not enough arguments were passed to a function
|
||||||
|
TooFewArgs,
|
||||||
|
/// Too many arguments were passed to a function
|
||||||
|
TooManyArgs,
|
||||||
|
/// An operation resulted in integer underflow.
|
||||||
|
Underflow,
|
||||||
|
/// One of the referenced variables in an expression has not been
|
||||||
|
/// assigned yet. Different to FunctionNotFound because that occurs
|
||||||
|
/// for functions.
|
||||||
|
VariableNotFound,
|
||||||
|
/// This is no an error per-se, but rather the result of return with
|
||||||
|
/// no expression to return. This is early-returned to simulate the
|
||||||
|
/// fact that any expression involving null would also return null.
|
||||||
|
Void,
|
||||||
|
/// An error occured while trying to parse an object passed by Bison
|
||||||
|
ParseError,
|
||||||
|
/// An error occured while trying to print an object. This basically
|
||||||
|
/// never occurs unless stdout has been redirected, but is useful
|
||||||
|
/// for a no-panic program that involves io.
|
||||||
|
PrintError,
|
||||||
|
/// The denominator of a division was 0.
|
||||||
|
ZeroDivision,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> FromResidual<MathResult<U>> for MathResult<T> {
|
||||||
|
/// This implementation of residual is a bit weird. The residual
|
||||||
|
/// is of type `MathResult<U>` because the error variants are effectively
|
||||||
|
/// untyped, in that they do not have any generic or concrete types.
|
||||||
|
/// # Undefined behaviour
|
||||||
|
/// This function assumes that it will only be called by Rust to
|
||||||
|
/// reconstitute a `MathResult<T>` from one of its error variants, and
|
||||||
|
/// as such it is **Undefined Behaviour** to call this on a `MathResult::Ok`
|
||||||
|
fn from_residual(this: MathResult<U>) -> Self {
|
||||||
|
match this {
|
||||||
|
MathResult::Ok(_) => unsafe { unreachable_unchecked() },
|
||||||
|
MathResult::BadNumber => Self::BadNumber,
|
||||||
|
MathResult::FunctionNotFound => Self::FunctionNotFound,
|
||||||
|
MathResult::Overflow => Self::Overflow,
|
||||||
|
MathResult::OutOfRange => Self::OutOfRange,
|
||||||
|
MathResult::TooFewArgs => Self::TooFewArgs,
|
||||||
|
MathResult::TooManyArgs => Self::TooManyArgs,
|
||||||
|
MathResult::Underflow => Self::Underflow,
|
||||||
|
MathResult::VariableNotFound => Self::VariableNotFound,
|
||||||
|
MathResult::Void => Self::Void,
|
||||||
|
MathResult::ParseError => Self::ParseError,
|
||||||
|
MathResult::PrintError => Self::PrintError,
|
||||||
|
MathResult::ZeroDivision => Self::ZeroDivision,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Try for MathResult<T> {
|
||||||
|
type Output = T;
|
||||||
|
type Residual = Self;
|
||||||
|
|
||||||
|
fn from_output(data: T) -> Self {
|
||||||
|
Self::Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
|
||||||
|
// this is a weird match, but essentialy the Ok data can
|
||||||
|
// get `Continue`d, while others get `Break`ed.
|
||||||
|
match self {
|
||||||
|
MathResult::Ok(data) => ControlFlow::Continue(data),
|
||||||
|
other => ControlFlow::Break(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a given Result<OkType, MathResult<Infallible>>, convert into a
|
||||||
|
/// MathResult<OkType> by turning Ok(t) into MathResult::Ok(t) and Err(n)
|
||||||
|
/// into Self::n. E in the Result must be of type MathResult<Infallible>
|
||||||
|
/// to avoid the possibility of Err(Ok(_)).
|
||||||
|
impl<OkType> From< Result< OkType, MathResult<Infallible> > > for MathResult<OkType> {
|
||||||
|
fn from(value: Result< OkType, MathResult<Infallible> >) -> MathResult<OkType> {
|
||||||
|
match value {
|
||||||
|
Ok(data) => Self::Ok(data),
|
||||||
|
// this is unreachable because this is Err(Ok(Infalliable))
|
||||||
|
Err(MathResult::Ok(_)) => unsafe { unreachable_unchecked() },
|
||||||
|
Err(MathResult::BadNumber) => Self::BadNumber,
|
||||||
|
Err(MathResult::FunctionNotFound) => Self::FunctionNotFound,
|
||||||
|
Err(MathResult::OutOfRange) => Self::OutOfRange,
|
||||||
|
Err(MathResult::Overflow) => Self::Overflow,
|
||||||
|
Err(MathResult::TooFewArgs) => Self::TooFewArgs,
|
||||||
|
Err(MathResult::TooManyArgs) => Self::TooManyArgs,
|
||||||
|
Err(MathResult::Underflow) => Self::Underflow,
|
||||||
|
Err(MathResult::VariableNotFound) => Self::VariableNotFound,
|
||||||
|
Err(MathResult::ZeroDivision) => Self::ZeroDivision,
|
||||||
|
Err(MathResult::Void) => Self::Void,
|
||||||
|
Err(MathResult::ParseError) => Self::ParseError,
|
||||||
|
Err(MathResult::PrintError) => Self::PrintError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a given MathResult<OkType>, turn it into a
|
||||||
|
/// Result<OkType, MathResult<Infalliable> by mapping Self::Ok(data) to
|
||||||
|
/// Ok(data), and any error value to Err(value)
|
||||||
|
impl<OkType> From< MathResult<OkType> > for Result<OkType, MathResult<Infallible>> {
|
||||||
|
fn from(val: MathResult<OkType>) -> Self {
|
||||||
|
match val {
|
||||||
|
MathResult::Ok(data) => Ok(data),
|
||||||
|
MathResult::BadNumber => Err(MathResult::BadNumber),
|
||||||
|
MathResult::FunctionNotFound => Err(MathResult::FunctionNotFound),
|
||||||
|
MathResult::OutOfRange => Err(MathResult::OutOfRange),
|
||||||
|
MathResult::Overflow => Err(MathResult::Overflow),
|
||||||
|
MathResult::TooFewArgs => Err(MathResult::TooFewArgs),
|
||||||
|
MathResult::TooManyArgs => Err(MathResult::TooManyArgs),
|
||||||
|
MathResult::Underflow => Err(MathResult::Underflow),
|
||||||
|
MathResult::VariableNotFound => Err(MathResult::VariableNotFound),
|
||||||
|
MathResult::ZeroDivision => Err(MathResult::ZeroDivision),
|
||||||
|
MathResult::Void => Err(MathResult::Void),
|
||||||
|
MathResult::ParseError => Err(MathResult::ParseError),
|
||||||
|
MathResult::PrintError => Err(MathResult::PrintError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<OkType, Collection> FromIterator< MathResult<OkType> > for MathResult<Collection>
|
||||||
|
where Collection: FromIterator<OkType> {
|
||||||
|
fn from_iter<I>(iter: I) -> MathResult<Collection>
|
||||||
|
where I: IntoIterator< Item = MathResult<OkType> > {
|
||||||
|
let result_iterator = iter.into_iter().map(
|
||||||
|
// this hellish generic selects the Into<T> implementation that converts
|
||||||
|
// a MathResult<T> into a Result<T, MathResult<Infallible>>, so that I
|
||||||
|
// can use Result's implementation of FromIterator later.
|
||||||
|
<MathResult<OkType> as Into< Result<OkType, MathResult<Infallible>> >>::into
|
||||||
|
);
|
||||||
|
// Use Result<T, E>'s implementation of FromIterator to build up the
|
||||||
|
// collection as a Result.
|
||||||
|
let collected_result: Result<Collection, MathResult<Infallible>> = result_iterator.collect();
|
||||||
|
// convert the result into a MathResult<Collection>. The Err variant is
|
||||||
|
// of type MathResult<Infallible> so this is a flawless conversion.
|
||||||
|
collected_result.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> Display for MathResult<T>
|
||||||
|
where T: Display {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// pretty self-explanatory. write the right message for each variant
|
||||||
|
match self {
|
||||||
|
Self::Ok(data) => write!(f, "{data}"),
|
||||||
|
Self::BadNumber => write!(f, "unparseable number"),
|
||||||
|
Self::FunctionNotFound => write!(f, "function not found"),
|
||||||
|
Self::OutOfRange => write!(f, "out of range"),
|
||||||
|
Self::Overflow => write!(f, "integer overflow"),
|
||||||
|
Self::TooFewArgs => write!(f, "too few arguments"),
|
||||||
|
Self::TooManyArgs => write!(f, "too many arguments"),
|
||||||
|
Self::Underflow => write!(f, "integer underflow"),
|
||||||
|
Self::VariableNotFound => write!(f, "variable not found"),
|
||||||
|
Self::ZeroDivision => write!(f, "division by zero"),
|
||||||
|
Self::Void => write!(f, "(void)"),
|
||||||
|
Self::ParseError => write!(f, "parse error"),
|
||||||
|
Self::PrintError => write!(f, "printing error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod seal_mod {
|
||||||
|
/// This is just a sanity check to make sure I never try to ipmlement
|
||||||
|
/// IntoMath elsewhere in the codebase.
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
/// Ugly hack to simplify moving between rust's error-handling types `Option<T>`
|
||||||
|
/// and `Result<T, E>` and my `MathResult<T>`.
|
||||||
|
/// This is `Sealed` for sanity reasons.
|
||||||
|
pub trait IntoMath: seal_mod::Sealed {
|
||||||
|
type Target;
|
||||||
|
|
||||||
|
fn into_math(self, fallback: MathResult<Self::Target>) -> MathResult<Self::Target>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> seal_mod::Sealed for Option<T> {}
|
||||||
|
impl<T> IntoMath for Option<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn into_math(self, fallback: MathResult<T>) -> MathResult<T> {
|
||||||
|
match self {
|
||||||
|
Some(data) => MathResult::Ok(data),
|
||||||
|
None => fallback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> seal_mod::Sealed for Result<T, E> {}
|
||||||
|
impl<T, E> IntoMath for Result<T, E> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn into_math(self, fallback: MathResult<T>) -> MathResult<T> {
|
||||||
|
match self {
|
||||||
|
Ok(data) => MathResult::Ok(data),
|
||||||
|
Err(_) => fallback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
use std::mem::{forget, take};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::functions::Functions;
|
||||||
|
use crate::{Number, bst_eval, BSTNode};
|
||||||
|
use crate::result::{MathResult, IntoMath};
|
||||||
|
use crate::yytext::YYText;
|
||||||
|
use crate::instruction::Instruction;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Frame {
|
||||||
|
pub name: YYText,
|
||||||
|
args: Vec<YYText>,
|
||||||
|
locals: HashMap<YYText, Number>,
|
||||||
|
instructions: Vec<Instruction>,
|
||||||
|
}
|
||||||
|
impl Frame {
|
||||||
|
pub fn new(name: YYText) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_args(source: &Frame, args: Vec<BSTNode>, frame: &Frame, function_list: &Functions) -> MathResult<Self> {
|
||||||
|
if args.len() < source.args.len() {
|
||||||
|
return MathResult::TooFewArgs;
|
||||||
|
}
|
||||||
|
if args.len() > source.args.len() {
|
||||||
|
return MathResult::TooManyArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = args.into_iter()
|
||||||
|
.map(|root| bst_eval(root, frame, function_list))
|
||||||
|
.collect::< MathResult<Vec<Number>> >()?;
|
||||||
|
|
||||||
|
let mut result = Self::default();
|
||||||
|
|
||||||
|
for (name, rhs) in source.args.iter().zip(args) {
|
||||||
|
result.locals.insert(name.clone(), rhs);
|
||||||
|
}
|
||||||
|
result.instructions = source.instructions.clone();
|
||||||
|
MathResult::Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(mut self, function_list: &Functions) -> MathResult<Number> {
|
||||||
|
// take the instruction list to allow a &self in the loop. otherwise
|
||||||
|
// rust complains of ref to partially moved self
|
||||||
|
let instructions = take(&mut self.instructions);
|
||||||
|
for inst in instructions {
|
||||||
|
match inst {
|
||||||
|
Instruction::Assign(name, node) => {
|
||||||
|
let rhs = bst_eval(node, &self, function_list)?;
|
||||||
|
self.locals.insert(name, rhs);
|
||||||
|
}
|
||||||
|
Instruction::Return(root) => return bst_eval(root, &self, function_list),
|
||||||
|
Instruction::Evaluate(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MathResult::Void
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, ident: &YYText) -> MathResult<Number> {
|
||||||
|
self.locals.get(ident)
|
||||||
|
.map(Clone::clone)
|
||||||
|
.into_math(MathResult::VariableNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign(&mut self, ident: YYText, value: Number) {
|
||||||
|
self.locals.insert(ident, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, args: Vec<BSTNode>, frame: &Frame, function_list: &Functions) -> MathResult<Number> {
|
||||||
|
let child = Frame::from_args(self, args, frame, function_list)?;
|
||||||
|
child.execute(function_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forget(&mut self, ident: YYText) {
|
||||||
|
self.locals.remove(&ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn insert_instruction(f: &mut Frame, value: Instruction) {
|
||||||
|
f.instructions.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn print_node(f: &Frame, root: BSTNode, function_list: &Functions) {
|
||||||
|
println!("Result: {}", bst_eval(root, f, function_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn assign_node(f: &mut Frame, ident: BSTNode, root: BSTNode, function_list: &Functions) {
|
||||||
|
match bst_eval(root, f, function_list) {
|
||||||
|
MathResult::Ok(n) => { f.locals.insert(ident.identifier().unwrap(), n); },
|
||||||
|
problem => eprintln!("Result: {problem}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// this makes the main frame for the app to run in.
|
||||||
|
/// #[allow(unused_allocation)] because rust thinks i'm putting this
|
||||||
|
/// on the heap for no reason
|
||||||
|
#[allow(unused_allocation)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn make_frame(name: YYText) -> *mut Frame {
|
||||||
|
let mut b = Box::new( Frame::new(name) );
|
||||||
|
let result = b.as_mut() as *mut Frame;
|
||||||
|
forget(b);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a frame from a bst node. the provided node
|
||||||
|
/// must be of the variable variant so that it's
|
||||||
|
/// identifier can be extracted
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn make_frame_from_declaration(name: BSTNode, args: BSTNode) -> *mut Frame {
|
||||||
|
let name = unsafe { name.identifier().unwrap_unchecked() };
|
||||||
|
let args = unsafe { args.args().unwrap_unchecked() }
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(BSTNode::identifier)
|
||||||
|
.collect();
|
||||||
|
let mut result = Frame::new(name);
|
||||||
|
result.args = args;
|
||||||
|
let mut b = Box::new(result);
|
||||||
|
let result = b.as_mut() as *mut Frame;
|
||||||
|
forget(b);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy a frame by reclaiming the original box
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn destroy_frame(frame: *mut Frame) {
|
||||||
|
drop( unsafe { Box::from_raw(frame) } );
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
use std::mem::forget;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::ffi::{CString, c_char, CStr};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct YYText {
|
||||||
|
ptr: *mut u8,
|
||||||
|
len: usize,
|
||||||
|
cap: usize,
|
||||||
|
}
|
||||||
|
impl YYText {
|
||||||
|
pub fn new(mut s: String) -> Self {
|
||||||
|
// decompose a String into its parts
|
||||||
|
let ptr = s.as_mut_ptr();
|
||||||
|
let len = s.len();
|
||||||
|
let cap = s.capacity();
|
||||||
|
let result = Self { ptr, len, cap };
|
||||||
|
|
||||||
|
// make sure s doesn't get dropped. a bit unsafe
|
||||||
|
// since the pointer returned by as_mut_ptr() isn't
|
||||||
|
// guaranteed to stay safe after forgetting, but it
|
||||||
|
// seems to work well enough
|
||||||
|
forget(s);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
let as_slice = unsafe { std::slice::from_raw_parts(self.ptr, self.len) };
|
||||||
|
let as_str = std::str::from_utf8(as_slice).expect("invalid utf8");
|
||||||
|
as_str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// When dropped, reconstitute the String and make sure
|
||||||
|
/// that it gets freed correctly
|
||||||
|
impl Drop for YYText {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let s = unsafe { String::from_raw_parts(self.ptr, self.len, self.cap) };
|
||||||
|
drop(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Debug as str but include the pointer
|
||||||
|
impl Debug for YYText {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?} @ {:?}", self.as_str(), self.ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Clone for YYText {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self::new( self.as_str().to_owned() )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for YYText {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ptr: std::ptr::null_mut(),
|
||||||
|
len: 0,
|
||||||
|
cap: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PartialEq as str
|
||||||
|
impl PartialEq for YYText {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.as_str() == other.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Eq as str
|
||||||
|
impl Eq for YYText {}
|
||||||
|
|
||||||
|
/// Hash as str
|
||||||
|
impl Hash for YYText {
|
||||||
|
fn hash<H>(&self, state: &mut H)
|
||||||
|
where H: std::hash::Hasher {
|
||||||
|
self.as_str().hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string_from_cstr(src: *mut c_char) -> String {
|
||||||
|
let c_str = unsafe { CStr::from_ptr(src) };
|
||||||
|
let c_string = CString::from(c_str);
|
||||||
|
c_string.into_string()
|
||||||
|
// bad error handling
|
||||||
|
.expect("could not convert to string")
|
||||||
|
}
|
||||||
|
/// More evil pointer manipulation to turn a char* into a YYText
|
||||||
|
/// basically, make a YYText that is equivalent to
|
||||||
|
/// ```
|
||||||
|
/// reinterp_cast<YYText>(yytext)
|
||||||
|
/// ```
|
||||||
|
/// then clone that, then forget the original since it can't be dropped
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C"
|
||||||
|
fn yytext_from_cstr(src: *mut c_char) -> YYText {
|
||||||
|
let string = string_from_cstr(src);
|
||||||
|
YYText::new(string)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
%{
|
||||||
|
#include <string.h> // strncmp
|
||||||
|
|
||||||
|
#include "rust.h" // bst_* functions
|
||||||
|
#define YYSTYPE BSTNode
|
||||||
|
#include "grammar.tab.h" // PLUS MINUS etc.
|
||||||
|
|
||||||
|
extern YYSTYPE yylval;
|
||||||
|
extern Frame *current_frame;
|
||||||
|
|
||||||
|
#include <readline/readline.h>
|
||||||
|
#include <readline/history.h>
|
||||||
|
#define YY_INPUT(buf,result,max_size) result = mygetinput(buf, max_size);
|
||||||
|
|
||||||
|
static int mygetinput(char *buf, int size) {
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
if (feof(yyin))
|
||||||
|
return YY_NULL;
|
||||||
|
|
||||||
|
line = readline("> ");
|
||||||
|
|
||||||
|
if(line == NULL)
|
||||||
|
return YY_NULL;
|
||||||
|
|
||||||
|
size_t len = strnlen(line, size);
|
||||||
|
if (len > size-2)
|
||||||
|
return YY_NULL;
|
||||||
|
|
||||||
|
snprintf(buf, size, "%s\n", line);
|
||||||
|
|
||||||
|
add_history(line);
|
||||||
|
free(line);
|
||||||
|
|
||||||
|
return strlen(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%option noyywrap
|
||||||
|
|
||||||
|
white [ \t]+
|
||||||
|
integer [0-9]+
|
||||||
|
ident [a-zA-Z][a-zA-Z0-9_]*
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
{white} { ; }
|
||||||
|
|
||||||
|
{integer}"d"{integer} {
|
||||||
|
yylval = bst_dice(yytext);
|
||||||
|
return DICE;
|
||||||
|
}
|
||||||
|
{integer} {
|
||||||
|
yylval = bst_number(yytext);
|
||||||
|
return NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ident} {
|
||||||
|
// check for keywords
|
||||||
|
// note to self: don't pass yyleng as the max length,
|
||||||
|
// because then things like "ret" would pass as "return"
|
||||||
|
if (strncmp(yytext, "return", 6) == 0) {
|
||||||
|
return RETURN;
|
||||||
|
}
|
||||||
|
if (strncmp(yytext, "function", 8) == 0) {
|
||||||
|
return FUNCTION;
|
||||||
|
}
|
||||||
|
if (strncmp(yytext, "drop", 4) == 0) {
|
||||||
|
return DROP;
|
||||||
|
}
|
||||||
|
yylval = bst_variable(yytext);
|
||||||
|
return IDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
"+" return PLUS;
|
||||||
|
"-" return MINUS;
|
||||||
|
"**" return FLATMUL;
|
||||||
|
"*" return MUL;
|
||||||
|
"/" return DIV;
|
||||||
|
"^" return POWER;
|
||||||
|
"(" return LEFT;
|
||||||
|
")" return RIGHT;
|
||||||
|
"!" return FACT;
|
||||||
|
"|" return BAR;
|
||||||
|
"\n" return END;
|
||||||
|
"<=" return LESSEQ;
|
||||||
|
"<" return LESS;
|
||||||
|
">=" return GREATEREQ;
|
||||||
|
">" return GREATER;
|
||||||
|
"==" return EQ;
|
||||||
|
"=" return ASSIGN;
|
||||||
|
"," return ARGSEP;
|
||||||
|
"{" return BLOCKSTART;
|
||||||
|
"}" return BLOCKEND;
|
||||||
|
|
||||||
|
%%
|
Loading…
Reference in New Issue