From e38fa3725841e095eb1e2f6fb6c466a9c6f94a63 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:29:31 -0500 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=AC=20integration=20tests=20for=20?= =?UTF-8?q?adverbs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/t.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/t.rs b/tests/t.rs index 94c1204..411b4bc 100644 --- a/tests/t.rs +++ b/tests/t.rs @@ -69,4 +69,50 @@ #[test]fn display_scalar()->R<()>{let(a)=A::from_i(666)?;eq!(a.to_string(),"666\n");ok!()} #[test]fn display_slice()->R<()>{let a:&[I]=&[7,8,9];let a=A::try_from(a)?;eq!(a.to_string(),"7 8 9\n");ok!()} #[test]fn display_matrix()->R<()>{let(a)=eval_s("i. 3 3")?;eq!(a.to_string(),"0 1 2\n3 4 5\n6 7 8\n");ok!()} +} #[cfg(test)]mod adverb{use super::*; + // === monadic / "insert" adverb + #[test]fn insert_sum_one_number()->R<()>{let(a)=eval_s("+ / 1")?;let(i)=a.as_i()?;eq!(i,1);ok!()} + #[test]fn insert_sum_two_numbers()->R<()>{let(a)=eval_s("+ / 1 8")?;let(i)=a.as_i()?;eq!(i,9);ok!()} + #[test]fn insert_sum_a_sequence()->R<()>{let(a)=eval_s("+ / i. 4")?;let(i)=a.as_i()?;eq!(i,6);ok!()} + #[test]fn insert_sum_a_shifted_sequence()->R<()>{let(a)=eval_s("+ / 1 + i. 4")?;let(i)=a.as_i()?;eq!(i,10);ok!()} + #[test]fn insert_product_of_a_sequence()->R<()>{let(a)=eval_s("* / i. 3")?;let(i)=a.as_i()?;eq!(i,0);ok!()} + #[test]fn insert_product_of_a_shifted_sequence()->R<()>{let(a)=eval_s("* / 2 + i. 3")?;let(i)=a.as_i()?;eq!(i,24);ok!()} + // === monadic \ "prefix" adverb + #[test]fn prefix_of_scalar()->R<()>{let(a)=eval_s("] \\ 1")?;let(i)=a.as_i()?;eq!(i,1);ok!()} + #[test]fn prefix_of_slice() ->R<()>{let(a)=eval_s("] \\ 1 2 3")?;eq!(a.into_matrix()?,&[&[1,0,0], + &[1,2,0], + &[1,2,3],]);ok!()} + #[test]fn prefix_of_slice_2()->R<()>{let(a)=eval_s("+ \\ 1 2 3")?;eq!(a.into_matrix()?,&[&[1,0,0], + &[1,2,0], + &[1,2,3],]);ok!()} + #[test]fn prefix_of_slice_3()->R<()>{let(a)=eval_s("* \\ 1 2 3")?;eq!(a.into_matrix()?,&[&[1,0,0], + &[1,1,0], + &[1,1,1],]);ok!()} + // === dyadic / "table" adverb + #[test]fn table_of_scalars_plus()->R<()>{let(a)=eval_s("1 + / 1")?;eq!(a.as_i()?,2);ok!()} + #[test]fn table_of_scalars_mult()->R<()>{let(a)=eval_s("1 * / 1")?;eq!(a.as_i()?,1);ok!()} + #[test]fn table_of_scalar_plus_slice()->R<()>{let(a)=eval_s("1 + / 1 2 3")?;eq!(a.as_slice()?,&[2,3,4]);ok!()} + #[test]fn table_of_two_slices_mult()->R<()>{let(a)=eval_s("1 2 3 * / 1 2 3")?;eq!(a.into_matrix()?,&[&[1,2,3], + &[2,4,6], + &[3,6,9]]); + ok!()} + #[test]fn table_of_two_diff_slices_mult()->R<()>{let(a)=eval_s("2 4 * / 1 2 3")?;eq!(a.into_matrix()?,&[&[2,4,6], + &[4,8,12]]); + ok!()} + // === dyadic \ "infix" adverb + #[test]fn infix_to_reshape_1()->R<()>{let(a)=eval_s("1 ] \\ 1 2 3")?;eq!(a.into_matrix()?,&[&[1], + &[2], + &[3]]); + ok!()} + #[test]fn infix_to_reshape_2()->R<()>{let(a)=eval_s("2 ] \\ 1 2 3")?;eq!(a.into_matrix()?,&[&[1,2], + &[2,3]]); + ok!()} + #[test]fn infix_to_reshape_3()->R<()>{let(a)=eval_s("3 ] \\ 1 2 3 4")?;eq!(a.into_matrix()?,&[&[1,2,3], + &[2,3,4]]); + ok!()} +} #[cfg(test)]mod adverb_fancy{use super::*; /*XXX: these are left unsolved for now*/ + #[ignore] #[test]fn running_sum_of_a_sequence()->R<()>{let(a)=eval_s("+ / \\ 1 2 3 4 5")?; + let(i)=a.as_slice()?;eq!(i,&[1,3,6,10,15]);ok!()} + #[ignore] #[test]fn running_product_of_a_sequence()->R<()>{let(a)=eval_s("* / \\ 1 2 3 4 5")?; + let(i)=a.as_slice()?;eq!(i,&[1,2,6,24,120]);ok!()} } From 2e9db637375539134c2434be2cce78545c661460 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:30:15 -0500 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=A9=B9=20test=20for=20`[`=20output=20?= =?UTF-8?q?dimensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/t.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/t.rs b/tests/t.rs index 411b4bc..505bfbe 100644 --- a/tests/t.rs +++ b/tests/t.rs @@ -53,6 +53,9 @@ #[test]fn right_dyad() ->R<()>{let(a)=eval_s("1 ] 2")?; eq!(a.as_i()?,2); ok!()} #[test]fn left_dyad_other() ->R<()>{let(a)=eval_s("1 [ 2 3 4")?; eq!(a.as_i()?,1); ok!()} #[test]fn right_dyad_other() ->R<()>{let(a)=eval_s("1 ] 2 3 4")?; eq!(a.as_slice()?,&[2,3,4]);ok!()} + #[test]fn left_dyad_does_not_rotate_slice()->R<()>{ + /*NB: other operators like + or * may rotate the left-hand argument to fit. [ does not. */ + let(a)=eval_s("1 2 3 4 [ i. 4 1")?;eq!(a.as_slice()?,&[1,2,3,4]);ok!()} } #[cfg(test)]mod symbol_assignment{use super::*; #[test]fn assign_and_get_i()->R<()>{let(mut st)=ST::default();let(a)=eval("a =: 3",&mut st)?; assert_eq!(st.get_s("a").unwrap().as_i().unwrap(),3);ok!()} From afe652c4a4645f9c6340d8af57675a77a3c50a9a Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:41:54 -0500 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=92=98=20adverbs=20can=20be=20parsed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/r.rs | 112 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/src/r.rs b/src/r.rs index 35ae338..875338b 100644 --- a/src/r.rs +++ b/src/r.rs @@ -1,12 +1,15 @@ -/**input lexing*/pub(crate) use lex::lex;mod lex{use crate::*; +/**input lexing*/ pub(crate) use self::{lex::lex,parse::{D,M,N,Yd,Ym,parse}}; +mod lex{use crate::*; /**syntax token*/ #[derive(CL,DBG,PE)] pub(crate) enum T {/*array literal*/A(V), + /*assignment*/ E , /* NB: this does not identify whether possible verbs */ /*(ad)verb*/ V(S) , /* are monadic or dyadic. that is done during parsing.*/ /*symbol*/ SY(SY) } impl T{pub(super) fn is_noun(&self)->bool{use T::*;matches!(self,A(_)|SY(_))}} pub(crate) fn lex(input:&str)->R>{use std::ops::Deref; let(mut ts)=input.split_whitespace().peekable(); let(mut o)=V::with_capacity(ts.size_hint().0); while let Some(t) =ts.next(){ - if let Some(sy) =t.parse().ok().map(T::SY){o.push(sy);} // symbol + if t == "=:" {o.push(T::E)} // assignment + else if let Some(sy) =t.parse().ok().map(T::SY){o.push(sy);} // symbol else if let Some(mut v)=t.parse().ok().map(|i|vec![i]){ // array literal macro_rules! peek{()=>{ts.peek().and_then(|t|t.parse().ok())}} // ..is the next token a number? macro_rules! put{($i:ident)=>{ts.next().map(drop);v.push($i);}} // ..append to our array literal @@ -16,6 +19,7 @@ #[cfg(test)]mod t{use super::{*,T::A as TA,T::V as TV,T::SY as TSY}; /// test helper: lex an expression and check the output macro_rules! t{($f:ident,$i:literal,$o:expr)=>{#[test]fn $f()->R<()>{eq!(lex($i)?,$o);ok!()}}} + macro_rules! sy{($i:literal)=>{$i.parse().map(T::SY).unwrap()}} // === lexing unit tests === t!(lex_1, "1", v![TA(v![1])] ); t!(lex_9, "9", v![TA(v![9])] ); @@ -23,16 +27,24 @@ t!(lex_monad, "# 1 2 3", v![TV(S::from("#")), TA(v![1,2,3])] ); t!(lex_dyad, "1 + 2", v![TA(v![1]), TV(S::from("+")), TA(v![2])] ); t!(lex_two_verbs, "1 + # 1 2 3", v![TA(v![1]), TV(S::from("+")), TV(S::from("#")), TA(v![1,2,3])] ); - t!(lex_symbol, "abc", v![TSY("abc".parse().unwrap())] ); + t!(sum_over, "+ / 1 2", v![TV(S::from("+")), TV(S::from("/")), TA(v![1,2])] ); + t!(lex_symbol, "abc", v![sy!("abc")] ); + t!(lex_assign, "a =: 1", v![sy!("a"), T::E, TA(v![1])] ); } -}/**input parsing*/pub(crate) use parse::{D,M,N,parse};mod parse{use {crate::*,super::lex::{T,lex}}; +}/**input parsing*/mod parse{use {crate::*,super::lex::{T,lex}}; /**dyadic verb */ #[derive(DBG,PE,PO)] pub enum D {Plus,Mul, Left, Right } /**monadic verb */ #[derive(DBG,PE,PO)] pub enum M {Idot,Shape,Tally,Transpose,Same} + /**dyadic adverb */ #[derive(DBG )] pub enum Yd{/**dyadic `/` */ Table , + /**dyadic `\` */ Infix } + /**monadic adverb */ #[derive(DBG )] pub enum Ym{/**monadic `/`*/ Insert, + /**monadic `\`*/ Prefix} /**ast node */ #[derive(DBG, )] pub enum N {/**array literal*/ A{a:A}, /**dyadic verb*/ D{d:D,l:B,r:B}, /**monadic verb*/ M{m:M,o:B}, + /**dyadic adverb*/ Yd{yd:Yd,d:D,l:B,r:B}, + /**monadic adverb*/ Ym{ym:Ym,d:D,o:B}, /**symbol*/ S{sy:SY}, - /**symbol assignment*/V{sy:SY,e:B}} + /**symbol assignment*/E{sy:SY,e:B}} impl From for N{fn from(sy:SY)->N{N::S{sy}}} impl TF> for N{type Error=E; fn try_from(a:Vec)->R{a.try_into().map(|a|N::A{a})}} /**parse a sequence of tokens into an abstract syntax tree.*/ @@ -41,32 +53,84 @@ /*debug*/debug_assert!(ts.is_empty());debug_assert!(ctx.len() <= 1,"AST needs a root node: {ctx:?}");/*debug*/ Ok(ctx.pop())} fn parse_(ts:&mut V,ctx:&mut V>)->R<()>{ + // push a new AST node onto the `ctx` stack and return, indicating a successful parsing "step." macro_rules! step{($n:expr)=>{ctx.push(b!($n));r!(ok!());}} + let(v):S=match ts.pop(){ Some(T::V(v)) =>v, /*take the next verb, or return if done*/ None=>r!(ok!()), Some(T::A(v)) =>{let(n)=v.try_into()?;step!(n);} // array literal - Some(T::SY(sy))=>{let(n)=sy.into(); step!(n);}}; // symbol name - let(rhs)=ctx.pop().ok_or(err!("no right-hand operand for `{v:?}`"))?; - if ts.last().map(T::is_noun).unwrap_or(false){ // dyadic verbs - let(l)=match(ts.pop()).unwrap(/*we just peeked this*/){T::V(v)=>ur!(),/*..and checked it isn't a verb*/ - T::A(a)=> {a.try_into().map(|a|b!(a))?} - T::SY(sy)=>if(v=="=:"){step!(N::V{sy,e:rhs});}else{b!(sy.into())} }; // handle variable assignment - let(d)=D::new(&v).ok_or(err!("invalid dyadic verb {v:?}"))?; step!(N::D{d,l,r:rhs}); - }else{let(m)=M::new(&v).ok_or(err!("invalid monadic verb {v:?}"))?; step!(N::M{m,o:rhs}); }} - impl M{fn new(s:&str)->O{use M::*;Some(match s{"i."=>Idot,"$"=>Shape,"#"=>Tally,"|:"=>Transpose,"["|"]"=>Same,_=>r!(None)})}} - impl D{fn new(s:&str)->O{use D::*;Some(match s{"+"=>Plus,"*"=>Mul,"["=>Left,"]"=>Right,_=>r!(None)})}} + Some(T::SY(sy))=>{let(n)=sy.into(); step!(n);} // symbol name + Some(T::E) =>{let Some(T::SY(sy))=ts.pop()else{bail!("assignment must apply to a symbol")}; + /*assignment*/ let(e)=ctx.pop().ok_or(err!("assignment missing right-hand side"))?; + step!(N::E{sy,e});}}; + let(rhs)=ctx.pop().ok_or(err!("no right-hand operand for `{v:?}`"))?; /*right-hand operand*/ + let(lhs):O>=match ts.pop(){ /*take the left-hand operand, if it exists. */ + None =>{ None} Some(T::A(v)) =>Some(b!(v.try_into()?)), + Some(t@T::V(_)|t@T::E)=>{ts.push(t);None} Some(T::SY(sy)) =>Some(b!(sy.into())), + }; + /*first, process monadic and dyadic verbs*/ + if let Some(l)=lhs{let(d)=D::new(&v).ok_or(err!("invalid dyad {v:?}"))?;step!(N::D{l,r:rhs,d});} + else if let Some(m)=M::new(&v){step!(N::M{m,o:rhs});} + + /*otherwise, we should treat this as an adverb*/ + let(y)=v;let(d)=ts.pop().ok_or(err!("adverbs need a verb to apply"))?; + macro_rules! ym {()=>{ + let(ym)=Ym::new(&y).ok_or(err!("invalid monadic adverb {y:?}"))?; + let(d)=match(d){T::V(ref d)=>D::new(d),_=>None}.ok_or(err!("invalid dyadic verb {d:?} for adverb {y:?}"))?; + step!(N::Ym{ym,d,o:rhs}); + }} + macro_rules! yd {($l:ident)=>{ + let(yd)=Yd::new(&y).ok_or(err!("invalid dyadic adverb {y:?}"))?; + let(d)=match(d){T::V(ref d)=>D::new(d),_=>None}.ok_or(err!("invalid dyadic verb {d:?} for adverb {y:?}"))?; + step!(N::Yd{yd,d,l:$l,r:rhs}); + }} + match(ts.pop()){ /*confirm the arity by examining the left-hand operand (NB: put it back if you don't need it!)*/ + /*monadic adverb*/ /*dyadic adverb */ + None =>{ ym!();} Some(T::A(v)) =>{let(l)=b!(v.try_into()?);yd!(l);} + Some(t@T::E|t@T::V(_))=>{ts.push(t);ym!();} Some(T::SY(sy))=>{let(l)=b!(sy.into()); yd!(l);} + } + bail!("fallthrough: unexpected parsing error"); + } + /**get a tuple, representing a window peeking on the next two elements in this token stream.*/ + fn lhs_window(ts:&[T])->(O<&T>,O<&T>){match ts{ + []=>(None,None), [a]=>(None,Some(&a)), [a,b]=>(Some(&a),Some(&b)), [..,a,b]=>(Some(&a),Some(&b)) } } + #[cfg(test)]mod lhs_t{use super::*; + #[test] fn lhs_window_works_on_empty(){is!(matches!(lhs_window(&[]), (None, None))) } + #[test] fn lhs_window_works_on_one (){is!(matches!(lhs_window(&[T::E]), (None, Some(_)))) } + #[test] fn lhs_window_works_on_two (){is!(matches!(lhs_window(&[T::E,T::A(v![])]),(Some(T::E),Some(T::A(_)))))} + #[test] fn lhs_window_works_on_three(){ + is!(matches!(lhs_window(&[T::E,T::A(v![]),T::V(S::from("+"))]),(Some(T::A(_)),Some(_))))} + } + + impl M {fn new(s:&str)->O {use M::*; Some(match s{"i."=>Idot ,"$" =>Shape ,"|:"=>Transpose , + "#" =>Tally ,"[" =>Same ,"]" =>Same , + _=>r!(None)})}} + impl D {fn new(s:&str)->O {use D::*; Some(match s{"+" =>Plus ,"*" =>Mul ,"[" =>Left , + "]" =>Right , _=>r!(None)})}} + impl Ym{fn new(s:&str)->O{use Ym::*;Some(match s{"/" =>Insert,"\\"=>Prefix, _=>r!(None)})}} + impl Yd{fn new(s:&str)->O{use Yd::*;Some(match s{"/" =>Table ,"\\"=>Infix , _=>r!(None)})}} #[cfg(test)]mod t{use super::*; macro_rules! t{($f:ident,$i:literal)=>{#[test]fn $f()->R<()>{let(mut ts)=lex($i)?;let ast=parse(&mut ts)?;ok!()}}} macro_rules! tf{($f:ident,$i:literal)=>{#[test] #[should_panic]fn $f(){let(mut ts)=lex($i).unwrap();let ast=parse(&mut ts).unwrap();}}} - t!(parse_1x1,"1"); t!(parse_1x3,"1 2 3"); t!(parse_tally_1,"# 1"); t!(parse_tally_1x3,"# 1 2 3"); - tf!(parse_tally_as_dyad_fails, "1 # 2"); tf!(parse_tally_with_no_operand, "#"); - tf!(parse_idot_as_dyad_fails, "1 # 2"); tf!(parse_idot_with_no_operand, "i."); - t!(parse_idot_1,"i. 1"); t!(parse_idot_1x2,"i. 4 3"); t!(parse_1plus2,"1 + 2"); - t!(parse_1x3_times_1x3,"1 2 3 * 4 5 6"); t!(parse_tally_tally_1x3,"# # 1 2 3"); - t!(parse_symbol,"a"); t!(parse_symbol_plus_symbol,"a + b"); t!(parse_tally_symbol,"# a"); - t!(parse_symbol_times_symbol,"a * b"); t!(parse_tally_tally_symbol,"# # a"); - tf!(parse_symbol_times_symbol_numbers,"a * b 1"); tf!(parse_tally_tally_symbol_symbol,"# # a b"); - t!(assign_symbol_scalar,"a =: 1"); t!(assign_symbol_slice,"a =: 1 2 3"); t!(assign_symbol_idot,"a =: i. 2 3"); + /*parsing unit tests; t!(..) asserts a success, while tf asserts a failure.*/ + t!(parse_1x1,"1"); t!(parse_1x3,"1 2 3"); + t!(parse_tally_1,"# 1"); t!(parse_tally_1x3,"# 1 2 3"); + tf!(parse_tally_as_dyad_fails,"1 # 2"); tf!(parse_tally_with_no_operand, "#"); + tf!(parse_idot_as_dyad_fails,"1 # 2"); tf!(parse_idot_with_no_operand, "i."); + t!(parse_idot_1,"i. 1"); t!(parse_idot_1x2,"i. 4 3"); + t!(parse_1plus2,"1 + 2"); t!(parse_1x3_times_1x3,"1 2 3 * 4 5 6"); + t!(parse_tally_tally_1x3,"# # 1 2 3"); t!(parse_symbol,"a"); + t!(parse_symbol_plus_symbol,"a + b"); t!(parse_tally_symbol,"# a"); + t!(parse_symbol_times_symbol,"a * b"); t!(parse_tally_tally_symbol,"# # a"); + tf!(parse_bad_symbol_literal,"a * b 1"); tf!(parse_tally_tally_symbol_symbol,"# # a b"); + t!(assign_symbol_scalar,"a =: 1"); t!(assign_symbol_slice,"a =: 1 2 3"); + t!(assign_symbol_idot,"a =: i. 2 3"); t!(parse_monad_then_dyad,"1 + # 1 2 3"); t!(assign_symbol_slice_plus_slice,"a =: 1 2 3 + 1 2 3"); t!(parse_empty,""); + t!(parse_insert_add_to_matrix,"+ / i. 3 3"); t!(parse_prefix_of_sequence, "] \\ i. 3"); + t!(parse_multiplication_table,"1 2 3 * / 1 2 3"); t!(parse_infixes_of_sequence,"4 ] \\ i. 10"); + tf!(parse_no_verb_over_sequence_fails,"/ i. 3 3"); tf!(parse_no_verb_prefix_sequence_fails,"/ i. 3 3"); + // NOTE: J will allow this, but first-class functions are not implemented here. + tf!(parse_add_over_no_sequence_fails,"+ /"); tf!(parse_add_prefix_no_sequence_fails,"+ \\"); + /* TODO: running sums should be supported */ // t!(parse_a_running_sum, "+ / \\ 1 2 3 4 5"); } } From c63f1b038b9819a8b5f9a89539f2f116c574ee58 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:38:11 -0500 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=9A=B0=20D::{add,mul,left,right}=20as?= =?UTF-8?q?sociated=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/a.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/a.rs b/src/a.rs index a9aa2b9..8f1fbcc 100644 --- a/src/a.rs +++ b/src/a.rs @@ -140,11 +140,16 @@ use super::*; use std::marker::PhantomData as PD; let(f)=|i_o,j_o|{i.get(j_o,i_o)};A::new(m_o,n_o)?.init_with(f)} } -/**dyadic verbs*/impl A{ - pub fn d_left (self,r:A)->R{Ok(self)} - pub fn d_right(self,r:A)->R{Ok(r) } - pub fn d_plus(self,r:A)->R{A::d_do(self,r,|x,y|x+y)} - pub fn d_mul (self,r:A)->R{A::d_do(self,r,|x,y|x*y)} +/**dyadic verbs*/impl D{ + /*return dyad function**/ pub fn f(&self)->fn(I,I)->I{use D::*; + match(self){Plus=>D::add, Mul=>D::mul, Left=>D::left, Right=>D::right} } + /*add two numbers*/fn add (x:I,y:I)->I{x+y} /*multiply two numbers*/fn mul (x:I,y:I)->I{x*y} + /*left */fn left(x:I,y:I)->I{x } /*right */fn right(x:I,y:I)->I{ y} +} impl A{ + pub fn d_left (self,r:A)->R{Ok(self) } + pub fn d_right(self,r:A)->R{Ok(r) } + pub fn d_plus(self,r:A) ->R{A::d_do(self,r,D::add)} + pub fn d_mul (self,r:A) ->R{A::d_do(self,r,D::mul)} pub fn d_do(l@A{m:ml,n:nl,..}:A,r@A{m:mr,n:nr,..}:A,f:impl Fn(I,I)->I)->R>{ let(li,ri)=(l.as_i().ok(),r.as_i().ok());let(ls,rs)=(l.as_slice().ok(),r.as_slice().ok()); if let(Some(li),Some(ri))=(li,ri){r!(A::from_i(f(li,ri)))} // two scalars From 1e31fcad676d9cb01112d893d23f6bd061dd6df7 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:39:35 -0500 Subject: [PATCH 5/7] =?UTF-8?q?=E2=AD=90=20core=20logic=20for=20adverbs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/a.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/a.rs b/src/a.rs index 8f1fbcc..14b3029 100644 --- a/src/a.rs +++ b/src/a.rs @@ -165,6 +165,47 @@ use super::*; use std::marker::PhantomData as PD; } } +/**monadic adverbs*/mod adverbs_m{use super::*; + impl Ym{ + /// using this adverb, apply the given dyadic verb to the provided operand. + pub fn apply(&self,d:D,a:A)->R{use Ym::*;match(self){Insert=>Ym::insert(d,a),Prefix=>Ym::prefix(d,a)}} + fn insert(d:D,a:A)->R{let mut v=a.vals();let(i)=v.next().ok_or(err!("empty"))?;v.fold(i,d.f()).try_into()} + fn prefix(d:D,a:A)->R{ + let d_=|i|{use{D::*};match(d){Mul=>1,_=>i}}; + if let Ok(i)=a.as_i() {A::from_i(d_(i))} + else if let Ok(s)=a.as_slice(){let (m,n)=(s.len(),s.len()); + let p=|i,j|if(j>i){Ok(0)}else{a.get(1,j).map(d_)}; + A::new(m,n)?.init_with(p)} + else { bail!("monadic `\\` is not implemented for matrices") }} + } + // === monadic `/`, `Ym::Insert` tests + macro_rules! test_insert{($f:ident,$d:expr,$a:expr,$o:expr)=> + {#[test]fn $f()->R<()>{let(a):R={$a};let(d):D={$d}; // typecheck macro arguments. + let i:I=a.and_then(|a:A|Ym::insert($d,a)).and_then(|a|a.as_i())?; + eq!(i,$o);ok!()}}} + test_insert!(add_a_scalar, D::Plus, A::from_i(42), 42 ); + test_insert!(add_a_sequence, D::Plus, >::try_from(&[1,2,3,4,5]), (1+2+3+4+5) ); + test_insert!(mul_a_sequence, D::Mul , >::try_from(&[1,2,3,4,5]), (1*2*3*4*5) ); +} + +/**dyadic adverbs*/mod adverbs_d{use super::*; + impl Yd{ + /// using this adverb, apply the given dyadic verb to the provided operand. + pub fn apply(&self,d:D,l:A,r:A)->R{use Yd::*;match(self){Table=>Yd::table(d,l,r),Infix=>Yd::infix(d,l,r)}} + fn table(d:D,l:A,r:A)->R{let (l_i,r_i)=(l.as_i() ,r.as_i()); + let (l_s,r_s)=(l.as_slice(),r.as_slice()); + if let (Ok(l),Ok(r))=(l_i,r_i){let(i)=d.f()(l,r);A::from_i(i)} + else if let (Ok(l),Ok(r))=(l_s,r_s){let(m,n)=(l.len(),r.len());let(d)=d.f(); + let f=|i,j|->R{let(x,y)=(l[i-1],r[j-1]);Ok(d(x,y))}; + A::new(m,n)?.init_with(f)} + else {bail!("unexpected fallthrough in Yd::table")}} + fn infix(d:D,l:A,r:A)->R{let(s)=r.as_slice().map_err(|_|err!("infix rhs must be a slice"))?; + let(il)=l.as_i() .map_err(|_|err!("infix lhs must be a scalar"))?.try_into()?; + let(ic)=(s.len()-il)+1; + A::new(ic,il)?.init_with(|i,j|Ok(s[(i-1)+(j-1)]))} + } +} + /**deep-copy*/impl A{ pub fn deep_copy(&self)->R{let A{m,n,l:li,d:di,i:_}=*self;A::new(m,n)?.init_with(|i,j|{self.get(i,j)})} } From 48416493d9cc76279a8ce74533ac896a2b720130 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Tue, 14 Nov 2023 16:42:58 -0500 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20adverbs=20can=20be=20evaluated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/j.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/j.rs b/src/j.rs index 60ecbe9..4d09ead 100644 --- a/src/j.rs +++ b/src/j.rs @@ -13,6 +13,8 @@ fn eval_(ast:B,st:&mut ST)->R>{use{M::*,D::*}; Tally=>a.m_tally(),Transpose=>a.m_trans()}} N::D{d,l,r}=>{let(l,r)=(rec(l)?,rec(r)?);match d{Plus=>l.d_plus(r), Mul=>l.d_mul(r), Left=>l.d_left(r), Right=>l.d_right(r)}} + N::Ym{ym,d,o}=>{rec(o).and_then(|a|ym.apply(d,a))} + N::Yd{yd,d,l,r}=>{let(l,r)=(rec(l)?,rec(r)?);yd.apply(d,l,r)} N::S{sy} =>{st.get(&sy).ok_or(err!("undefined symbol: {sy:?}"))?;todo!("symbol value clone")} - N::V{sy,e} =>{let(a)=rec(e)?;st.insert(sy,a);r!(Ok(None))} + N::E{sy,e} =>{let(a)=rec(e)?;st.insert(sy,a);r!(Ok(None))} }.map(O::Some)} From f489c3287d90eee6c5a3e64ed8534913723e3ee7 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Wed, 15 Nov 2023 11:46:48 -0500 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=9C=20update=20readme=20to=20inclu?= =?UTF-8?q?de=20adverbs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e417e1b..f24efcb 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,7 @@ the jsoftware wiki includes the following story concerning the original Incunabu accordingly, j does not intend to be a fully-fledged implementation of an array language. j values may be integers, arrays, or 2-dimensional matrices; higher-rank matrices are not -implemented. J's adverbs are not implemented. a small number of monadic and dyadic verbs are -provided. variables may be defined. +implemented. a small number of verbs and adverbs are provided. variables may be defined. **monadic verbs** * `i.` "idot" generates a value @@ -34,6 +33,14 @@ provided. variables may be defined. * `[` "left" returns the left value * `]` "right" returns the right value +**monadic adverbs** +* `/` "insert" places a dyadic verb between items of its argument +* `\` "prefix" returns successive prefixes of its argument + +**dyadic adverbs** +* `/` "table" returns a table of entries using a dyadic verb and two arguments +* `\` "infix" applies a verb to successive parts of its right-hand argument + variables are assigned using `=:`. variable names may only contain lowercase ASCII `a-z` characters, numeric `0-9` characters, and `_` underscores. variable names must begin with a lowercase ASCII `a-z` character. @@ -332,12 +339,17 @@ taken together, the expression `1+i.4` adds `1` to each element of the array `0 `1 2 3 4`. `/` is a kind of "adverb." in traditional human languages, an adverb is a part of speech used to -describe how a verb is performed. this same concept holds roughly true for J and K's adverbs. +apply an adjective to a verb. or in other words, it describes how a verb is/was performed. this +same concept holds roughly true for J and K's adverbs. -we won't comprehensively explain adverbs here. in short, `+ /` applies the dyadic `+` operator -"over" its argument(s). +adverbs and [gerunds][j-gerunds] are very similar to higher-order functions. a higher-order +function is a function that either accepts as an argument, or returns, another function. these +constructs provide a way for programmers to abbreviate or abstract over common control flow +patterns. J refers to the `/` adverb in this statement as "insert". this operator places the +dyadic `+` operator between the elements of its argument. thus, `+ / 1 2 3` is equivalent to +`1+2+3`. -so, the expression above has the following structure, expressed as an ascii diagram. +so, the expression above has the following structure: ``` + / 1 + ! 100 @@ -350,7 +362,7 @@ so, the expression above has the following structure, expressed as an ascii diag ┝┳┥ ┗━━━━━━━━━━━━━━━ find the sum of the given argument ┝━━━┳━━━━━━━┥ - ┗━━━━━━━━━━━━ sum "over" the sequence of numbers from 1 through 100 + ┗━━━━━━━━━━━━ add each of the numbers from 1 through 100 ``` these programs share the same structure, save that `i.` is the verb for generating sequences, @@ -693,4 +705,4 @@ todo... [incunabulum]: https://code.jsoftware.com/wiki/Essays/Incunabulum [why-k]: https://xpqz.github.io/kbook/Introduction.html#why-k [api-guidelines]: https://rust-lang.github.io/api-guidelines/future-proofing.html - +[j-gerunds]: https://code.jsoftware.com/wiki/Vocabulary/GerundsAndAtomicRepresentation