#![allow(clippy::collapsible_if)]
use std::str::FromStr;
use crate::vval::{VVal};
use crate::vval::Syntax;
pub mod state;
pub use state::State;
pub use state::{ParseValueError, ParseNumberError, ParseError, ParseErrorKind};
use state::StrPart;
fn add_c_to_vec(v: &mut Vec<u8>, c: char) {
if c.is_ascii() {
v.push((c as u32) as u8);
} else {
let mut b = [0; 4];
for cb in c.encode_utf8(&mut b).as_bytes().iter() {
v.push(*cb);
}
}
}
fn addchr(v: &mut Vec<u8>, s: &mut String, b: bool, c: char) {
if b { add_c_to_vec(v, c); }
else { s.push(c); }
}
fn parse_q_string(ps: &mut State, bytes: bool) -> Result<VVal, ParseError> {
let vec = ps.syn(Syntax::Str);
if bytes {
vec.push(VVal::new_byt(
parse_quoted(ps, Vec::new(), add_c_to_vec)?));
} else {
vec.push(VVal::new_str_mv(
parse_quoted(ps, String::new(), |v, c| v.push(c))?));
}
Ok(vec)
}
fn parse_quoted<F, V>(ps: &mut State, mut v: V, add_char: F) -> Result<V, ParseError>
where F: Fn(&mut V, char)
{
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("string"))); }
let quote_char = ps.expect_some(ps.peek())?;
ps.consume();
let quote_char = match quote_char {
'[' => ']',
'(' => ')',
'{' => '}',
'<' => '>',
_ => quote_char
};
while ps.peek().unwrap_or(quote_char) != quote_char {
let c = ps.expect_some(ps.peek())?;
ps.consume();
add_char(&mut v, c);
}
if !ps.consume_if_eq(quote_char) {
return Err(ps.err(
ParseErrorKind::ExpectedToken(quote_char, "quote string end")));
}
ps.skip_ws_and_comments();
Ok(v)
}
enum NVecKind {
Int,
Flt,
}
fn parse_nvec_body(ps: &mut State, kind: NVecKind) -> Result<VVal, ParseError> {
match ps.expect_some(ps.peek())? {
'(' => {
ps.consume_wsc();
let vec = ps.syn(match kind {
NVecKind::Int => Syntax::IVec,
NVecKind::Flt => Syntax::FVec,
});
vec.push(parse_expr(ps)?);
while ps.consume_if_eq_wsc(',') {
vec.push(parse_expr(ps)?);
}
let dim = vec.len() - 1;
if !ps.consume_if_eq_wsc(')') {
Err(ps.err(ParseErrorKind::ExpectedToken(')', "numerical vector end")))
} else if !(1..=4).contains(&dim) {
Err(ps.err(ParseValueError::VectorLength))
} else {
Ok(vec)
}
},
_ => Err(ps.err(ParseErrorKind::ExpectedToken('(', "numerical vector start"))),
}
}
pub fn parse_2hex(ps: &mut State) -> Result<u8, ParseError> {
let hex = ps.peek2();
if let Some(h) = hex {
let h = h.to_string();
ps.consume();
ps.consume();
if let Ok(cn) = u8::from_str_radix(&h, 16) {
Ok(cn)
} else {
Err(ps.err(ParseErrorKind::BadEscape("Bad hex escape in string")))
}
} else {
Err(ps.err(ParseErrorKind::EOF("string hex escape")))
}
}
pub fn parse_unicode_hex(ps: &mut State) -> Result<char, ParseError> {
if !ps.consume_if_eq('{') {
return Err(ps.err(ParseErrorKind::ExpectedToken('{', "unicode escape start")));
}
let uh = ps.take_while(|c| c.is_digit(16));
let c =
if let Ok(cn) = u32::from_str_radix(&uh.to_string(), 16) {
if let Some(c) = std::char::from_u32(cn) {
c
} else {
return Err(ps.err(ParseErrorKind::BadEscape(
"Bad char in unicode escape in string"
)));
}
} else {
return Err(ps.err(ParseErrorKind::BadEscape(
"Bad unicode hex escape in string"
)));
};
if !ps.consume_if_eq('}') {
return Err(ps.err(ParseErrorKind::ExpectedToken('}', "unicode escape end")));
}
Ok(c)
}
#[derive(Debug, Clone, Copy)]
pub enum EscSeqValue {
Char(char),
Byte(u8),
}
pub fn parse_ascii_char_name(ps: &mut State) -> Result<EscSeqValue, ParseError> {
if ps.consume_lookahead("NULL") || ps.consume_lookahead("null") {
Ok(EscSeqValue::Byte(b'\x00'))
} else if ps.consume_lookahead("SOH") || ps.consume_lookahead("soh") {
Ok(EscSeqValue::Byte(b'\x01'))
} else if ps.consume_lookahead("STX") || ps.consume_lookahead("stx") {
Ok(EscSeqValue::Byte(b'\x02'))
} else if ps.consume_lookahead("ETX") || ps.consume_lookahead("etx") {
Ok(EscSeqValue::Byte(b'\x03'))
} else if ps.consume_lookahead("EOT") || ps.consume_lookahead("eot") {
Ok(EscSeqValue::Byte(b'\x04'))
} else if ps.consume_lookahead("ENQ") || ps.consume_lookahead("enq") {
Ok(EscSeqValue::Byte(b'\x05'))
} else if ps.consume_lookahead("ACK") || ps.consume_lookahead("ack") {
Ok(EscSeqValue::Byte(b'\x06'))
} else if ps.consume_lookahead("BEL") || ps.consume_lookahead("bel") {
Ok(EscSeqValue::Byte(b'\x07'))
} else if ps.consume_lookahead("BS") || ps.consume_lookahead("bs") {
Ok(EscSeqValue::Byte(b'\x08'))
} else if ps.consume_lookahead("HT") || ps.consume_lookahead("ht") {
Ok(EscSeqValue::Byte(b'\x09'))
} else if ps.consume_lookahead("LF") || ps.consume_lookahead("lf") {
Ok(EscSeqValue::Byte(b'\x0a'))
} else if ps.consume_lookahead("VT") || ps.consume_lookahead("vt") {
Ok(EscSeqValue::Byte(b'\x0b'))
} else if ps.consume_lookahead("FF") || ps.consume_lookahead("ff") {
Ok(EscSeqValue::Byte(b'\x0c'))
} else if ps.consume_lookahead("CR") || ps.consume_lookahead("cr") {
Ok(EscSeqValue::Byte(b'\x0d'))
} else if ps.consume_lookahead("SO") || ps.consume_lookahead("so") {
Ok(EscSeqValue::Byte(b'\x0e'))
} else if ps.consume_lookahead("SI") || ps.consume_lookahead("si") {
Ok(EscSeqValue::Byte(b'\x0f'))
} else if ps.consume_lookahead("DLE") || ps.consume_lookahead("dle") {
Ok(EscSeqValue::Byte(b'\x10'))
} else if ps.consume_lookahead("DC1") || ps.consume_lookahead("dc1") {
Ok(EscSeqValue::Byte(b'\x11'))
} else if ps.consume_lookahead("DC2") || ps.consume_lookahead("dc2") {
Ok(EscSeqValue::Byte(b'\x12'))
} else if ps.consume_lookahead("DC3") || ps.consume_lookahead("dc3") {
Ok(EscSeqValue::Byte(b'\x13'))
} else if ps.consume_lookahead("DC4") || ps.consume_lookahead("dc4") {
Ok(EscSeqValue::Byte(b'\x14'))
} else if ps.consume_lookahead("NAK") || ps.consume_lookahead("nak") {
Ok(EscSeqValue::Byte(b'\x15'))
} else if ps.consume_lookahead("SYN") || ps.consume_lookahead("syn") {
Ok(EscSeqValue::Byte(b'\x16'))
} else if ps.consume_lookahead("ETB") || ps.consume_lookahead("etb") {
Ok(EscSeqValue::Byte(b'\x17'))
} else if ps.consume_lookahead("CAN") || ps.consume_lookahead("can") {
Ok(EscSeqValue::Byte(b'\x18'))
} else if ps.consume_lookahead("EM") || ps.consume_lookahead("em") {
Ok(EscSeqValue::Byte(b'\x19'))
} else if ps.consume_lookahead("SUB") || ps.consume_lookahead("sub") {
Ok(EscSeqValue::Byte(b'\x1a'))
} else if ps.consume_lookahead("ESC") || ps.consume_lookahead("esc") {
Ok(EscSeqValue::Byte(b'\x1b'))
} else if ps.consume_lookahead("FS") || ps.consume_lookahead("fs") {
Ok(EscSeqValue::Byte(b'\x1c'))
} else if ps.consume_lookahead("GS") || ps.consume_lookahead("gs") {
Ok(EscSeqValue::Byte(b'\x1d'))
} else if ps.consume_lookahead("RS") || ps.consume_lookahead("rs") {
Ok(EscSeqValue::Byte(b'\x1e'))
} else if ps.consume_lookahead("US") || ps.consume_lookahead("us") {
Ok(EscSeqValue::Byte(b'\x1f'))
} else if ps.consume_lookahead("DEL") || ps.consume_lookahead("del") {
Ok(EscSeqValue::Byte(b'\x7f'))
} else if ps.consume_lookahead("SPACE")|| ps.consume_lookahead("space") {
Ok(EscSeqValue::Byte(b'\x20'))
} else if ps.consume_lookahead("NBSP")|| ps.consume_lookahead("nbsp") {
Ok(EscSeqValue::Byte(b'\xff'))
} else {
Err(ps.err(ParseErrorKind::BadEscape("Bad ascii character name escape")))
}
}
pub fn parse_str_backslash(ps: &mut State) -> Result<EscSeqValue, ParseError>
{
match ps.expect_some(ps.peek())? {
'n' => { ps.consume(); Ok(EscSeqValue::Char('\n')) },
'r' => { ps.consume(); Ok(EscSeqValue::Char('\r')) },
't' => { ps.consume(); Ok(EscSeqValue::Char('\t')) },
'0' => { ps.consume(); Ok(EscSeqValue::Char('\0')) },
'x' => { ps.consume(); Ok(EscSeqValue::Byte(parse_2hex(ps)?)) },
'u' => { ps.consume(); Ok(EscSeqValue::Char(parse_unicode_hex(ps)?)) },
'<' => { ps.consume();
let ret = parse_ascii_char_name(ps);
if !ps.consume_if_eq('>') {
return Err(ps.err(ParseErrorKind::BadEscape(
"Bad ascii character name escape, does not end with '>'")));
}
ret
},
c => { ps.consume(); Ok(EscSeqValue::Char(c)) },
}
}
fn parse_char(ps: &mut State, byte: bool) -> Result<VVal, ParseError> {
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("character"))); }
if !ps.consume_if_eq('\'') {
return Err(ps.err(ParseErrorKind::ExpectedToken('\'', "character start")));
}
let c = ps.expect_some(ps.peek())?;
let ret =
match c {
'\\' => {
ps.consume();
match parse_str_backslash(ps)? {
EscSeqValue::Char(c) => {
if byte {
let c = c as u32;
if c > 0xFF {
VVal::new_byte('?' as u32 as u8)
} else {
VVal::new_byte(c as u8)
}
} else {
VVal::new_char(c)
}
},
EscSeqValue::Byte(b) =>
if byte { VVal::new_byte(b) }
else { VVal::new_char(b as char) },
}
},
_ => {
ps.consume();
if byte {
let c = c as u32;
if c > 0xFF {
VVal::new_byte('?' as u32 as u8)
} else {
VVal::new_byte(c as u8)
}
} else {
VVal::new_char(c)
}
},
};
if !ps.consume_if_eq('\'') {
return Err(ps.err(ParseErrorKind::ExpectedToken('\'', "character end")));
}
ps.skip_ws_and_comments();
Ok(ret)
}
fn parse_string(ps: &mut State, bytes: bool) -> Result<VVal, ParseError> {
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("string"))); }
if !ps.consume_if_eq('"') {
return Err(ps.err(ParseErrorKind::ExpectedToken('\"', "string start")));
}
let vec = ps.syn(Syntax::Str);
let mut s = String::from("");
let mut v : Vec<u8> = Vec::new();
while ps.peek().unwrap_or('"') != '"' {
let c = ps.expect_some(ps.peek())?;
match c {
'\\' => {
ps.consume();
match parse_str_backslash(ps)? {
EscSeqValue::Char(c) => addchr(&mut v, &mut s, bytes, c),
EscSeqValue::Byte(b) =>
if bytes { v.push(b); }
else { s.push(b as char); },
}
},
_ => { ps.consume(); addchr(&mut v, &mut s, bytes, c); },
}
}
if bytes {
vec.push(VVal::new_byt(v));
} else {
vec.push(VVal::new_str(&s));
}
if !ps.consume_if_eq('"') {
return Err(ps.err(ParseErrorKind::ExpectedToken('\"', "string end")));
}
ps.skip_ws_and_comments();
Ok(vec)
}
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::cast_lossless)]
fn parse_num(ps: &mut State) -> Result<VVal, ParseError> {
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("number"))); }
let c = ps.expect_some(ps.peek())?;
let sign = match c {
'-' => {
ps.consume();
if !ps.peek().unwrap_or(' ').is_digit(10) {
ps.skip_ws_and_comments();
return Ok(make_var(ps, "-"));
}
-1
},
'+' => {
ps.consume();
if !ps.peek().unwrap_or(' ').is_digit(10) {
ps.skip_ws_and_comments();
return Ok(make_var(ps, "+"));
}
1
},
_ => 1
};
let radix_or_num = ps.take_while(|c| c.is_digit(10)).to_string();
let (radix, num) = if ps.consume_if_eq('r') {
let radix = radix_or_num.parse::<u8>().unwrap_or(10);
if !(2..=36).contains(&radix) {
return Err(ps.err(ParseNumberError::UnsupportedRadix(radix)));
}
(radix, ps.take_while(|c| c.is_digit(radix as u32)).to_string())
} else if ps.consume_if_eq('x') {
if radix_or_num != "0" {
return Err(ps.err(ParseNumberError::UnsupportedRadixPrefix('x', radix_or_num)));
}
(16, ps.take_while(|c| c.is_digit(16)).to_string())
} else if ps.consume_if_eq('b') {
if radix_or_num != "0" {
return Err(ps.err(ParseNumberError::UnsupportedRadixPrefix('b', radix_or_num)));
}
(2, ps.take_while(|c| c.is_digit(2)).to_string())
} else if ps.consume_if_eq('o') {
if radix_or_num != "0" {
return Err(ps.err(ParseNumberError::UnsupportedRadixPrefix('o', radix_or_num)));
}
(8, ps.take_while(|c| c.is_digit(8)).to_string())
} else {
(10, radix_or_num)
};
let (is_float, fract_num) = if ps.consume_if_eq('.') {
let fract_digits = ps.take_while(|c| c.is_digit(radix as u32)).to_string();
if let Ok(fract_num) = u64::from_str_radix(&fract_digits, radix as u32) {
(true, (fract_num as f64) / (radix as f64).powf(fract_digits.len() as f64))
} else {
return Err(ps.err(ParseNumberError::InvalidFractionalDigits(fract_digits)));
}
} else {
(false, 0.0)
};
ps.skip_ws_and_comments();
match u64::from_str_radix(&num, radix as u32) {
Ok(num) => {
if is_float {
if sign == -1 {
Ok(VVal::Flt(-((num as f64) + fract_num)))
} else {
Ok(VVal::Flt((num as f64) + fract_num))
}
} else {
if sign == -1 {
Ok(VVal::Int((num as i64).wrapping_neg()))
} else {
Ok(VVal::Int(num as i64))
}
}
},
Err(e) => Err(ps.err(ParseNumberError::InvalidRadix(num, radix, e)))
}
}
fn parse_list(ps: &mut State) -> Result<VVal, ParseError> {
if !ps.consume_if_eq_wsc('[') {
return Err(ps.err(ParseErrorKind::ExpectedToken('[', "list start")));
}
let list = ps.syn(Syntax::Lst);
while ps.expect_some(ps.peek())? != ']' {
if ps.consume_if_eq_wsc('*') {
let r = ps.syn(Syntax::VecSplice);
r.push(parse_expr(ps)?);
list.push(r);
} else {
list.push(parse_expr(ps)?);
}
if !ps.consume_if_eq_wsc(',') { break; }
}
if !ps.consume_if_eq_wsc(']') {
return Err(ps.err(ParseErrorKind::ExpectedToken(']', "list end")));
}
Ok(list)
}
fn parse_map(ps: &mut State) -> Result<VVal, ParseError> {
if !ps.consume_if_eq_wsc('{') {
return Err(ps.err(ParseErrorKind::ExpectedToken('{', "map start")));
}
let map = ps.syn(Syntax::Map);
while ps.expect_some(ps.peek())? != '}' {
let c = ps.expect_some(ps.peek())?;
map.push(
if ps.consume_if_eq_wsc('*') {
let r = ps.syn(Syntax::MapSplice);
r.push(parse_expr(ps)?);
r
} else {
let key = if is_ident_start(c) {
VVal::new_sym_mv(parse_identifier(ps)?)
} else {
parse_expr(ps)?
};
if !ps.consume_if_eq_wsc('=') {
return Err(ps.err(ParseErrorKind::ExpectedToken('=', "map key")));
}
let elem = VVal::vec();
elem.push(key);
elem.push(parse_expr(ps)?);
elem
});
if !ps.consume_if_eq_wsc(',') { break; }
}
if !ps.consume_if_eq_wsc('}') {
return Err(ps.err(ParseErrorKind::ExpectedToken('}', "map end")));
}
Ok(map)
}
fn parse_special_value(ps: &mut State) -> Result<VVal, ParseError> {
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("literal value"))); }
let c = ps.expect_some(ps.peek())?;
match c {
'r' => {
ps.consume();
let mode =
if ps.consume_if_eq('g') { "g" }
else if ps.consume_if_eq('s') { "s" }
else { "f" };
let pattern_source = parse_quoted(ps, String::new(), |s, c| s.push(c))?;
let vec = ps.syn(Syntax::Pattern);
vec.push(VVal::new_str_mv(pattern_source));
vec.push(VVal::new_str_mv(String::from(mode)));
Ok(vec)
},
'S' => {
ps.consume();
let selector_source = parse_quoted(ps, String::new(), |s, c| s.push(c))?;
let vec = ps.syn(Syntax::Selector);
vec.push(VVal::new_str_mv(selector_source));
Ok(vec)
},
'M' => {
ps.consume_wsc();
let pat_expr = parse_expr(ps)?;
let vec = ps.syn(Syntax::StructPattern);
vec.push(pat_expr);
Ok(vec)
},
'F' => {
ps.consume_wsc();
let str_lit = parse_string_lit(ps)?;
let vec = ps.syn(Syntax::Formatter);
vec.push(str_lit);
Ok(vec)
},
'\\' => { ps.consume_wsc(); Ok(make_var(ps, "\\")) },
'[' => parse_list(ps),
'{' => parse_map(ps),
'n' => {
if ps.consume_lookahead("none") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
Ok(VVal::None)
},
'i' => {
if ps.consume_lookahead("iter") {
ps.skip_ws_and_comments();
let err = ps.syn(Syntax::Iter);
err.push(parse_expr(ps)?);
Ok(err)
} else {
ps.consume();
parse_nvec_body(ps, NVecKind::Int)
}
},
'D' if ps.consume_lookahead("DEBUG") => {
ps.skip_ws_and_comments();
Ok(ps.syn(Syntax::DebugPrint))
},
'd' => {
if ps.consume_lookahead("data") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
Ok(ps.syn(Syntax::SelfData))
},
's' => {
if ps.consume_lookahead("self") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
Ok(ps.syn(Syntax::SelfObj))
},
't' => {
if ps.consume_lookahead("true") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
Ok(VVal::Bol(true))
},
'f' => {
if ps.consume_lookahead("false") {
ps.skip_ws_and_comments();
Ok(VVal::Bol(false))
} else if ps.lookahead("f(") {
ps.consume();
parse_nvec_body(ps, NVecKind::Flt)
} else {
ps.consume_wsc();
Ok(VVal::Bol(false))
}
},
'e' => {
if ps.consume_lookahead("error") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
let err = ps.syn(Syntax::Err);
err.push(parse_expr(ps)?);
Ok(err)
},
'*' => {
ps.consume_wsc();
let r = ps.syn(Syntax::Deref);
r.push(parse_value(ps)?);
Ok(r)
},
'o' => {
ps.consume_wsc();
if !ps.consume_if_eq_wsc('(') {
return Err(ps.err(ParseErrorKind::ExpectedToken('(', "start of optional value")))
}
if ps.consume_if_eq_wsc(')') {
Ok(ps.syn(Syntax::Opt))
} else {
let a = parse_expr(ps)?;
let opt_v = ps.syn(Syntax::Opt);
opt_v.push(a);
if ps.consume_if_eq_wsc(')') {
Ok(opt_v)
} else {
Err(ps.err(ParseErrorKind::ExpectedToken(')', "end of optional value")))
}
}
},
'p' => {
ps.consume_wsc();
if !ps.consume_if_eq_wsc('(') {
return Err(ps.err(ParseErrorKind::ExpectedToken('(', "pair start")))
}
let a = parse_expr(ps)?;
let ret =
if ps.consume_if_eq_wsc(',') {
let b = parse_expr(ps)?;
VVal::pair(a, b)
} else {
VVal::pair(a, VVal::None)
};
if ps.consume_if_eq_wsc(')') {
Ok(ret)
} else {
Err(ps.err(ParseErrorKind::ExpectedToken(')', "pair end")))
}
},
':' => {
ps.consume_wsc();
let capture = ps.syn(Syntax::CaptureRef);
capture.push(VVal::new_sym_mv(parse_identifier(ps)?));
Ok(capture)
},
'w' if ps.consume_lookahead("weak&")
|| ps.consume_lookahead("w&") => {
ps.skip_ws_and_comments();
let r = ps.syn(Syntax::WRef);
r.push(parse_value(ps)?);
Ok(r)
},
'&' => {
if ps.consume_lookahead("&&") {
ps.skip_ws_and_comments();
let r = ps.syn(Syntax::Ref);
r.push(parse_value(ps)?);
Ok(r)
} else {
ps.consume_wsc();
let r = ps.syn(Syntax::HRef);
r.push(parse_value(ps)?);
Ok(r)
}
},
'%' => {
let mut sp = ps.syn_pos(Syntax::Block);
ps.consume();
if ps.consume_lookahead(":") {
let syntax_name = parse_identifier(ps)?;
if let Ok(syn) = Syntax::from_str(&syntax_name) {
ps.skip_ws_and_comments();
sp.syn = syn;
Ok(VVal::Syn(sp))
} else {
Err(ps.err(ParseValueError::Expected(
"valid WLambda syntax type identifier ($%:Block, $%:Call, ...)")))
}
} else {
Err(ps.err(ParseErrorKind::ExpectedToken(':', "syntax symbol start")))
}
},
'@' => {
let a = ps.syn(Syntax::Accum);
if ps.consume_lookahead("@int") || ps.consume_lookahead("@i") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("int"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@float") || ps.consume_lookahead("@f") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("float"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@string") || ps.consume_lookahead("@s") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("string"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@bytes") || ps.consume_lookahead("@b") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("bytes"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@vec") || ps.consume_lookahead("@v") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("vec"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@map") || ps.consume_lookahead("@m") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("map"));
a.push(parse_expr(ps)?);
Ok(a)
} else if ps.consume_lookahead("@@") {
ps.skip_ws_and_comments();
a.push(VVal::new_sym("@"));
Ok(a)
} else {
Err(ps.err(ParseValueError::ExpectedAccumulator))
}
},
'+' => {
ps.consume_wsc();
Ok(ps.syn(Syntax::Accum))
},
c => {
Err(ps.err(ParseValueError::UnknownSpecialIdentifier(c)))
}
}
}
#[allow(dead_code)]
fn is_var(expr: &VVal) -> bool {
if let Some(VVal::Syn(s)) = expr.at(0) {
return s.syn() == Syntax::Var;
}
false
}
fn is_call(expr: &VVal) -> bool {
if let Some(VVal::Syn(s)) = expr.at(0) {
return s.syn() == Syntax::Call;
}
false
}
fn make_to_call(ps: &State, expr: VVal) -> VVal {
let call = ps.syn(Syntax::Call);
call.push(expr);
call
}
fn make_var(ps: &State, identifier: &str) -> VVal {
let id = ps.syn(Syntax::Var);
id.push(VVal::new_sym(identifier));
id
}
fn make_sym(ps: &State, identifier: &str) -> VVal {
let id = ps.syn(Syntax::Key);
id.push(VVal::new_sym(identifier));
id
}
fn make_binop(ps: &State, op: StrPart) -> VVal {
if op == "&and" {
ps.syn(Syntax::And)
} else if op == "&or" {
ps.syn(Syntax::Or)
} else if op == "//" {
ps.syn(Syntax::BinOpSomeOr)
} else if op == "/?" {
ps.syn(Syntax::BinOpExtSomeOr)
} else if op == "/$e" {
ps.syn(Syntax::BinOpErrOr)
} else if op == "/$o" {
ps.syn(Syntax::BinOpOptOr)
} else if op == "/$n" {
ps.syn(Syntax::BinOpNoneOr)
} else if op == "+" {
ps.syn(Syntax::BinOpAdd)
} else if op == "-" {
ps.syn(Syntax::BinOpSub)
} else if op == "*" {
ps.syn(Syntax::BinOpMul)
} else if op == "/" {
ps.syn(Syntax::BinOpDiv)
} else if op == "%" {
ps.syn(Syntax::BinOpMod)
} else if op == ">" {
ps.syn(Syntax::BinOpGt)
} else if op == "<" {
ps.syn(Syntax::BinOpLt)
} else if op == "<=" {
ps.syn(Syntax::BinOpLe)
} else if op == ">=" {
ps.syn(Syntax::BinOpGe)
} else if op == "==" {
ps.syn(Syntax::BinOpEq)
} else if op == "=>" {
ps.syn(Syntax::OpNewPair)
} else if op == "&>" {
ps.syn(Syntax::OpCallRwL)
} else if op == "<&" {
ps.syn(Syntax::OpCallLwR)
} else if op == "&@>" {
ps.syn(Syntax::OpCallApplyRwL)
} else if op == "<@&" {
ps.syn(Syntax::OpCallApplyLwR)
} else if op == "+>" {
ps.syn(Syntax::OpColAddR)
} else if op == "<+" {
ps.syn(Syntax::OpColAddL)
} else {
make_to_call(ps, make_var(ps, &op.to_string()))
}
}
fn parse_identifier(ps: &mut State) -> Result<String, ParseError> {
if ps.peek().is_none() {
return Err(ps.err(ParseErrorKind::EOF("identifier")));
}
if ps.peek().unwrap() == '`' {
let mut identifier = "".to_string();
ps.consume();
while ps.peek().unwrap_or('`') != '`' {
let c = ps.expect_some(ps.peek())?;
match c {
'\\' => {
ps.consume();
if let Some(c) = ps.peek() {
ps.consume();
identifier.push(c);
} else {
return Err(ps.err(ParseErrorKind::EOF("identifier escape")));
}
},
_ => {
ps.consume();
identifier.push(c);
},
}
}
if !ps.consume_if_eq('`') {
return Err(ps.err(ParseErrorKind::ExpectedToken('`', "quoted identifier end")));
}
ps.skip_ws_and_comments();
Ok(identifier)
} else {
let identifier =
ps.take_while_wsc(|c| {
match c {
'.' | ',' | ';' | '{' | '}'
| '[' | ']' | '(' | ')' | '~' | '|' | '='
=> false,
_ => !c.is_whitespace()
}
});
Ok(identifier.to_string())
}
}
fn is_ident_start(c: char) -> bool {
c.is_alphabetic() || c == '_' || c == '@' || c == '`' || c == '?'
}
fn parse_string_lit(ps: &mut State) -> Result<VVal, ParseError> {
match ps.expect_some(ps.peek())? {
'"' => parse_string(ps, false),
'\'' => parse_char(ps, false),
'$' => { ps.consume();
match ps.expect_some(ps.peek())? {
'b' => { ps.consume();
if ps.lookahead("'") {
parse_char(ps, true)
} else {
parse_string(ps, true)
}
},
'q' => { ps.consume(); parse_q_string(ps, false) },
'Q' => { ps.consume(); parse_q_string(ps, true) },
'c' => {
if ps.consume_lookahead("code") {
ps.skip_ws_and_comments();
} else {
ps.consume_wsc();
}
let code =
if ps.consume_if_eq_wsc('{') {
let code_start_pos = ps.remember();
parse_block(ps, false, false, true)?;
let code_end_pos = ps.remember();
let code = ps.collect(code_start_pos, code_end_pos).to_string();
if !ps.consume_if_eq_wsc('}') {
return Err(ps.err(
ParseErrorKind::ExpectedToken(
'}', "block end")));
}
code
} else {
let code_start_pos = ps.remember();
parse_expr(ps)?;
let code_end_pos = ps.remember();
ps.collect(code_start_pos, code_end_pos).to_string()
};
let vec = ps.syn(Syntax::Str);
vec.push(VVal::new_str_mv(code));
Ok(vec)
},
_ => Err(ps.err(ParseValueError::Expected("literal string"))),
}
},
_ => Err(ps.err(ParseValueError::Expected("literal string"))),
}
}
fn parse_value(ps: &mut State) -> Result<VVal, ParseError> {
match ps.expect_some(ps.peek())? {
'0' ..= '9' | '+' | '-' => parse_num(ps),
_ if ps.lookahead("$q")
|| ps.lookahead("$Q")
|| ps.lookahead("$b")
|| ps.lookahead("$c")
|| ps.lookahead("'")
|| ps.lookahead("\"") => parse_string_lit(ps),
'$' => { ps.consume_wsc(); parse_special_value(ps) },
'(' => {
ps.consume_wsc();
let expr = parse_expr(ps)?;
if !ps.consume_if_eq_wsc(')') {
return Err(ps.err(
ParseErrorKind::ExpectedToken(
')', "sub expression end")));
}
Ok(expr)
},
'{' => {
let syn = ps.syn_raw(Syntax::Func);
let block = parse_block(ps, true, true, true)?;
block.set_at(0, syn);
block.insert_at(1, VVal::None);
Ok(block)
},
'\\' => {
ps.consume_wsc();
if ps.consume_if_eq_wsc(':') {
let syn = ps.syn_raw(Syntax::Func);
let block_name = parse_identifier(ps)?;
ps.skip_ws_and_comments();
let block = parse_block(ps, true, true, true)?;
block.set_at(0, syn);
block.insert_at(1, VVal::new_sym_mv(block_name));
Ok(block)
} else {
let block = ps.syn(Syntax::Func);
let arity =
if ps.lookahead("|") { parse_arity(ps)? }
else { VVal::None };
let next_stmt = parse_stmt(ps)?;
block.push(VVal::None);
block.push(arity);
block.push(next_stmt);
Ok(block)
}
},
':' => {
ps.consume_wsc();
if ps.lookahead("\"") {
let s = parse_string(ps, false)?;
s.at(1).unwrap().with_s_ref(|s: &str|
Ok(make_sym(ps, s)))
} else {
let id = parse_identifier(ps)?;
Ok(make_sym(ps, &id))
}
},
c if is_ident_start(c) => {
let id = parse_identifier(ps)?;
Ok(make_var(ps, &id))
},
_ => {
Err(ps.err(ParseValueError::Expected("literal value, sub \
expression, block, key or identifier")))
}
}
}
#[allow(clippy::collapsible_else_if)]
fn optimize_get_key(ps: &mut State, obj: VVal, value: VVal) -> VVal {
let mut first_syn = obj.v_(0);
if first_syn.get_syn() == Syntax::GetIdx && value.is_int() {
first_syn.set_syn(Syntax::GetIdx2);
obj.set_at(0, first_syn);
obj.push(value);
obj
} else if first_syn.get_syn() == Syntax::GetIdx2 && value.is_int() {
first_syn.set_syn(Syntax::GetIdx3);
obj.set_at(0, first_syn);
obj.push(value);
obj
} else if first_syn.get_syn() == Syntax::GetSym && value.v_(0).get_syn() == Syntax::Key {
first_syn.set_syn(Syntax::GetSym2);
obj.set_at(0, first_syn);
obj.push(value.v_(1));
obj
} else if first_syn.get_syn() == Syntax::GetSym2 && value.v_(0).get_syn() == Syntax::Key {
first_syn.set_syn(Syntax::GetSym3);
obj.set_at(0, first_syn);
obj.push(value.v_(1));
obj
} else if first_syn.get_syn() == Syntax::GetSym && value.v_(0).get_syn() == Syntax::Str {
first_syn.set_syn(Syntax::GetSym2);
obj.set_at(0, first_syn);
obj.push(VVal::new_str_mv(value.v_(1).s_raw()));
obj
} else if first_syn.get_syn() == Syntax::GetSym2 && value.v_(0).get_syn() == Syntax::Str {
first_syn.set_syn(Syntax::GetSym3);
obj.set_at(0, first_syn);
obj.push(VVal::new_str_mv(value.v_(1).s_raw()));
obj
} else {
if value.v_(0).get_syn() == Syntax::Key {
let call = ps.syn(Syntax::GetSym);
call.push(obj);
call.push(value.v_(1));
call
} else if value.v_(0).get_syn() == Syntax::Str {
let call = ps.syn(Syntax::GetSym);
call.push(obj);
call.push(VVal::new_str_mv(value.v_(1).s_raw()));
call
} else if value.is_int() {
let call = ps.syn(Syntax::GetIdx);
call.push(obj);
call.push(value);
call
} else {
let call = ps.syn(Syntax::GetKey);
call.push(obj);
call.push(value);
call
}
}
}
fn parse_field_access(obj_val: VVal, ps: &mut State) -> Result<VVal, ParseError> {
let mut obj = obj_val;
while let Some(c) = ps.peek() {
if c != '.' { break; }
ps.consume_wsc();
let c = if let Some(c) = ps.peek() {
c
} else {
return Err(ps.err(ParseErrorKind::EOF("field access")));
};
let value =
if c.is_digit(10) {
let idx = ps.take_while(|c| c.is_digit(10)).to_string();
if let Ok(idx_num) = idx.parse::<i64>() {
ps.skip_ws_and_comments();
VVal::Int(idx_num)
} else {
return Err(ps.err(ParseNumberError::InvalidIndexDigits(idx)));
}
} else if is_ident_start(c) {
let id = ps.syn(Syntax::Key);
id.push(VVal::new_sym_mv(parse_identifier(ps)?));
id
} else {
parse_value(ps)?
};
if let Some(c) = ps.peek() {
let op = ps.peek_op_ws_la("=");
match c {
_ if op.is_some() => {
let op = op.unwrap();
let op_len = op.len();
let binop = make_binop(ps, op);
ps.consume_wsc_n(op_len);
ps.consume_wsc();
let get_value =
optimize_get_key(ps, obj.clone(), value.clone());
let field_set = ps.syn(Syntax::SetKey);
field_set.push(obj);
field_set.push(value);
field_set.push(
reform_binop(construct_op(binop, get_value, parse_expr(ps)?)));
return Ok(field_set);
},
'=' => {
if ps.peek_op().is_none() {
ps.consume_wsc();
let field_set = ps.syn(Syntax::SetKey);
field_set.push(obj);
field_set.push(value);
field_set.push(parse_expr(ps)?);
return Ok(field_set);
}
},
'[' => {
let call = optimize_get_key(ps, obj, value);
let mut field_call = make_to_call(ps, call);
match parse_arg_list(&mut field_call, ps) {
Ok(_) => return Ok(field_call),
Err(err) => return Err(err),
}
},
_ => { }
}
}
obj = optimize_get_key(ps, obj, value);
}
Ok(obj)
}
fn parse_arg_list<'a, 'b>(call: &'a mut VVal, ps: &'b mut State) -> Result<&'a mut VVal, ParseError> {
if !ps.consume_if_eq_wsc('[') {
return Err(ps.err(ParseErrorKind::ExpectedToken('[', "call arguments start")));
}
let is_apply = ps.consume_if_eq_wsc('[');
if is_apply {
if let VVal::Syn(mut sp) = call.at(0).unwrap_or(VVal::None) {
sp.set_syn(Syntax::Apply);
call.set_at(0, VVal::Syn(sp));
}
let call_argv = parse_expr(ps)?;
call.push(call_argv);
} else {
while let Some(c) = ps.peek() {
if c == ']' { break; }
let call_arg = parse_expr(ps)?;
call.push(call_arg);
if !ps.consume_if_eq_wsc(',') {
break;
}
}
}
if ps.at_end() {
return Err(ps.err(ParseErrorKind::EOF("call args")));
}
if is_apply && !ps.consume_if_eq_wsc(']') {
return Err(ps.err(ParseErrorKind::ExpectedToken(']', "apply arguments end")));
}
if !ps.consume_if_eq_wsc(']') {
return Err(ps.err(ParseErrorKind::ExpectedToken(']', "call arguments end")));
}
Ok(call)
}
fn get_op_binding_power(ps: &State, op: StrPart) -> Result<(i32, i32), ParseError> {
if op == "&>"
|| op == "&@>" { Ok((56, 57)) }
else if op == "<&"
|| op == "<@&" { Ok((55, 54)) }
else if op == "//"
|| op == "/?"
|| op == "/$n"
|| op == "/$e"
|| op == "/$o" { Ok((52, 53)) }
else if op == "^" { Ok((50, 51)) }
else if op == "*"
|| op == "/"
|| op == "%" { Ok((48, 49)) }
else if op == "+"
|| op == "-" { Ok((46, 47)) }
else if op == "<<"
|| op == ">>" { Ok((44, 45)) }
else if op == "<"
|| op == ">"
|| op == ">="
|| op == "<=" { Ok((42, 43)) }
else if op == "=="
|| op == "!=" { Ok((40, 41)) }
else if op == "&" { Ok((29, 30)) }
else if op == "&^" { Ok((27, 28)) }
else if op == "&|" { Ok((25, 26)) }
else if op == "&and" { Ok((23, 24)) }
else if op == "&or" { Ok((21, 22)) }
else if op == "=>" { Ok((20, 19)) }
else if op == "+>" { Ok((10, 11)) }
else if op == "<+" { Ok(( 9, 8)) }
else if op == "%>" { Ok(( 6, 7)) }
else if op == "<%" { Ok(( 5, 4)) }
else {
let c = op.to_string().chars().next().unwrap_or(' ');
Err(ps.err(ParseErrorKind::UnexpectedToken(c, "Operator")))
}
}
fn reform_binop(op: VVal) -> VVal {
match op.v_(0).get_syn() {
Syntax::OpColAddL => {
op.unshift(op.v_(0));
op.set_syn_at(0, Syntax::Call);
op
},
Syntax::OpColAddR => {
op.unshift(op.v_(0));
op.set_syn_at(0, Syntax::Call);
op
},
_ => op
}
}
fn construct_op(binop: VVal, left: VVal, right: VVal) -> VVal {
match binop.at(0).unwrap().get_syn() {
Syntax::OpNewPair => VVal::pair(left, right),
Syntax::OpCallApplyLwR => {
binop.set_syn_at(0, Syntax::Apply);
binop.push(left);
binop.push(right);
binop
},
Syntax::OpCallLwR => {
binop.set_syn_at(0, Syntax::Call);
binop.push(left);
binop.push(right);
binop
},
Syntax::OpCallApplyRwL => {
binop.set_syn_at(0, Syntax::Apply);
binop.push(right);
binop.push(left);
binop
},
Syntax::OpCallRwL => {
binop.set_syn_at(0, Syntax::Call);
binop.push(right);
binop.push(left);
binop
},
Syntax::OpColAddL => {
if right.v_(0).get_syn() == Syntax::OpColAddL {
right.push(left);
right
} else {
binop.push(right);
binop.push(left);
binop
}
},
Syntax::OpColAddR => {
if left.v_(0).get_syn() == Syntax::OpColAddR {
left.push(right);
left
} else {
binop.push(left);
binop.push(right);
binop
}
},
_ => {
binop.push(left);
binop.push(right);
binop
}
}
}
fn parse_binop(left: Option<VVal>, ps: &mut State, bind_pow: i32)
-> Result<VVal, ParseError>
{
let mut left =
if let Some(l) = left { l }
else { parse_call(ps, true)? };
while let Some(op) = ps.peek_op() {
let (l_bp, r_bp) = get_op_binding_power(ps, op)?;
if l_bp < bind_pow {
break;
}
let binop = make_binop(ps, op);
let op_len = op.len();
ps.consume_wsc_n(op_len);
let right = parse_binop(None, ps, r_bp)?;
left = construct_op(binop, left, right);
}
Ok(left)
}
fn parse_call(ps: &mut State, binop_mode: bool) -> Result<VVal, ParseError> {
let call_indent = ps.indent_pos();
let mut value = parse_value(ps)?;
if ps.lookahead_one_of(";),]}|") || ps.at_end() {
return Ok(value);
}
let mut res_call = VVal::None;
while let Some(c) = ps.peek() {
let op = ps.peek_op();
match c {
'[' => {
let mut call = make_to_call(ps, value);
match parse_arg_list(&mut call, ps) {
Ok(_) => { value = call; },
Err(err) => return Err(err),
}
},
'.' => {
value = parse_field_access(value, ps)?;
},
'~' => {
if binop_mode { break; }
ps.consume_wsc();
if let VVal::None = res_call { res_call = make_to_call(ps, value); }
else { res_call.push(value); }
res_call.push(parse_expr(ps)?);
return Ok(res_call);
},
';' | ')' | ',' | ']' | '|' | '}' => {
break;
},
_ if op.is_some() => {
if binop_mode { break; }
value = reform_binop(parse_binop(Some(value), ps, 0)?);
},
'=' => { break; }, _ => {
if binop_mode { break; }
if let VVal::None = res_call { res_call = make_to_call(ps, value); }
else { res_call.push(value); }
if !ps.indent_pos().belongs_to(&call_indent) {
return Err(ps.err(ParseErrorKind::BadIndent(
"Call argument does not belong to call, it needs a higher indentation.")));
}
value = parse_value(ps)?;
},
}
}
if let VVal::None = res_call {
res_call = value;
} else {
res_call.push(value);
}
Ok(res_call)
}
fn parse_expr(ps: &mut State) -> Result<VVal, ParseError> {
let mut call = parse_call(ps, false)?;
if ps.at_end() {
return Ok(call);
}
while let Some(c) = ps.peek() {
match c {
'|' => {
if ps.lookahead("|>") {
ps.consume();
ps.consume_wsc();
let call_right = parse_call(ps, false)?;
let new_call = make_to_call(ps, call);
new_call.push(call_right);
call = new_call;
} else {
let push_front =
if ps.lookahead("||") { ps.consume(); true } else { false };
ps.consume_wsc();
let mut fn_expr = parse_call(ps, false)?;
if !is_call(&fn_expr) {
fn_expr = make_to_call(ps, fn_expr);
}
if push_front {
fn_expr.insert_at(2, call);
} else {
fn_expr.push(call);
}
call = fn_expr;
}
},
_ => {
break;
}
}
}
Ok(call)
}
#[allow(clippy::unnecessary_unwrap)]
#[allow(clippy::collapsible_else_if)]
fn parse_assignment(ps: &mut State, is_def: bool) -> Result<VVal, ParseError> {
if ps.at_end() {
return Err(ps.err(ParseErrorKind::EOF("assignment")));
}
let mut assign = VVal::vec();
if is_def {
assign.push(ps.syn_raw(Syntax::Def));
} else {
assign.push(ps.syn_raw(Syntax::Assign));
}
let mut is_ref = false;
if is_def {
if ps.consume_if_eq_wsc(':') {
let key = parse_identifier(ps)?;
if key == "global" {
assign = ps.syn(Syntax::DefGlobRef);
} else if key == "const" {
assign = ps.syn(Syntax::DefConst);
}
}
} else {
if ps.consume_if_eq_wsc('*') {
assign = ps.syn(Syntax::AssignRef);
is_ref = true;
}
}
let mut destructuring = false;
let ids = VVal::vec();
match ps.expect_some(ps.peek())? {
'(' => {
ps.consume_wsc();
destructuring = true;
while let Some(c) = ps.peek() {
if c == ')' { break; }
ids.push(VVal::new_sym_mv(parse_identifier(ps)?));
if !ps.consume_if_eq_wsc(',') { break; }
}
if ps.at_end() {
return Err(ps.err(ParseErrorKind::EOF(
"destructuring assignment")));
}
if !ps.consume_if_eq_wsc(')') {
return Err(ps.err(ParseErrorKind::ExpectedToken(
')', "destructuring assignment end")));
}
},
_ => { ids.push(VVal::new_sym_mv(parse_identifier(ps)?)); }
}
let op = ps.peek_op_ws_la("=");
if !is_def && !destructuring && op.is_some() && ids.len() == 1 {
let op = op.unwrap();
let op_len = op.len();
let binop = make_binop(ps, op);
ps.consume_wsc_n(op_len);
ps.consume_wsc();
let mut var =
ids.at(0).unwrap().with_s_ref(|var_name|
make_var(ps, var_name));
if is_ref {
let r = ps.syn(Syntax::Deref);
r.push(var);
var = r;
}
assign.push(ids);
assign.push(
reform_binop(construct_op(binop, var, parse_expr(ps)?)));
return Ok(assign);
} else if !ps.consume_if_eq_wsc('=') {
return Err(ps.err(ParseErrorKind::ExpectedToken('=', "assignment")));
}
assign.push(ids);
assign.push(parse_expr(ps)?);
if destructuring {
assign.push(VVal::Bol(destructuring));
}
Ok(assign)
}
fn parse_stmt(ps: &mut State) -> Result<VVal, ParseError> {
match ps.peek() {
Some(c) => {
match c {
'!' => {
ps.consume_wsc();
if ps.consume_if_eq_wsc('@') {
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("special assignment"))); }
let id = parse_identifier(ps)?;
match &id[..] {
"wlambda" => {
let imp = ps.syn(Syntax::Import);
imp.push(VVal::None);
imp.push(VVal::new_sym("wlambda"));
Ok(imp)
},
"import" => {
let prefix =
VVal::new_sym_mv(parse_identifier(ps)?);
ps.skip_ws_and_comments();
let name =
if ps.peek().unwrap_or(';') == ';' {
prefix.clone()
} else {
ps.consume_if_eq_wsc('=');
VVal::new_sym_mv(parse_identifier(ps)?)
};
let imp = ps.syn(Syntax::Import);
imp.push(prefix);
imp.push(name);
Ok(imp)
},
"export" => {
let name = parse_identifier(ps)?;
ps.skip_ws_and_comments();
ps.consume_if_eq_wsc('=');
let expr = parse_expr(ps)?;
let exp = ps.syn(Syntax::Export);
exp.push(VVal::new_sym_mv(name));
exp.push(expr);
Ok(exp)
},
"dump_stack" => Ok(ps.syn(Syntax::DumpStack)),
"dump_vm" => Ok(ps.syn(Syntax::DumpVM)),
_ => Err(ps.err(ParseErrorKind::BadKeyword(id.to_string(), "import or export"))),
}
} else {
parse_assignment(ps, true)
}
},
'.' => {
ps.consume_wsc();
parse_assignment(ps, false)
}
_ => parse_expr(ps),
}
},
None => Err(ps.err(ParseErrorKind::EOF("statement")))
}
}
fn parse_arity(ps: &mut State) -> Result<VVal, ParseError> {
if !ps.consume_if_eq_wsc('|') {
return Err(ps.err(
ParseErrorKind::ExpectedToken('|', "arity definition start")));
}
if ps.at_end() { return Err(ps.err(ParseErrorKind::EOF("parsing arity definition"))); }
let arity = if ps.expect_some(ps.peek())? != '|' {
let min = parse_num(ps)?;
if !min.is_int() {
return Err(ps.err(ParseValueError::ExpectedMinArity));
}
let max = if ps.consume_if_eq_wsc('<') {
let max = parse_num(ps)?;
if !max.is_int() {
return Err(ps.err(ParseValueError::ExpectedMaxArity));
}
max
} else {
min.clone()
};
let arity = VVal::vec();
arity.push(min);
arity.push(max);
arity
} else {
let arity = VVal::vec();
arity.push(VVal::Bol(true));
arity.push(VVal::Bol(true));
arity
};
if !ps.consume_if_eq_wsc('|') {
return Err(ps.err(ParseErrorKind::ExpectedToken(
'|', "arity definition end")));
}
Ok(arity)
}
pub fn parse_block(ps: &mut State, with_arity: bool, delimited: bool, end_delim: bool) -> Result<VVal, ParseError> {
if delimited {
if !ps.consume_if_eq_wsc('{') {
return Err(ps.err(ParseErrorKind::ExpectedToken('{', "block start")));
}
}
let block = ps.syn(Syntax::Block);
if with_arity && ps.lookahead("|") {
block.push(parse_arity(ps)?);
} else if with_arity {
block.push(VVal::None);
}
while let Some(c) = ps.peek() {
if end_delim { if c == '}' { break; } }
let next_stmt = parse_stmt(ps)?;
block.push(next_stmt);
while ps.consume_if_eq_wsc(';') {
while ps.consume_if_eq_wsc(';') { }
if ps.at_end() || (end_delim && ps.peek().unwrap_or(' ') == '}') {
if delimited {
ps.consume_if_eq_wsc('}');
}
return Ok(block);
}
let next_stmt = parse_stmt(ps)?;
block.push(next_stmt);
}
}
if delimited {
if ps.at_end() {
return Err(ps.err(ParseErrorKind::EOF("parsing block")));
}
if !ps.consume_if_eq_wsc('}') {
return Err(ps.err(ParseErrorKind::ExpectedToken('}', "block end")));
}
}
Ok(block)
}
pub fn parse(s: &str, filename: &str) -> Result<VVal, String> {
let mut ps = State::new(s, filename);
parse_block(&mut ps, false, false, true).map_err(|e| format!("{}", e))
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(s: &str) -> String {
let mut ps = State::new(s, "<parser_test>");
match parse_block(&mut ps, false, false, true) {
Ok(v) => v.s(),
Err(e) => panic!("Parse error: {}", e),
}
}
fn parse_error(s: &str) -> String {
let mut ps = State::new(s,"<parser_test>");
match parse_block(&mut ps, false, false, true) {
Ok(v) => panic!("Expected error but got result: {} for input '{}'",
v.s(), s),
Err(e) => format!("Parse error: {}", e),
}
}
#[test]
fn check_parse_numbers() {
assert_eq!(parse("#comment \n10;#fom \n"), "$[$%:Block,10]");
assert_eq!(parse("10;"), "$[$%:Block,10]");
assert_eq!(parse("10.123;"), "$[$%:Block,10.123]");
assert_eq!(parse("-10;"), "$[$%:Block,-10]");
assert_eq!(parse("-0xFF;"), "$[$%:Block,-255]");
assert_eq!(parse("-0xFF.1;"), "$[$%:Block,-255.0625]");
assert_eq!(parse("-0xFF.9;"), "$[$%:Block,-255.5625]");
assert_eq!(parse("-0xFF.A;"), "$[$%:Block,-255.625]");
assert_eq!(parse("-0xFF.F;"), "$[$%:Block,-255.9375]");
}
#[test]
fn check_parse_sym() {
assert_eq!(parse(":\"foo bar\""),
"$[$%:Block,$[$%:Key,:\"foo bar\"]]");
assert_eq!(parse("foo :bar -2.3 2.3"),
"$[$%:Block,$[$%:Call,$[$%:Var,:foo],$[$%:Key,:bar],-2.3,2.3]]");
assert_eq!(parse("foo :bar -x 2"),
"$[$%:Block,$[$%:Call,$[$%:Var,:foo],$[$%:BinOpSub,$[$%:Key,:bar],$[$%:Var,:x]],2]]");
assert_eq!(parse("foo :bar -2 2"),
"$[$%:Block,$[$%:Call,$[$%:Var,:foo],$[$%:Key,:bar],-2,2]]");
}
#[test]
fn check_parse_vec() {
assert_eq!(parse("$[10];"),
"$[$%:Block,$[$%:Lst,10]]");
assert_eq!(parse("$[10, 11.23, -30, -0xFF];"),
"$[$%:Block,$[$%:Lst,10,11.23,-30,-255]]");
assert_eq!(parse("$[10, $[1,2,3], 11.23, -30, -0xFF];"),
"$[$%:Block,$[$%:Lst,10,$[$%:Lst,1,2,3],11.23,-30,-255]]");
}
#[test]
fn check_parse_last_commas() {
assert_eq!(parse("$[10,]"), "$[$%:Block,$[$%:Lst,10]]");
assert_eq!(parse("$[10,20,]"), "$[$%:Block,$[$%:Lst,10,20]]");
assert_eq!(parse("${a=1,b=2,}"), "$[$%:Block,$[$%:Map,$[:a,1],$[:b,2]]]");
assert_eq!(parse("${a=1,}"), "$[$%:Block,$[$%:Map,$[:a,1]]]");
assert_eq!(parse("f[1,]"), "$[$%:Block,$[$%:Call,$[$%:Var,:f],1]]");
}
#[test]
fn check_calls() {
assert_eq!(parse("10"), "$[$%:Block,10]");
assert_eq!(parse("10;"), "$[$%:Block,10]");
assert_eq!(parse("10; 20"), "$[$%:Block,10,20]");
assert_eq!(parse("10;;; 20"), "$[$%:Block,10,20]");
assert_eq!(parse("10;;; 20;"), "$[$%:Block,10,20]");
assert_eq!(parse("10 20;"), "$[$%:Block,$[$%:Call,10,20]]");
assert_eq!(parse("(10) 20;"), "$[$%:Block,$[$%:Call,10,20]]");
}
#[test]
fn check_expr() {
assert_eq!(parse("10 20 30"),
"$[$%:Block,$[$%:Call,10,20,30]]");
assert_eq!(parse("10 20 30 40"),
"$[$%:Block,$[$%:Call,10,20,30,40]]");
assert_eq!(parse("10 || 20 30"),
"$[$%:Block,$[$%:Call,20,10,30]]");
assert_eq!(parse("10 | 20 30"),
"$[$%:Block,$[$%:Call,20,30,10]]");
assert_eq!(parse("10 20 | 30 40"),
"$[$%:Block,$[$%:Call,30,40,$[$%:Call,10,20]]]");
assert_eq!(parse("10 20 || 30 40"),
"$[$%:Block,$[$%:Call,30,$[$%:Call,10,20],40]]");
assert_eq!(parse("10 20 | 30 40 | 50"),
"$[$%:Block,$[$%:Call,50,$[$%:Call,30,40,$[$%:Call,10,20]]]]");
assert_eq!(parse("10 | 20 | 30 | 40"),
"$[$%:Block,$[$%:Call,40,$[$%:Call,30,$[$%:Call,20,10]]]]");
assert_eq!(parse("10[] | 20 | 30 | 40"),
"$[$%:Block,$[$%:Call,40,$[$%:Call,30,$[$%:Call,20,$[$%:Call,10]]]]]");
assert_eq!(parse("10[][] | 20 | 30 | 40"),
"$[$%:Block,$[$%:Call,40,$[$%:Call,30,$[$%:Call,20,$[$%:Call,$[$%:Call,10]]]]]]");
assert_eq!(parse("(10 | 20) | (foo(bar))"),
"$[$%:Block,$[$%:Call,$[$%:Var,:foo],$[$%:Var,:bar],$[$%:Call,20,10]]]");
assert_eq!(parse("10 ~ 20 ~ 30 ~ 40"),
"$[$%:Block,$[$%:Call,10,$[$%:Call,20,$[$%:Call,30,40]]]]");
assert_eq!(parse("10 | 20"), "$[$%:Block,$[$%:Call,20,10]]");
assert_eq!(parse("10 (1 2) (3 4 5) (6 (7))"), "$[$%:Block,$[$%:Call,10,$[$%:Call,1,2],$[$%:Call,3,4,5],$[$%:Call,6,7]]]");
assert_eq!(parse("10[]"), "$[$%:Block,$[$%:Call,10]]");
assert_eq!(parse("10[20, 30]"), "$[$%:Block,$[$%:Call,10,20,30]]");
assert_eq!(parse("10 x[20, 30]"), "$[$%:Block,$[$%:Call,10,$[$%:Call,$[$%:Var,:x],20,30]]]");
assert_eq!(parse("10 x[20, 30] | 50"), "$[$%:Block,$[$%:Call,50,$[$%:Call,10,$[$%:Call,$[$%:Var,:x],20,30]]]]");
assert_eq!(parse("(10).(\"a\" \"b\")"), "$[$%:Block,$[$%:GetKey,10,$[$%:Call,$[$%:Str,\"a\"],$[$%:Str,\"b\"]]]]");
assert_eq!(parse("(10).(\"ab\")"), "$[$%:Block,$[$%:GetSym,10,\"ab\"]]");
assert_eq!(parse("(10).a"), "$[$%:Block,$[$%:GetSym,10,:a]]");
assert_eq!(parse("a.b"), "$[$%:Block,$[$%:GetSym,$[$%:Var,:a],:b]]");
assert_eq!(parse("10 a.b"), "$[$%:Block,$[$%:Call,10,$[$%:GetSym,$[$%:Var,:a],:b]]]");
assert_eq!(parse("(10).(20)"), "$[$%:Block,$[$%:GetIdx,10,20]]");
assert_eq!(parse("10.20 30"), "$[$%:Block,$[$%:Call,10.2,30]]");
assert_eq!(parse("10 20 ~ 30 ~ 40 ~ 50"), "$[$%:Block,$[$%:Call,10,20,$[$%:Call,30,$[$%:Call,40,50]]]]");
assert_eq!(parse("10 20 ~ 30 40 ~ 40 1 2 3 ~ 50 60"), "$[$%:Block,$[$%:Call,10,20,$[$%:Call,30,40,$[$%:Call,40,1,2,3,$[$%:Call,50,60]]]]]");
assert_eq!(parse("10[10[1,2,3 foo] ~ 4]"), "$[$%:Block,$[$%:Call,10,$[$%:Call,$[$%:Call,10,1,2,$[$%:Call,3,$[$%:Var,:foo]]],4]]]");
assert_eq!(parse("foo.b.c.d"), "$[$%:Block,$[$%:GetSym3,$[$%:Var,:foo],:b,:c,:d]]");
assert_eq!(parse("foo.b.c.d[]"), "$[$%:Block,$[$%:Call,$[$%:GetSym3,$[$%:Var,:foo],:b,:c,:d]]]");
assert_eq!(parse("foo.b.c.d[1,2,3]"), "$[$%:Block,$[$%:Call,$[$%:GetSym3,$[$%:Var,:foo],:b,:c,:d],1,2,3]]");
assert_eq!(parse("foo.b.c.d 1 2 3"), "$[$%:Block,$[$%:Call,$[$%:GetSym3,$[$%:Var,:foo],:b,:c,:d],1,2,3]]");
assert_eq!(parse("(foo.b.c.d) 1 2 3"), "$[$%:Block,$[$%:Call,$[$%:GetSym3,$[$%:Var,:foo],:b,:c,:d],1,2,3]]");
assert_eq!(parse("foo.a = 10"), "$[$%:Block,$[$%:SetKey,$[$%:Var,:foo],$[$%:Key,:a],10]]");
assert_eq!(parse("foo.a = 10 | 20"), "$[$%:Block,$[$%:SetKey,$[$%:Var,:foo],$[$%:Key,:a],$[$%:Call,20,10]]]");
assert_eq!(parse("foo.a = 10 ~ 20"), "$[$%:Block,$[$%:SetKey,$[$%:Var,:foo],$[$%:Key,:a],$[$%:Call,10,20]]]");
assert_eq!(parse("4 == 5 ~ 10"), "$[$%:Block,$[$%:Call,$[$%:BinOpEq,4,5],10]]");
assert_eq!(parse("foo.(i) = 10"), "$[$%:Block,$[$%:SetKey,$[$%:Var,:foo],$[$%:Var,:i],10]]");
assert_eq!(parse("foo :x :y 10"), "$[$%:Block,$[$%:Call,$[$%:Var,:foo],$[$%:Key,:x],$[$%:Key,:y],10]]");
}
#[test]
fn check_expr_err() {
assert_eq!(parse_error("foo.a[] = 10"),
"Parse error: <parser_test>:1:9 Expected literal value, sub expression, block, key or identifier\nat code:\n1 | = 10\n");
}
#[test]
fn check_identifier() {
assert_eq!(parse("+"), "$[$%:Block,$[$%:Var,:+]]");
assert_eq!(parse("-"), "$[$%:Block,$[$%:Var,:-]]");
assert_eq!(parse("+ 10 20"), "$[$%:Block,$[$%:Call,$[$%:Var,:+],10,20]]");
assert_eq!(parse("13 + 10 20"), "$[$%:Block,$[$%:Call,$[$%:BinOpAdd,13,10],20]]");
assert_eq!(parse("13 + 10 == 23"),
"$[$%:Block,$[$%:BinOpEq,$[$%:BinOpAdd,13,10],23]]");
assert_eq!(parse("(+ 12 ~ - 24 23) == 13"),
"$[$%:Block,$[$%:BinOpEq,$[$%:Call,$[$%:Var,:+],12,$[$%:Call,$[$%:Var,:-],24,23]],13]]");
assert_eq!(parse("_"), "$[$%:Block,$[$%:Var,:_]]");
assert_eq!(parse("ten"), "$[$%:Block,$[$%:Var,:ten]]");
assert_eq!(parse("tenäß foo"), "$[$%:Block,$[$%:Call,$[$%:Var,:tenäß],$[$%:Var,:foo]]]");
}
#[test]
fn check_primitives() {
assert_eq!(parse("$n"), "$[$%:Block,$n]");
assert_eq!(parse("$none"), "$[$%:Block,$n]");
assert_eq!(parse("$t"), "$[$%:Block,$true]");
assert_eq!(parse("$true"), "$[$%:Block,$true]");
assert_eq!(parse("$f"), "$[$%:Block,$false]");
assert_eq!(parse("$false"), "$[$%:Block,$false]");
}
#[test]
fn check_binops() {
assert_eq!(parse("20 * 10"), "$[$%:Block,$[$%:BinOpMul,20,10]]");
assert_eq!(parse("20 + 10"), "$[$%:Block,$[$%:BinOpAdd,20,10]]");
assert_eq!(parse("20 - 10"), "$[$%:Block,$[$%:BinOpSub,20,10]]");
assert_eq!(parse("20 / 10"), "$[$%:Block,$[$%:BinOpDiv,20,10]]");
assert_eq!(parse("20 % 10"), "$[$%:Block,$[$%:BinOpMod,20,10]]");
assert_eq!(parse("20 > 10"), "$[$%:Block,$[$%:BinOpGt,20,10]]");
assert_eq!(parse("20 < 10"), "$[$%:Block,$[$%:BinOpLt,20,10]]");
assert_eq!(parse("20 <= 10"), "$[$%:Block,$[$%:BinOpLe,20,10]]");
assert_eq!(parse("20 >= 10"), "$[$%:Block,$[$%:BinOpGe,20,10]]");
assert_eq!(parse("20 // 10"), "$[$%:Block,$[$%:BinOpSomeOr,20,10]]");
assert_eq!(parse("20 /? 10"), "$[$%:Block,$[$%:BinOpExtSomeOr,20,10]]");
assert_eq!(parse("20 /$e 10"), "$[$%:Block,$[$%:BinOpErrOr,20,10]]");
assert_eq!(parse("20 /$o 10"), "$[$%:Block,$[$%:BinOpOptOr,20,10]]");
assert_eq!(parse("20 /$n 10"), "$[$%:Block,$[$%:BinOpNoneOr,20,10]]");
assert_eq!(parse("40 20 * 10"), "$[$%:Block,$[$%:Call,40,$[$%:BinOpMul,20,10]]]");
assert_eq!(parse("40 20 * 10 30"), "$[$%:Block,$[$%:Call,40,$[$%:BinOpMul,20,10],30]]");
assert_eq!(parse("40 20 * 10[]"), "$[$%:Block,$[$%:Call,40,$[$%:BinOpMul,20,$[$%:Call,10]]]]");
assert_eq!(parse("40 20[] * 10[]"), "$[$%:Block,$[$%:Call,40,$[$%:BinOpMul,$[$%:Call,20],$[$%:Call,10]]]]");
assert_eq!(parse("20[] * 10[]"), "$[$%:Block,$[$%:BinOpMul,$[$%:Call,20],$[$%:Call,10]]]");
assert_eq!(parse("10 - 20 * 30"), "$[$%:Block,$[$%:BinOpSub,10,$[$%:BinOpMul,20,30]]]");
assert_eq!(parse("10 * 20 - 30"), "$[$%:Block,$[$%:BinOpSub,$[$%:BinOpMul,10,20],30]]");
assert_eq!(parse("10 * 20 - 30 * 2"), "$[$%:Block,$[$%:BinOpSub,$[$%:BinOpMul,10,20],$[$%:BinOpMul,30,2]]]");
assert_eq!(parse("10 * 20 * 30"), "$[$%:Block,$[$%:BinOpMul,$[$%:BinOpMul,10,20],30]]");
assert_eq!(parse("10 - 20 - 30 - 40"), "$[$%:Block,$[$%:BinOpSub,$[$%:BinOpSub,$[$%:BinOpSub,10,20],30],40]]");
assert_eq!(parse("10 - 20 - (30 - 40)"), "$[$%:Block,$[$%:BinOpSub,$[$%:BinOpSub,10,20],$[$%:BinOpSub,30,40]]]");
assert_eq!(parse("$t &and $f"), "$[$%:Block,$[$%:And,$true,$false]]");
assert_eq!(parse("1 &and 2 &and 3 &and 4"), "$[$%:Block,$[$%:And,$[$%:And,$[$%:And,1,2],3],4]]");
assert_eq!(parse("$t &or $f"), "$[$%:Block,$[$%:Or,$true,$false]]");
assert_eq!(parse("$t &and $f &or $f &and $f"), "$[$%:Block,$[$%:Or,$[$%:And,$true,$false],$[$%:And,$false,$false]]]");
assert_eq!(parse("20 & 10"), "$[$%:Block,$[$%:Call,$[$%:Var,:&],20,10]]");
}
#[test]
fn check_assignments() {
assert_eq!(parse("!x=10;"), "$[$%:Block,$[$%:Def,$[:x],10]]");
assert_eq!(parse("! x = 10 ;"), "$[$%:Block,$[$%:Def,$[:x],10]]");
assert_eq!(parse("! x = 10"), "$[$%:Block,$[$%:Def,$[:x],10]]");
assert_eq!(parse("!:global (y,x) = @"), "$[$%:Block,$[$%:DefGlobRef,$[:y,:x],$[$%:Var,:@],$true]]");
assert_eq!(parse(". (a,b) = 10"), "$[$%:Block,$[$%:Assign,$[:a,:b],10,$true]]");
}
#[test]
fn check_func() {
assert_eq!(parse("{}"), "$[$%:Block,$[$%:Func,$n,$n]]");
assert_eq!(parse("{10;}"), "$[$%:Block,$[$%:Func,$n,$n,10]]");
assert_eq!(parse("{10;;;}"), "$[$%:Block,$[$%:Func,$n,$n,10]]");
assert_eq!(parse("{10; 20}"), "$[$%:Block,$[$%:Func,$n,$n,10,20]]");
assert_eq!(parse("{ 10 } { }"), "$[$%:Block,$[$%:Call,$[$%:Func,$n,$n,10],$[$%:Func,$n,$n]]]");
assert_eq!(parse("\\:x { }"), "$[$%:Block,$[$%:Func,:x,$n]]");
assert_eq!(parse("\\ p 1 | 20 ~ 30"), "$[$%:Block,$[$%:Func,$n,$n,$[$%:Call,20,30,$[$%:Call,$[$%:Var,:p],1]]]]");
}
#[test]
fn check_map() {
assert_eq!(parse("${a=10}"), "$[$%:Block,$[$%:Map,$[:a,10]]]");
assert_eq!(parse("${:a=10}"), "$[$%:Block,$[$%:Map,$[$[$%:Key,:a],10]]]");
}
#[test]
fn check_str() {
assert_eq!(parse("\"foo\""), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$q$foo$"), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("\"fo\0o\""), "$[$%:Block,$[$%:Str,\"fo\\0o\"]]");
assert_eq!(parse("\"fo\no\""), "$[$%:Block,$[$%:Str,\"fo\\no\"]]");
assert_eq!(parse("\"fo\ro\""), "$[$%:Block,$[$%:Str,\"fo\\ro\"]]");
assert_eq!(parse("\"fo\\\"o\""), "$[$%:Block,$[$%:Str,\"fo\\\"o\"]]");
assert_eq!(parse("\"fo\x05o\""), "$[$%:Block,$[$%:Str,\"fo\\x05o\\u{9f}\"]]");
assert_eq!(parse("\"fo\x05o\\u{9f}\""), "$[$%:Block,$[$%:Str,\"fo\\x05o\\u{9f}\"]]");
assert_eq!(parse("\"fo\x05o\\u{0009f}\""), "$[$%:Block,$[$%:Str,\"fo\\x05o\\u{9f}\"]]");
assert_eq!(parse("\"fo\x05o\\u{09f}\""), "$[$%:Block,$[$%:Str,\"fo\\x05o\\u{9f}\"]]");
assert_eq!(parse("\"fo\x05o\\u{2400}\""), "$[$%:Block,$[$%:Str,\"fo\\x05o␀\"]]");
assert_eq!(parse("$q foo "), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$q[foo]"), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$q(foo)"), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$q{foo}"), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$q<foo>"), "$[$%:Block,$[$%:Str,\"foo\"]]");
assert_eq!(parse("$b\"\\u{2400}\""), "$[$%:Block,$[$%:Str,$b\"\\xE2\\x90\\x80\"]]");
assert_eq!(parse("$Q'foo'"), "$[$%:Block,$[$%:Str,$b\"foo\"]]");
assert_eq!(parse("$b\"\\x00\\xFF\\xEB\""), "$[$%:Block,$[$%:Str,$b\"\\0\\xFF\\xEB\"]]");
}
#[test]
fn check_parse_field_access() {
assert_eq!(parse("foo.(bar) == 2019"), "$[$%:Block,$[$%:BinOpEq,$[$%:GetKey,$[$%:Var,:foo],$[$%:Var,:bar]],2019]]");
assert_eq!(parse("o.x[]"), "$[$%:Block,$[$%:Call,$[$%:GetSym,$[$%:Var,:o],:x]]]");
assert_eq!(parse("o.1"), "$[$%:Block,$[$%:GetIdx,$[$%:Var,:o],1]]");
assert_eq!(parse("o.1.2"), "$[$%:Block,$[$%:GetIdx2,$[$%:Var,:o],1,2]]");
assert_eq!(parse("o.1.2.3"), "$[$%:Block,$[$%:GetIdx3,$[$%:Var,:o],1,2,3]]");
assert_eq!(parse("o.x"), "$[$%:Block,$[$%:GetSym,$[$%:Var,:o],:x]]");
assert_eq!(parse("o.x.y"), "$[$%:Block,$[$%:GetSym2,$[$%:Var,:o],:x,:y]]");
assert_eq!(parse("o.x.y.z"), "$[$%:Block,$[$%:GetSym3,$[$%:Var,:o],:x,:y,:z]]");
assert_eq!(parse("o.x.(\"y\").z"), "$[$%:Block,$[$%:GetSym3,$[$%:Var,:o],:x,\"y\",:z]]");
assert_eq!(parse("o.(\"x\")"), "$[$%:Block,$[$%:GetSym,$[$%:Var,:o],\"x\"]]");
assert_eq!(parse("o.(\"x\").(\"y\")"), "$[$%:Block,$[$%:GetSym2,$[$%:Var,:o],\"x\",\"y\"]]");
assert_eq!(parse("o.(\"x\").(\"y\").(\"z\")"), "$[$%:Block,$[$%:GetSym3,$[$%:Var,:o],\"x\",\"y\",\"z\"]]");
assert_eq!(parse("o.(1 \"x\")"), "$[$%:Block,$[$%:GetKey,$[$%:Var,:o],$[$%:Call,1,$[$%:Str,\"x\"]]]]");
assert_eq!(parse("o.(1 \"x\").(1 \"y\")"), "$[$%:Block,$[$%:GetKey,$[$%:GetKey,$[$%:Var,:o],$[$%:Call,1,$[$%:Str,\"x\"]]],$[$%:Call,1,$[$%:Str,\"y\"]]]]");
assert_eq!(parse("o.(1 \"x\").(1 \"y\").(1 \"z\")"), "$[$%:Block,$[$%:GetKey,$[$%:GetKey,$[$%:GetKey,$[$%:Var,:o],$[$%:Call,1,$[$%:Str,\"x\"]]],$[$%:Call,1,$[$%:Str,\"y\"]]],$[$%:Call,1,$[$%:Str,\"z\"]]]]");
}
#[test]
fn check_err_val() {
assert_eq!(parse("$e 10 20 30"), "$[$%:Block,$[$%:Err,$[$%:Call,10,20,30]]]");
assert_eq!(parse("$e 10 20 30 | 20"), "$[$%:Block,$[$%:Err,$[$%:Call,20,$[$%:Call,10,20,30]]]]");
}
#[test]
fn check_parse_ref_deref() {
assert_eq!(parse("$& 1"), "$[$%:Block,$[$%:HRef,1]]");
assert_eq!(parse("$&$[1,2]"), "$[$%:Block,$[$%:HRef,$[$%:Lst,1,2]]]");
assert_eq!(parse("$&${z=1}"), "$[$%:Block,$[$%:HRef,$[$%:Map,$[:z,1]]]]");
assert_eq!(parse("$&& 1"), "$[$%:Block,$[$%:Ref,1]]");
assert_eq!(parse("$&&$[1,2]"), "$[$%:Block,$[$%:Ref,$[$%:Lst,1,2]]]");
assert_eq!(parse("$&&${z=1}"), "$[$%:Block,$[$%:Ref,$[$%:Map,$[:z,1]]]]");
assert_eq!(parse("$*${z=1}.f=1"), "$[$%:Block,$[$%:SetKey,$[$%:Deref,$[$%:Map,$[:z,1]]],$[$%:Key,:f],1]]");
assert_eq!(parse("$*xxx.f=1"), "$[$%:Block,$[$%:SetKey,$[$%:Deref,$[$%:Var,:xxx]],$[$%:Key,:f],1]]");
assert_eq!(parse("$*xxx.f"), "$[$%:Block,$[$%:GetSym,$[$%:Deref,$[$%:Var,:xxx]],:f]]");
}
#[test]
fn check_self() {
assert_eq!(parse("$s"), "$[$%:Block,$[$%:SelfObj]]");
assert_eq!(parse("$self"), "$[$%:Block,$[$%:SelfObj]]");
assert_eq!(parse("$d"), "$[$%:Block,$[$%:SelfData]]");
assert_eq!(parse("$data"), "$[$%:Block,$[$%:SelfData]]");
}
#[test]
fn check_backtick_ident() {
assert_eq!(parse("` `"), "$[$%:Block,$[$%:Var,:\" \"]]");
assert_eq!(parse("`\\``"), "$[$%:Block,$[$%:Var,:\"`\"]]");
assert_eq!(parse("`\\\"`"), "$[$%:Block,$[$%:Var,:\"\"\"]]");
assert_eq!(parse("`\"`"), "$[$%:Block,$[$%:Var,:\"\"\"]]");
assert_eq!(parse("!` ` = 10;"), "$[$%:Block,$[$%:Def,$[:\" \"],10]]");
}
#[test]
fn check_apply() {
assert_eq!(parse("fo[[@]]"), "$[$%:Block,$[$%:Apply,$[$%:Var,:fo],$[$%:Var,:@]]]");
assert_eq!(parse("fo[[$[1,2,3]]]"), "$[$%:Block,$[$%:Apply,$[$%:Var,:fo],$[$%:Lst,1,2,3]]]");
assert_eq!(parse("obj.1.field[[_]]"), "$[$%:Block,$[$%:Apply,$[$%:GetSym,$[$%:GetIdx,$[$%:Var,:obj],1],:field],$[$%:Var,:_]]]");
assert_eq!(parse("obj.1.(\"field\")[[_]]"), "$[$%:Block,$[$%:Apply,$[$%:GetSym,$[$%:GetIdx,$[$%:Var,:obj],1],\"field\"],$[$%:Var,:_]]]");
}
#[test]
fn check_right_call() {
assert_eq!(parse("10 |> 20"), "$[$%:Block,$[$%:Call,10,20]]");
assert_eq!(parse("10 20"), "$[$%:Block,$[$%:Call,10,20]]");
assert_eq!(parse("10 |> 20 |> 30"), "$[$%:Block,$[$%:Call,$[$%:Call,10,20],30]]");
assert_eq!(parse("10 20 |> 30"), "$[$%:Block,$[$%:Call,$[$%:Call,10,20],30]]");
assert_eq!(parse("10 20 |> 20 30 40"), "$[$%:Block,$[$%:Call,$[$%:Call,10,20],$[$%:Call,20,30,40]]]");
assert_eq!(parse("10 11 |> 20"), "$[$%:Block,$[$%:Call,$[$%:Call,10,11],20]]");
assert_eq!(parse("10 11 |> 20 |> 30"), "$[$%:Block,$[$%:Call,$[$%:Call,$[$%:Call,10,11],20],30]]");
assert_eq!(parse("10 11 |> 20 21 |> 30"), "$[$%:Block,$[$%:Call,$[$%:Call,$[$%:Call,10,11],$[$%:Call,20,21]],30]]");
assert_eq!(parse("10 11 |> 20 21 |> 30 31"), "$[$%:Block,$[$%:Call,$[$%:Call,$[$%:Call,10,11],$[$%:Call,20,21]],$[$%:Call,30,31]]]");
}
#[test]
fn check_const() {
assert_eq!(parse("!:const X = 32;"), "$[$%:Block,$[$%:DefConst,$[:X],32]]");
assert_eq!(parse("!:const X = 32.4;"), "$[$%:Block,$[$%:DefConst,$[:X],32.4]]");
assert_eq!(parse("!:const X = :XX;"), "$[$%:Block,$[$%:DefConst,$[:X],$[$%:Key,:XX]]]");
assert_eq!(parse("!:const X = \"fo\";"), "$[$%:Block,$[$%:DefConst,$[:X],$[$%:Str,\"fo\"]]]");
assert_eq!(parse("!:const X = $[120];"), "$[$%:Block,$[$%:DefConst,$[:X],$[$%:Lst,120]]]");
assert_eq!(parse("!:const X = ${a=10};"), "$[$%:Block,$[$%:DefConst,$[:X],$[$%:Map,$[:a,10]]]]");
assert_eq!(parse("!:const (A,B,X) = $[1,3,4];"), "$[$%:Block,$[$%:DefConst,$[:A,:B,:X],$[$%:Lst,1,3,4],$true]]");
}
#[test]
fn check_indent_error() {
assert_eq!(parse_error(" 10 11\n12"),
"Parse error: <parser_test>:2:1 Call argument does not belong to call, it needs a higher indentation.\nat code:\n2 | 12\n");
assert_eq!(parse_error("10 11\n12"),
"Parse error: <parser_test>:2:1 Call argument does not belong to call, it needs a higher indentation.\nat code:\n2 | 12\n");
assert_eq!(parse_error("!x = 10 11\n12"),
"Parse error: <parser_test>:2:1 Call argument does not belong to call, it needs a higher indentation.\nat code:\n2 | 12\n");
assert_eq!(parse("10 11\n 12"),
"$[$%:Block,$[$%:Call,10,11,12]]");
assert_eq!(parse("10 11\n;12"),
"$[$%:Block,$[$%:Call,10,11],12]");
assert_eq!(parse("10 11\n;\n12"),
"$[$%:Block,$[$%:Call,10,11],12]");
assert_eq!(parse("10 11\n 12 13"),
"$[$%:Block,$[$%:Call,10,11,12,13]]");
assert_eq!(parse("!x = 10 11\n 12"),
"$[$%:Block,$[$%:Def,$[:x],$[$%:Call,10,11,12]]]");
assert_eq!(parse("!x = 10 11\n 12 13"),
"$[$%:Block,$[$%:Def,$[:x],$[$%:Call,10,11,12,13]]]");
}
#[test]
fn check_nvec() {
assert_eq!(parse("$i(30, 18, 5)"), "$[$%:Block,$[$%:IVec,30,18,5]]");
assert_eq!(parse("$i(30,18,5)"), "$[$%:Block,$[$%:IVec,30,18,5]]");
assert_eq!(parse("$i(0b100,0xC,0o10)"), "$[$%:Block,$[$%:IVec,4,12,8]]");
assert_eq!(parse("$i(0b101 + 1, 0xF * 4)"), "$[$%:Block,$[$%:IVec,$[$%:BinOpAdd,5,1],$[$%:BinOpMul,15,4]]]");
assert_eq!(parse("$f(1.2, 3.4, 5.6, 7.8)"), "$[$%:Block,$[$%:FVec,1.2,3.4,5.6,7.8]]");
assert_eq!(
parse("$f(1/2, 1/3, 1/4, 1/5)"),
"$[$%:Block,$[$%:FVec,$[$%:BinOpDiv,1,2],$[$%:BinOpDiv,1,3],$[$%:BinOpDiv,1,4],$[$%:BinOpDiv,1,5]]]"
);
}
#[test]
fn check_pair_op() {
assert_eq!(parse("match x a => b x => d 5"), "$[$%:Block,$[$%:Call,$[$%:Var,:match],$[$%:Var,:x],$p($[$%:Var,:a],$[$%:Var,:b]),$p($[$%:Var,:x],$[$%:Var,:d]),5]]");
assert_eq!(parse("iter i 0 => 10 ~ 20"), "$[$%:Block,$[$%:Call,$[$%:Var,:iter],$[$%:Var,:i],$p(0,10),20]]");
assert_eq!(parse("iter i 0 => 10 \\20"), "$[$%:Block,$[$%:Call,$[$%:Var,:iter],$[$%:Var,:i],$p(0,10),$[$%:Func,$n,$n,20]]]");
assert_eq!(parse("iter i 0 => 10 { 20 }"), "$[$%:Block,$[$%:Call,$[$%:Var,:iter],$[$%:Var,:i],$p(0,10),$[$%:Func,$n,$n,20]]]");
}
#[test]
fn check_call_op() {
assert_eq!(parse("a &> b x"), "$[$%:Block,$[$%:Call,$[$%:Call,$[$%:Var,:b],$[$%:Var,:a]],$[$%:Var,:x]]]");
assert_eq!(parse("x a &> b"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Var,:b],$[$%:Var,:a]]]]");
assert_eq!(parse("x a &> b &> c"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Var,:c],$[$%:Call,$[$%:Var,:b],$[$%:Var,:a]]]]]");
assert_eq!(parse("x a &> b <& c"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Call,$[$%:Var,:b],$[$%:Var,:a]],$[$%:Var,:c]]]]");
assert_eq!(parse("x a <& b"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Var,:a],$[$%:Var,:b]]]]");
assert_eq!(parse("x a <& b <& c"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Var,:a],$[$%:Call,$[$%:Var,:b],$[$%:Var,:c]]]]]");
assert_eq!(parse("x a <& b &> c"), "$[$%:Block,$[$%:Call,$[$%:Var,:x],$[$%:Call,$[$%:Var,:a],$[$%:Call,$[$%:Var,:c],$[$%:Var,:b]]]]]");
}
#[test]
fn check_coll_add_op() {
assert_eq!(parse("${} +> k => v"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Map],$p($[$%:Var,:k],$[$%:Var,:v])]]");
assert_eq!(parse("${} +> (k => v) +> k2 => v2"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Map],$p($[$%:Var,:k],$[$%:Var,:v]),$p($[$%:Var,:k2],$[$%:Var,:v2])]]");
assert_eq!(parse("${} +> k => v +> k2 => v2"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Map],$p($[$%:Var,:k],$[$%:Var,:v]),$p($[$%:Var,:k2],$[$%:Var,:v2])]]");
assert_eq!(parse("k3 => v3 <+ k2 => v2 <+ k => v <+ ${}"), "$[$%:Block,$[$%:Call,$%:OpColAddL,$[$%:Map],$p($[$%:Var,:k],$[$%:Var,:v]),$p($[$%:Var,:k2],$[$%:Var,:v2]),$p($[$%:Var,:k3],$[$%:Var,:v3])]]");
assert_eq!(parse("${} +> :a => 10 +> 4 + 3 => 20 * 2 +> :x"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Map],$p($[$%:Key,:a],10),$p($[$%:BinOpAdd,4,3],$[$%:BinOpMul,20,2]),$[$%:Key,:x]]]");
assert_eq!(parse("${} +> :a => 10 +> :b => 20 * 2 +> :x"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Map],$p($[$%:Key,:a],10),$p($[$%:Key,:b],$[$%:BinOpMul,20,2]),$[$%:Key,:x]]]");
assert_eq!(parse("a +> b +> c"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Var,:a],$[$%:Var,:b],$[$%:Var,:c]]]");
assert_eq!(parse("c <+ b <+ a"), "$[$%:Block,$[$%:Call,$%:OpColAddL,$[$%:Var,:a],$[$%:Var,:b],$[$%:Var,:c]]]");
assert_eq!(parse("a +> b"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:Var,:a],$[$%:Var,:b]]]");
assert_eq!(parse("b <+ a"), "$[$%:Block,$[$%:Call,$%:OpColAddL,$[$%:Var,:a],$[$%:Var,:b]]]");
assert_eq!(parse("a + x +> b + x"), "$[$%:Block,$[$%:Call,$%:OpColAddR,$[$%:BinOpAdd,$[$%:Var,:a],$[$%:Var,:x]],$[$%:BinOpAdd,$[$%:Var,:b],$[$%:Var,:x]]]]");
assert_eq!(parse("a + c <+ b + c"), "$[$%:Block,$[$%:Call,$%:OpColAddL,$[$%:BinOpAdd,$[$%:Var,:b],$[$%:Var,:c]],$[$%:BinOpAdd,$[$%:Var,:a],$[$%:Var,:c]]]]");
}
#[test]
fn check_char() {
assert_eq!(parse("'f'"), "$[$%:Block,\'f\']");
assert_eq!(parse("'\\xFF'"), "$[$%:Block,\'ÿ\']");
assert_eq!(parse("$b'f'"), "$[$%:Block,$b\'f\']");
assert_eq!(parse("$b'\\xFF'"), "$[$%:Block,$b\'\\xFF\']");
assert_eq!(parse("'\\u{3132}'"), "$[$%:Block,\'ㄲ\']");
assert_eq!(parse("'\\u{FF}'"), "$[$%:Block,\'ÿ\']");
assert_eq!(parse("$b'\\u{3132}'"), "$[$%:Block,$b\'?\']");
assert_eq!(parse("$b'\\u{FF}'"), "$[$%:Block,$b\'\\xFF\']");
}
}