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