Initial commit

master
nick 2024-08-16 10:16:51 -04:00
commit cdc6449f53
17 changed files with 1724 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
calc
lex.yy.*
grammar.tab.*
main.o
rust.h

48
Cargo.lock generated Normal file
View File

@ -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"

11
Cargo.toml Normal file
View File

@ -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"

47
Makefile Normal file
View File

@ -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

157
grammar.y Normal file
View File

@ -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);
}

19
main.c Normal file
View File

@ -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);
}

102
src/dice.rs Normal file
View File

@ -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)
}

271
src/distribution.rs Normal file
View File

@ -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())
}
}
}

51
src/functions.rs Normal file
View File

@ -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));
}

68
src/instruction.rs Normal file
View File

@ -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]))
}

174
src/lib.rs Normal file
View File

@ -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))
}

98
src/number.rs Normal file
View File

@ -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()),
}
}
}

108
src/ops.rs Normal file
View File

@ -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) )
}

226
src/result.rs Normal file
View File

@ -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,
}
}
}

142
src/stackframe.rs Normal file
View File

@ -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) } );
}

99
src/yytext.rs Normal file
View File

@ -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)
}

97
tokens.lex Normal file
View File

@ -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;
%%