woojin 0.1.3

Woojinlang interpreter written as Rust
Documentation
use crate::{
  ast::Statements,
  NomResult, types::{WoojinValue, parse::parse_value}, error::WoojinError, variable::VariableOption, exec, calc::{parse_calc, Calc}
};

use nom::{
  IResult,
  branch::{alt},
  multi::{many0},
  bytes::complete::{
    tag,
    take_while_m_n,
    take_while1,
  },
  character::complete::{char, multispace1, multispace0},
  sequence::{pair, preceded},
  combinator::{map, map_res, opt, value}
};

pub(crate) type WoojinResult<T> = Result<T, crate::error::WoojinError>;

pub(crate) fn parse_int(input: &str) -> Result<i32, std::num::ParseIntError> {
  i32::from_str_radix(input, 10)
}

pub(crate) fn yee(input: &str) -> NomResult<Statements> {
  let (input, _): (&str, &str) = tag("yee ")(input)?;
  let (input, sign): (&str, Option<&str>) = opt(tag("-"))(input)?;
  let (input, num): (&str, i32) = map_res(take_while_m_n(1, 10, |c: char| c.is_digit(10)), parse_int)(input)?;
  let num: i32 = if let Some(_) = sign { -num } else { num };
  Ok((input, Statements::Yee { code: num }))
}

pub(crate) fn vec2stmt(values: &Vec<&str>) -> WoojinResult<Vec<Box<Statements>>> {
  let mut result: Vec<Box<Statements>> = vec![];
  for value in values {
    let val: Statements = tokenizer(value)?;
    result.push(Box::new(val));
  };
  Ok(result)
}

pub(crate) fn split_comma(input: &str) -> WoojinResult<Vec<&str>> {
  let values: Vec<&str> = if input.trim().contains(",") {
    let mut in_quotes: bool = false;
    let mut start: usize = 0;
    let mut result: Vec<&str> = vec![];
    let chars: std::iter::Enumerate<std::str::Chars> = input.trim().chars().enumerate();
    for (i, c) in chars {
      match c {
        '"' => in_quotes = !in_quotes,
        ',' if !in_quotes => {
          let value: &&str = &input[start..i].trim();
          result.push(value.to_owned()); // push owned value
          start = i + 1;
        }
        _ => {}
      }
    }
    let value: &&str = &input[start..].trim();
    result.push(value.to_owned()); // push owned value
    result
  } else {
    vec![input.trim()]
  };
  Ok(values)
}

pub(crate) fn print(input: &str) -> WoojinResult<Statements> {
  let input: &str = match tag::<&str, _, nom::error::Error<&str>>("print ")(input) {
    Ok((input, _)) => input.trim(),
    Err(_) => return Err(WoojinError::new("Invalid usage of print", crate::error::WoojinErrorKind::Unknown))
  };
  let values: Vec<&str> = split_comma(input)?;
  Ok(Statements::Print { values: vec2stmt(&values)? })
}

pub(crate) fn println(input: &str) -> WoojinResult<Statements> {
  let input: &str = match tag::<&str, _, nom::error::Error<&str>>("println ")(input) {
    Ok((input, _)) => input.trim(),
    Err(_) => return Err(WoojinError::new("Invalid usage of println", crate::error::WoojinErrorKind::Unknown))
  };
  let values: Vec<&str> = split_comma(input)?;
  Ok(Statements::Println{ values: vec2stmt(&values)? })
}

pub(crate) fn roar(input: &str) -> WoojinResult<Statements> {
  let input: &str = match tag::<&str, _, nom::error::Error<&str>>("roar ")(input) {
    Ok((input, _)) => input,
    Err(_) => return Err(WoojinError::new("Invalid usage of roar", crate::error::WoojinErrorKind::Unknown))
  };
  Ok(Statements::Roar { value: test(input)? })
}

pub(crate) fn input(i: &str) -> WoojinResult<Statements> {
  let input: &str = match tag::<&str, _, nom::error::Error<&str>>("input ")(i) {
    Ok((input, _)) => input,
    Err(_) => return Err(WoojinError::new("Invalid usage of input", crate::error::WoojinErrorKind::Unknown))
  };
  Ok(Statements::Input { question: Box::new(tokenizer(&input.to_string())?) })
}

pub(crate) fn sleep(i: &str) -> WoojinResult<Statements> {
  let input: &str = match tag::<&str, _, nom::error::Error<&str>>("sleep ")(i) {
    Ok((input, _)) => input,
    Err(_) => return Err(WoojinError::new("Invalid usage of sleep", crate::error::WoojinErrorKind::Unknown))
  };
  Ok(Statements::Sleep { value: Box::new(tokenizer(&input.to_string())?) })
}

pub(crate) fn parse_variable(input: &str) -> IResult<&str, (String, &str, bool)> {
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, mutable): (&str, bool) = alt((
    value(true, preceded(tag("let"), preceded(multispace1, tag("mut")))),
    value(false, preceded(tag("let"), multispace1)),
  ))(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, var_name): (&str, String) = map(
    pair(
      take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
      many0(preceded(multispace1, take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'))),
    ),
    |(first, rest)| {
      let mut name: String = String::from(first);
      for s in rest {
        name.push(' ');
        name.push_str(s);
      }
      name
    },
  )(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, _): (&str, char) = char('=')(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  Ok((input, (var_name.to_string(), input, mutable)))
}

pub(crate) fn parse_assignment(input: &str) -> IResult<&str, (String, &str, String)> {
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, var_name): (&str, String) = map(
    pair(
      preceded(char('$'), take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_')),
      many0(preceded(multispace1, take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'))),
    ),
    |(first, rest)| {
      let mut name: String = String::from(first);
      for s in rest {
        name.push(' ');
        name.push_str(s);
      }
      name
    },
  )(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, _): (&str, char) = char('=')(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  let (input, value): (&str, String) = map(
    pair(
      take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
      many0(preceded(multispace1, take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'))),
    ),
    |(first, rest)| {
      let mut val: String = String::from(first);
      for s in rest {
        val.push(' ');
        val.push_str(s);
      }
      val
    },
  )(input)?;
  let (input, _): (&str, &str) = multispace0(input)?;
  Ok((input, (var_name.to_string(), input, value.to_string())))
}

pub(crate) fn test(input: &str) -> WoojinResult<WoojinValue> {
  match tokenizer(&input.to_string())? {
    Statements::Value { value: val } => Ok(val),
    a => match exec(&a) {
      Ok(val) => Ok(val),
      Err(e) => Err(e)
    }
  }
}

pub(crate) fn parse_variable_name(input: &str) -> IResult<&str, String> {
  let (input, a): (&str, &str) = preceded(char('$'), take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'))(input)?;
  Ok((input, a.to_string()))
}

pub(crate) fn tokenizer(line: &impl ToString) -> Result<Statements, crate::error::WoojinError> {
  let line: String = line.to_string().trim().to_string();
  match line {
    line if line == "" => Ok(Statements::Value { value: WoojinValue::String("".to_string()) }),
    line if line.starts_with("//") => Ok(Statements::Comment(line[2..].trim().to_string())),
    line if line.starts_with("yee") => match yee(&line)? { (_, a) => { return Ok(a); }, }
    line if line.starts_with("println") => Ok(println(&line)?),
    line if line.starts_with("print") => Ok(print(&line)?),
    line if line.starts_with("roar") => Ok(roar(&line)?),
    line if line.starts_with("input") => Ok(input(&line)?),
    line if line.starts_with("sleep") => Ok(sleep(&line)?),
    line if line.starts_with("let") => {
      let (_, (var_name, input, mutable)): (&str, (String, &str, bool)) = parse_variable(&line)?;
      let stmts: Statements = tokenizer(&input.to_string())?;
      Ok(Statements::Let {
        name: var_name,
        stmt: Box::new(stmts),
        option: VariableOption::new(Some(mutable), None)
      })
    },
    line if line.starts_with("$") && line.contains("=") => {
      let (_, (var_name, _, value)) = parse_assignment(&line)?;
      let stmts = tokenizer(&value.to_string())?;
      Ok(Statements::Assignment { name: var_name, value: Box::new(stmts) })
    },
    _ => match parse_calc(line.as_str()) {
      Ok(val) => {
        match val.1 {
          Calc::Value(a) => Ok(Statements::Value {value: a}),
          _ => Ok(Statements::Calc(val.1))
        }
      },
      _ => match parse_value(line.as_str()) {
        Ok(val) => Ok(Statements::Value {value: val.1}),
        Err(_) => Err(WoojinError::new(format!("Unknown token \"{}\"", line), crate::error::WoojinErrorKind::UnknownToken))
      }
    }
  }
}