use std::sync::Arc;
use imbl::{vector, Vector};
use nom::{branch::alt, combinator::map, bytes::complete::tag, character::complete::{char, multispace0}, combinator::{opt, value}, multi::fold_many0, sequence::{delimited, pair, preceded, terminated}, IResult, Parser};
use crate::{parser::{doc::StofParseError, expr::expr, statement::{assign::assign, declare::declare_statement, forin::for_in_loop, fors::for_loop, ifs::if_statement, switch::switch_statement, trycatch::try_catch_statement, whiles::{break_statement, continue_statement, loop_statement, while_statement}}, whitespace::whitespace}, runtime::{instruction::{Instruction, Instructions}, instructions::{empty::EmptyIns, ret::RetIns, Base, POP_STACK, POP_SYMBOL_SCOPE, PUSH_SYMBOL_SCOPE, SUSPEND}, Type}};
pub mod declare;
pub mod assign;
pub mod ifs;
pub mod whiles;
pub mod fors;
pub mod forin;
pub mod switch;
pub mod trycatch;
pub fn block(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, mut statements) = delimited(
char('{'),
multistatements,
preceded(whitespace, char('}'))
).parse(input)?;
if statements.is_empty() { return Ok((input, Default::default())); }
statements.push_front(PUSH_SYMBOL_SCOPE.clone());
statements.push_back(POP_SYMBOL_SCOPE.clone());
Ok((input, statements))
}
pub fn noscope_block(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, statements) = delimited(
char('{'),
multistatements,
preceded(whitespace, char('}'))
).parse(input)?;
Ok((input, statements))
}
fn multistatements(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let mut seen_return = false;
fold_many0(
statement,
Vector::default,
move |mut statements, current| {
if !seen_return { for ins in current {
if let Some(_) = ins.as_dyn_any().downcast_ref::<RetIns>() { seen_return = true; }
statements.push_back(ins);
if seen_return { break; }
}
}
statements
}
).parse(input)
}
pub fn statement(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, statements) = alt((
if_statement,
while_statement,
loop_statement,
for_in_loop,
for_loop,
switch_statement,
try_catch_statement,
terminated(continue_statement, preceded(multispace0, char(';'))),
terminated(break_statement, preceded(multispace0, char(';'))),
return_statement,
terminated(declare_statement, preceded(multispace0, char(';'))),
terminated(assign, preceded(multispace0, char(';'))),
async_block,
block,
expr_statement,
value(Vector::default(), preceded(whitespace, char(';'))) )).parse(input)?;
Ok((input, statements))
}
fn return_statement(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, res) = alt((
value(Arc::new(RetIns { expr: None }) as Arc<dyn Instruction>, terminated(tag("return"), preceded(multispace0, char(';')))),
map(delimited(tag("return"), expr, preceded(multispace0, char(';'))), |expr| Arc::new(RetIns { expr: Some(expr) }) as Arc<dyn Instruction>)
)).parse(input)?;
Ok((input, vector![res]))
}
fn expr_statement(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, ins) = pair(expr, opt(char(';'))).parse(input)?;
let mut res = Vector::default();
if ins.1.is_some() {
res.push_back(Arc::new(EmptyIns { ins: ins.0 }) as Arc<dyn Instruction>);
} else {
res.push_back(Arc::new(RetIns { expr: Some(ins.0) })); }
Ok((input, res))
}
fn async_block(input: &str) -> IResult<&str, Vector<Arc<dyn Instruction>>, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, ins) = preceded(tag("async"), alt((
block,
statement
))).parse(input)?;
let res: Vector<Arc<dyn Instruction>> = vector![
Arc::new(Base::Spawn((Instructions::from(ins), Type::Void))) as Arc<dyn Instruction>,
POP_STACK.clone(), SUSPEND.clone(), ];
Ok((input, res))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{model::Graph, parser::statement::block, runtime::{instructions::block::Block, Runtime}};
#[test]
fn declare_ret_block() {
let (_input, res) = block("{ const x = 10; ;; ; ; { let u = &x; u } }").unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 10.into());
}
#[test]
fn assignment() {
let (_input, res) = block(r#"{
let v = 42;
v *= 8;
v -= 300.;
v += 4;
v as int
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 40.into());
}
#[test]
fn if_statement() {
let (_input, res) = block(r#"{
let x = 10;
if (x > 40) 100
else if (x == 32) { 42 } // this is a comment here
else if (x == 10) 300
else 200
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 300.into());
}
#[test]
fn while_statement() {
let (_input, res) = block(r#"{
let x = 0;
let y = 200;
^outer while (x < 1e4) {
while (y > 0) {
if (y < 5 || x > 300) break ^outer;
y -= 1;
}
x += 1;
}
y
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 4.into());
}
#[test]
fn for_statement() {
let (_input, res) = block(r#"{
let total = 0;
^outer for (let x = 0; x < 100; x += 1) {
for (let y = 0; y < 50; y += 1) {
total += 1;
if (x > 80 && y > 30) break ^outer;
}
total += 1;
}
total
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 4163.into());
}
#[test]
fn switch_statement() {
let (_input, res) = block(r#"{
const val = 'hii';
switch (val) {
case 'a': 42
case 'hi': 100
case 'hello': 32
default: {
700
}
}
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, 700.into());
}
#[test]
fn try_catch_statement() {
let (_input, res) = block(r#"{
try 3.4.6 + 5
catch (error: str) {
error
}
}"#).unwrap();
let mut graph = Graph::default();
let val = Runtime::eval(&mut graph, Arc::new(Block { ins: res })).unwrap();
assert_eq!(val, "NotImplemented".into());
}
}