use crate::ast;
/*use pest::iterators::Pair;
use pest::Parser;
use pest_derive::Parser;*/
use crate::ast::StrInner;
use crate::ast::StringFlags;
use crate::builtin::Builtin;
use crate::compiler::ErrorReport;
use crate::compiler::RainbowColorGenerator;
use crate::compiler_info::CodeArea;
//use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use ariadne::Fmt;
use internment::Intern;
//use ast::ValueLiteral;
use logos::Lexer;
use logos::Logos;
use crate::compiler::create_error;
use crate::compiler_types::ImportType;
pub type FileRange = (usize, usize);
macro_rules! expected {
($expected:expr, $tokens:expr, $notes:expr, $a:expr) => {
return Err(SyntaxError::ExpectedErr {
expected: $expected,
found: format!(
"{}: \"{}\"",
match $a {
Some(t) => t.typ(),
None => "EOF",
},
$tokens.slice()
),
pos: $tokens.position(),
file: $notes.file.clone(),
})
};
}
#[derive(Debug)]
pub enum SyntaxError {
ExpectedErr {
expected: String,
found: String,
pos: FileRange,
file: PathBuf,
},
UnexpectedErr {
found: String,
pos: FileRange,
file: PathBuf,
},
SyntaxError {
message: String,
pos: FileRange,
file: PathBuf,
},
}
pub fn is_valid_symbol(name: &str, tokens: &Tokens, notes: &ParseNotes) -> Result<(), SyntaxError> {
if name.starts_with('_') && name.ends_with('_') {
if Builtin::from_str(name).is_ok() {
Ok(())
} else {
Err(SyntaxError::SyntaxError {
message: format!("{} is an invalid variable/property/argument name", name),
pos: tokens.position(),
file: notes.file.clone(),
})
}
} else {
Ok(())
}
}
impl From<SyntaxError> for ErrorReport {
fn from(err: SyntaxError) -> ErrorReport {
use crate::compiler_info::CompilerInfo;
//write!(f, "SuperErrorSideKick is here!")
let mut colors = RainbowColorGenerator::new(60.0, 1.0, 0.8);
let a = colors.next();
let b = colors.next();
match err {
SyntaxError::ExpectedErr {
expected,
found,
pos,
file,
} => create_error(
CompilerInfo::from_area(CodeArea {
pos,
file: Intern::new(file.clone()),
}),
"Syntax error",
&[(
CodeArea {
pos,
file: Intern::new(file),
},
&format!(
"{} {}, {} {}",
"Expected".fg(b),
expected,
"found".fg(a),
found
),
)],
None,
),
SyntaxError::UnexpectedErr { found, pos, file } => create_error(
CompilerInfo::from_area(CodeArea {
pos,
file: Intern::new(file.clone()),
}),
"Syntax error",
&[(
CodeArea {
pos,
file: Intern::new(file),
},
&format!("Unexpected {}", found),
)],
None,
),
SyntaxError::SyntaxError { message, pos, file } => create_error(
CompilerInfo::from_area(CodeArea {
pos,
file: Intern::new(file.clone()),
}),
"Syntax error",
&[(
CodeArea {
pos,
file: Intern::new(file),
},
&message,
)],
None,
),
}
}
}
#[derive(Logos, Debug, PartialEq, Copy, Clone)]
pub enum Token {
//OPERATORS
#[token("->")]
Arrow,
#[token("=>")]
ThickArrow,
#[token("<=>")]
Swap,
#[token("|")]
Either,
#[token("||")]
Or,
#[token("&&")]
And,
#[token("==")]
Equal,
#[token("!=")]
NotEqual,
#[token(">=")]
MoreOrEqual,
#[token("<=")]
LessOrEqual,
#[token(">")]
MoreThan,
#[token("<")]
LessThan,
#[token("*")]
Star,
#[token("%")]
Modulo,
#[token("^")]
Power,
#[token("**")]
DoubleStar,
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("/")]
Slash,
#[token("/%")]
IntDividedBy,
#[token("has")]
Has,
#[token("!")]
Exclamation,
#[token("=")]
Assign,
#[token("+=")]
Add,
#[token("-=")]
Subtract,
#[token("*=")]
Multiply,
#[token("/=")]
Divide,
#[token("/%=")]
IntDivide,
#[token("^=")]
Exponate,
#[token("%=")]
Modulate,
#[token("++")]
Increment,
#[token("--")]
Decrement,
#[token("as")]
As,
//VALUES
#[regex(r"([a-zA-Z_][a-zA-Z0-9_]*)|\$")]
Symbol,
#[regex(r"[0-9]+(\.[0-9]+)?")]
Number,
#[regex(r#"[a-z]?"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'"#)]
StringLiteral,
#[token("true")]
True,
#[token("false")]
False,
#[regex(r"[0-9?]+[gbci]")]
Id,
//TERMINATORS
#[token(",")]
Comma,
#[token("{")]
OpenCurlyBracket,
#[token("}")]
ClosingCurlyBracket,
#[token("[")]
OpenSquareBracket,
#[token("]")]
ClosingSquareBracket,
#[token("(")]
OpenBracket,
#[token(")")]
ClosingBracket,
#[token(":")]
Colon,
#[token("::")]
DoubleColon,
#[token(".")]
Period,
#[token("..")]
DotDot,
#[token("@")]
At,
#[token("#")]
Hash,
#[token("&")]
Ampersand,
//KEY WORDS
#[token("return")]
Return,
/*#[token("<+")]
Add,*/
#[token("impl")]
Implement,
#[token("for")]
For,
#[token("in")]
In,
#[token("throw")]
ErrorStatement,
#[token("if")]
If,
#[token("else")]
Else,
#[token("switch")]
Switch,
#[token("case")]
Case,
#[token("break")]
Break,
#[token("continue")]
Continue,
#[token("while")]
While,
#[token("obj")]
Object,
#[token("trigger")]
Trigger,
#[token("import")]
Import,
#[token("extract")]
Extract,
#[token("null")]
Null,
#[token("type")]
Type,
#[token("let")]
Let,
#[token("self")]
SelfVal,
#[token("sync")]
Sync,
//STATEMENT SEPARATOR
#[regex(r"[\n\r;]+")]
StatementSeparator,
#[error]
#[regex(r"[ \t\f]+|/\*[^*]*\*(([^/\*][^\*]*)?\*)*/|//[^\n]*", logos::skip)]
Error,
}
impl Token {
fn typ(&self) -> &'static str {
use Token::*;
match self {
Or | And | Equal | NotEqual | MoreOrEqual | LessOrEqual | MoreThan | LessThan
| Star | Modulo | Power | Plus | Minus | Slash | Exclamation | Assign | Add
| Subtract | Multiply | Divide | IntDividedBy | IntDivide | As | Has | Either
| DoubleStar | Exponate | Modulate | Increment | Decrement | Swap => "operator",
Symbol => "identifier",
Number => "number literal",
StringLiteral => "string literal",
True | False => "boolean literal",
Id => "ID literal",
Comma | OpenCurlyBracket | ClosingCurlyBracket | OpenSquareBracket
| ClosingSquareBracket | OpenBracket | ClosingBracket | Colon | DoubleColon
| Period | DotDot | At | Hash | Arrow | ThickArrow | Ampersand => "terminator",
Sync => "reserved keyword (not currently in use, but may be used in future updates)",
Return | Implement | For | In | ErrorStatement | If | Else | Object | Trigger
| Import | Extract | Null | Type | Let | SelfVal | Break | Continue | Switch | Case
| While => "keyword",
//Comment | MultiCommentStart | MultiCommentEnd => "comment",
StatementSeparator => "statement separator",
Error => "unknown",
}
}
}
pub struct ParseNotes {
pub tag: ast::Attribute,
pub file: PathBuf,
}
impl ParseNotes {
pub fn new(path: PathBuf) -> Self {
ParseNotes {
tag: ast::Attribute::new(),
file: path,
}
}
}
#[derive(Clone)]
pub struct Tokens<'a> {
iter: Lexer<'a, Token>,
stack: Vec<(Option<Token>, String, core::ops::Range<usize>)>,
line_breaks: Vec<u32>,
//index 0 = element of iter / last element in stack
index: usize,
}
impl<'a> Tokens<'a> {
fn new(iter: Lexer<'a, Token>) -> Self {
Tokens {
iter,
stack: Vec::new(),
line_breaks: vec![0],
index: 0,
}
}
fn inner_next(&mut self) -> Option<Token> {
if self.index == 0 {
let next_elem = self.iter.next();
let slice = self.iter.slice().to_string();
let range = self.iter.span();
self.stack.push((next_elem, slice, range));
next_elem
} else {
self.index -= 1;
self.stack[self.stack.len() - self.index - 1].0
}
}
fn next(&mut self, ss: bool) -> Option<Token> {
//println!("what ok {}", self.index);
// if self.index > 0 {
// let curr_element = self.stack[self.stack.len() - self.index].0;
// // if curr_element == Some(Token::MultiCommentStart) {
// // //println!("comment time");
// // let mut nest = 0;
// // loop {
// // let next_elem = self.inner_next();
// // if next_elem == Some(Token::MultiCommentStart) {
// // nest += 1;
// // } else if next_elem == Some(Token::MultiCommentEnd) {
// // nest -= 1;
// // }
// // if nest == 0 {
// // break;
// // }
// // }
// // self.inner_next();
// // }
// }
let next_element = self.inner_next();
if !ss && next_element == Some(Token::StatementSeparator) {
self.next(ss)
} else {
next_element
}
}
fn previous(&mut self) -> Option<Token> {
/*self.index += 1;
let len = self.stack.len();
if len > self.index {
if self.stack[len - self.index - 1].0 == Token::StatementSeparator
|| self.stack[len - self.index - 1].0 == Token::Comment
{
self.previous()
} else if len - self.index >= 1 {
Some(self.stack[len - self.index - 1].0)
} else {
None
}
} else {
None
}*/
self.previous_no_ignore(false)
}
fn previous_no_ignore(&mut self, ss: bool) -> Option<Token> {
self.index += 1;
let len = self.stack.len();
if len > self.index {
if !ss && self.stack[len - self.index - 1].0 == Some(Token::StatementSeparator) {
self.previous_no_ignore(ss)
} else if len - self.index >= 1 {
self.stack[len - self.index - 1].0
} else {
None
}
} else {
None
}
}
/*fn current(&self) -> Option<Token> {
let len = self.stack.len();
if len == 0 {
None
} else if len - self.index < 1 {
None
} else {
Some(self.stack[len - self.index - 1].0)
}
}*/
fn slice(&self) -> String {
self.stack[self.stack.len() - self.index - 1].1.clone()
}
fn position(&self) -> (usize, usize) {
if self.stack.len() - self.index == 0 {
return (0, 0);
}
let file_pos1 = self.stack[self.stack.len() - self.index - 1].2.start;
let file_pos2 = self.stack[self.stack.len() - self.index - 1].2.end;
(file_pos1, file_pos2)
}
/*fn abs_position(&self) -> usize {
self.stack[self.stack.len() - self.index - 1].2.start
}*/
/*fn span(&self) -> core::ops::Range<usize> {
self.stack[self.stack.len() - self.index - 1].2.clone()
}*/
}
//type TokenList = Peekable<Lexer<Token>>;
const STATEMENT_SEPARATOR_DESC: &str = "Statement separator (line-break or ';')";
pub fn parse_spwn(
mut unparsed: String,
path: PathBuf,
) -> Result<(Vec<ast::Statement>, ParseNotes), SyntaxError> {
unparsed = unparsed.replace("\r\n", "\n");
let tokens_iter = Token::lexer(&unparsed);
let mut tokens = Tokens::new(tokens_iter);
let mut statements = Vec::<ast::Statement>::new();
let mut notes = ParseNotes::new(path);
let mut line_breaks = Vec::<u32>::new();
let mut current_index: u32 = 0;
for line in unparsed.lines() {
current_index += line.len() as u32;
line_breaks.push(current_index);
current_index += 1; //line break char
}
tokens.line_breaks = line_breaks;
let start_tag = check_for_tag(&mut tokens, &mut notes)?;
notes.tag = start_tag;
loop {
//+ do something if we have tokens. if no more tokens, leave loop
match tokens.next(false) {
//oops we just advanced the tokens in an attempt to check if we have any
Some(_) => {
tokens.previous_no_ignore(false); //bring tokens back to original
//+ we are going to parse the tokens
let parsed = parse_statement(&mut tokens, &mut notes)?;
// if parsed.comment.0 == None && !statements.is_empty() {
// parsed.comment.0 = statements.last().unwrap().comment.1.clone();
// (*statements.last_mut().unwrap()).comment.1 = None;
// }
statements.push(parsed)
}
None => break, //+ no more tokens, probably end of file
}
//+ can't find any more tokens that are valid syntax, checking for line separator
match tokens.next(true) {
Some(Token::StatementSeparator) => {}
Some(a) => {
return Err(SyntaxError::ExpectedErr {
expected: STATEMENT_SEPARATOR_DESC.to_string(),
found: format!("{}: \"{}\"", a.typ(), tokens.slice()),
pos: tokens.position(),
file: notes.file,
})
}
None => break,
}
}
Ok((statements, notes))
}
fn parse_cmp_stmt(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<Vec<ast::Statement>, SyntaxError> {
let mut statements = Vec::<ast::Statement>::new();
let opening_bracket = tokens.position();
loop {
match tokens.next(false) {
Some(Token::ClosingCurlyBracket) => break,
Some(_) => {
tokens.previous_no_ignore(false);
let parsed = parse_statement(tokens, notes)?;
// if parsed.comment.0 == None && !statements.is_empty() {
// parsed.comment.0 = statements.last().unwrap().comment.1.clone();
// (*statements.last_mut().unwrap()).comment.1 = None;
// }
statements.push(parsed) // add to big statement list
//println!("statement done");
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching '}' for this '{'".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
}
match tokens.next(true) {
Some(Token::StatementSeparator) => {}
Some(Token::ClosingCurlyBracket) => break,
a => expected!(STATEMENT_SEPARATOR_DESC.to_string(), tokens, notes, a),
}
}
//tokens.next(false, false);
Ok(statements)
}
pub fn parse_statement(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<ast::Statement, SyntaxError> {
//let preceding_comment = check_for_comment(tokens);
//let mut comment_after = None;
let first = tokens.next(false);
let (start_pos, _) = tokens.position();
let mut arrow = false;
let body = match first {
// ooh what type of token is it
Some(Token::Arrow) => {
//parse async statement
if tokens.next(false) == Some(Token::Arrow) {
//double arrow (throw error)
return Err(SyntaxError::UnexpectedErr {
found: "double arrow (-> ->)".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
tokens.previous();
let rest_of_statement = parse_statement(tokens, notes)?; // recursion moment
arrow = true;
rest_of_statement.body
/* Summary:
check for double arrows, if it is then throw error because you cant do that
enable the async flag and parse everything else
*/
}
Some(Token::Return) => {
//parse return statement
match tokens.next(true) {
//do we actually return something?
Some(Token::StatementSeparator) | Some(Token::ClosingCurlyBracket) => {
// we dont return anything
tokens.previous();
ast::StatementBody::Return(None)
}
_ => {
// we are returning something, how fun
tokens.previous();
let expr = parse_expr(tokens, notes, true, true)?; //parse whatever we are returning
// comment_after =
// if let Some(comment) = expr.values.last().unwrap().comment.1.clone() {
// (*expr.values.last_mut().unwrap()).comment.1 = None;
// Some(comment)
// } else {
// None
// };
ast::StatementBody::Return(Some(expr))
}
}
/* Summary:
check if we are returning something
if not, just output an empty return syntax tree
if we are, parse the return expression and return a syntax tree with it
*/
}
Some(Token::Break) => ast::StatementBody::Break, // its just break
Some(Token::Continue) => ast::StatementBody::Continue,
Some(Token::If) => {
//parse if statement
// println!("if statement");
let condition = parse_expr(tokens, notes, true, false)?; // parse the condition part
match tokens.next(false) {
// check for a { character
Some(Token::OpenCurlyBracket) => (),
a => {
// no { this is very bad
expected!("'{'".to_string(), tokens, notes, a)
}
}
let if_body = parse_cmp_stmt(tokens, notes)?; // parse whatever is inside if statement
let else_body = match tokens.next(false) {
// is there an else?
Some(Token::Else) => match tokens.next(false) {
// there is an else, check for else if
Some(Token::OpenCurlyBracket) => {
// no else if, just else
Some(parse_cmp_stmt(tokens, notes)?) // parse the else
}
Some(Token::If) => {
// there is an else if
tokens.previous();
Some(vec![parse_statement(tokens, notes)?]) // parse the else if
}
a => {
// found something other than { or if
expected!("'{' or 'if'".to_string(), tokens, notes, a)
}
},
_ => {
// no else at all
tokens.previous();
None
}
};
let if_statement = ast::If {
condition,
if_body,
else_body,
};
ast::StatementBody::If(if_statement)
/* Summary:
parse the condition
check if the first "if" statement has a {
check for any "else" or "else if" statements
return a syntax tree for it
*/
}
Some(Token::While) => {
let condition = parse_expr(tokens, notes, true, false)?;
match tokens.next(false) {
// check for brace
Some(Token::OpenCurlyBracket) => {}
a => {
// no brace
expected!("'{'".to_string(), tokens, notes, a)
}
};
let body = parse_cmp_stmt(tokens, notes)?; // parse whats in the while loop
ast::StatementBody::While(ast::While { condition, body })
}
Some(Token::For) => {
//parse for statement
let symbol = match tokens.next(false) {
// check for variable
Some(Token::Symbol) => tokens.slice(),
Some(a) => {
// invalid variable name
return Err(SyntaxError::ExpectedErr {
expected: "iterator variable name".to_string(),
found: format!("{}: \"{}\"", a.typ(), tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
});
}
None => {
// literally no variable, why
return Err(SyntaxError::ExpectedErr {
expected: "iterator variable name".to_string(),
found: "None".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
};
match tokens.next(false) {
// check for an in
Some(Token::In) => {}
a => {
// didnt find an in
expected!("keyword 'in'".to_string(), tokens, notes, a)
}
};
let array = parse_expr(tokens, notes, true, false)?; // parse the array (or range)
match tokens.next(false) {
// check for brace
Some(Token::OpenCurlyBracket) => {}
a => {
// no brace
expected!("'{'".to_string(), tokens, notes, a)
}
};
let body = parse_cmp_stmt(tokens, notes)?; // parse whats in the for loop
ast::StatementBody::For(ast::For {
symbol: Intern::new(symbol),
array,
body,
})
/* Summary:
check for an iterator variable
parse range/list
parse code block
return syntax tree
*/
}
Some(Token::ErrorStatement) => {
let expr = parse_expr(tokens, notes, true, true)?;
// comment_after = if let Some(comment) = expr.values.last().unwrap().comment.1.clone() {
// (*expr.values.last_mut().unwrap()).comment.1 = None;
// Some(comment)
// } else {
// None
// };
ast::StatementBody::Error(ast::Error { message: expr })
//i dont think a summary is needed for this
}
Some(Token::Type) => {
// defining a new type
match tokens.next(false) {
// all types start with @, throw error if it doesn't
Some(Token::At) => (),
a => expected!("'@'".to_string(), tokens, notes, a),
};
match tokens.next(false) {
// check if type name is valid
Some(Token::Symbol) => ast::StatementBody::TypeDef(tokens.slice()),
a => expected!("type name".to_string(), tokens, notes, a),
}
/*Summary:
check for @ symbol at the start
check if type name is actually valid
return typedef syntax tree
*/
}
Some(Token::Implement) => {
//parse impl statement
let symbol = parse_variable(tokens, notes, true)?;
/*
You might be asking yourself here,
"why are we parsing it as a variable and not a type?"
Well the answer to that is simply that the developer thought
that some people might not like the typing system and would
want to use a variable instead.
*/
match tokens.next(false) {
// check if it has the brace
Some(Token::OpenCurlyBracket) => ast::StatementBody::Impl(ast::Implementation {
symbol,
members: parse_dict(tokens, notes)?, // impl block is basically a dict
}),
a => {
// no brace
expected!("'{'".to_string(), tokens, notes, a)
}
}
// honestly this shouldn't deserve a summary its so basic
}
Some(Token::Extract) => {
let expr = parse_expr(tokens, notes, true, true)?;
// its an expression because dicts can also be extracted alongside imported modules
ast::StatementBody::Extract(expr)
// too basic to have a summary,
}
Some(_) => {
//either expression, call or definition, FIGURE OUT
//parse it
//expression or call
tokens.previous_no_ignore(false);
let expr = parse_expr(tokens, notes, true, true)?;
if tokens.next(false) == Some(Token::Exclamation) {
//call
ast::StatementBody::Call(ast::Call {
function: expr.values[0].clone(),
})
} else {
// expression statement
// println!("found expr");
tokens.previous_no_ignore(false);
// comment_after = if let Some(comment) = expr.values.last().unwrap().comment.1.clone()
// {
// (*expr.values.last_mut().unwrap()).comment.1 = None;
// Some(comment)
// } else {
// None
// };
/*println!(
"current token after stmt post comment: {}: ",
tokens.slice()
);*/
ast::StatementBody::Expr(expr)
}
}
None => {
//end of input
unimplemented!()
}
};
let (_, end_pos) = tokens.position();
// if comment_after == None {
// comment_after = check_for_comment(tokens);
// }
/*println!(
"current token after stmt post comment: {}: ",
tokens.slice()
);*/
Ok(ast::Statement {
// we are returning a statement pog
body,
arrow,
pos: (start_pos, end_pos),
// comment: (preceding_comment, comment_after),
})
}
fn operator_precedence(op: &ast::Operator) -> u8 {
use ast::Operator::*;
match op {
As => 10,
Power => 9,
Either => 8,
Modulo => 7,
Star => 7,
Slash => 7,
IntDividedBy => 7,
Plus => 6,
Minus => 6,
Range => 5,
MoreOrEqual => 4,
LessOrEqual => 4,
More => 3,
Less => 3,
Equal => 2,
Has => 2,
NotEqual => 2,
Or => 1,
And => 1,
Assign => 0,
Add => 0,
Subtract => 0,
Multiply => 0,
Divide => 0,
IntDivide => 0,
Exponate => 0,
Modulate => 0,
Swap => 0,
}
}
fn fix_precedence(mut expr: ast::Expression) -> ast::Expression {
for val in &mut expr.values {
let body = &mut val.value.body;
if let ast::ValueBody::Expression(e) = body {
*e = fix_precedence(e.clone());
}
}
if expr.operators.len() <= 1 {
expr
} else {
let mut lowest = 10;
for op in &expr.operators {
let p = operator_precedence(op);
if p < lowest {
lowest = p
};
}
let mut new_expr = ast::Expression {
operators: Vec::new(),
values: Vec::new(),
};
for (i, op) in expr.operators.iter().enumerate().rev() {
if operator_precedence(op) == lowest {
new_expr.operators.push(*op);
new_expr.values.push(if i == expr.operators.len() - 1 {
expr.values.last().unwrap().clone()
} else {
// expr.operators[(i + 1)..].to_vec(),
// values: expr.values[(i + 1)..]
fix_precedence(ast::Expression {
operators: expr.operators[(i + 1)..].to_vec(),
values: expr.values[(i + 1)..].to_vec(),
})
.to_variable()
});
new_expr.values.push(if i == 0 {
expr.values[0].clone()
} else {
fix_precedence(ast::Expression {
operators: expr.operators[..i].to_vec(),
values: expr.values[..(i + 1)].to_vec(),
})
.to_variable()
});
break;
}
}
new_expr.operators.reverse();
new_expr.values.reverse();
new_expr
}
}
fn parse_cases(tokens: &mut Tokens, notes: &mut ParseNotes) -> Result<Vec<ast::Case>, SyntaxError> {
let mut default_enabled = false;
//let mut do_we_have_next = true;
let mut cases = Vec::<ast::Case>::new();
loop {
match tokens.next(false) {
Some(Token::ClosingCurlyBracket) => break,
Some(Token::Else) => {
// the default
if default_enabled {
return Err(SyntaxError::SyntaxError {
message: "Cannot have 2 else cases".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
default_enabled = true;
/* under normal circumstances we would add another value to check_types,
but since an else case never type checks and is always at the end,
there is no need to. */
match tokens.next(false) {
Some(Token::Colon) => {
let expr = parse_expr(tokens, notes, false, true)?; // parse whats after the :
cases.push(ast::Case {
typ: ast::CaseType::Default,
body: expr,
});
if tokens.next(false) != Some(Token::Comma) {
// for error formatting
tokens.previous();
}
}
a => expected!("':'".to_string(), tokens, notes, a),
}
}
Some(Token::Case) => {
if default_enabled {
return Err(SyntaxError::SyntaxError {
message: "cannot have more cases after 'else' field".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
let val = parse_expr(tokens, notes, false, true)?;
match tokens.next(false) {
Some(Token::Colon) => {
let expr = parse_expr(tokens, notes, false, true)?; // parse whats after the :
cases.push(ast::Case {
typ: ast::CaseType::Value(val),
body: expr,
});
if tokens.next(false) != Some(Token::Comma) {
// for error formatting
tokens.previous_no_ignore(false);
}
}
a => expected!("':'".to_string(), tokens, notes, a),
}
}
_ => {
tokens.previous();
let pat = parse_expr(tokens, notes, false, true)?;
if default_enabled {
return Err(SyntaxError::SyntaxError {
message: "cannot have more cases after 'else' field".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
match tokens.next(false) {
Some(Token::Colon) => {
let expr = parse_expr(tokens, notes, false, true)?; // parse whats after the :
cases.push(ast::Case {
typ: ast::CaseType::Pattern(pat),
body: expr,
});
if tokens.next(false) != Some(Token::Comma) {
// for error formatting
tokens.previous_no_ignore(false);
}
}
a => expected!("':'".to_string(), tokens, notes, a),
}
}
}
}
Ok(cases)
}
fn parse_expr(
tokens: &mut Tokens,
notes: &mut ParseNotes,
allow_mut_op: bool,
allow_macro_def: bool,
) -> Result<ast::Expression, SyntaxError> {
// Alright lets parse an expression
// NOTE: this parses whatever is *after* the current token
let mut values = Vec::<ast::Variable>::new();
let mut operators = Vec::<ast::Operator>::new();
tokens.next(false);
let (start_pos, _) = tokens.position();
tokens.previous_no_ignore(false);
values.push(parse_variable(tokens, notes, allow_macro_def)?);
// all expressions begin with a variable
while let Some(t) = tokens.next(false) {
// keep looking for operators and values
if let Some(o) = parse_operator(&t) {
// check if new operator
let op = if allow_mut_op {
o
} else {
match o {
ast::Operator::Assign
| ast::Operator::Add
| ast::Operator::Subtract
| ast::Operator::Multiply
| ast::Operator::Divide => break,
_ => o,
}
};
operators.push(op);
values.push(parse_variable(tokens, notes, allow_macro_def)?);
} else {
break;
}
}
tokens.previous_no_ignore(false);
let express = fix_precedence(ast::Expression { values, operators }); //pemdas and stuff
match tokens.next(true) {
Some(Token::If) => {
// oooh ternaries
// remove any = from the ternary and place into a separate stack
let mut old_values = express.values.clone();
let mut old_operators = express.operators;
let mut tern_values = Vec::<ast::Variable>::new();
let mut tern_operators = Vec::<ast::Operator>::new();
match old_values.pop() {
Some(v) => tern_values.push(v),
_ => {
return Err(SyntaxError::SyntaxError {
message: "expected expression before 'if'".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
})
}
};
// iterate though the operators until we get one like =
while !old_operators.is_empty() {
if operator_precedence(old_operators.last().unwrap()) < 1 {
break;
}
match (old_values.pop(), old_operators.pop()) {
// pop off of the original expression and put onto ternary stack
(Some(v), Some(o)) => {
tern_values.push(v);
tern_operators.push(o);
}
(_, _) => unreachable!(),
}
}
let conditional = parse_expr(tokens, notes, false, allow_macro_def)?;
let do_else = match tokens.next(false) {
// every ternary needs an else
Some(Token::Else) => parse_expr(tokens, notes, false, allow_macro_def),
_ => {
return Err(SyntaxError::ExpectedErr {
expected: "else".to_string(),
found: tokens.slice(),
pos: tokens.position(),
file: notes.file.clone(),
})
}
}?;
tern_values.reverse();
tern_operators.reverse();
let (end_pos, _) = tokens.position();
// SPWN syntax structures can get pretty messy with variables, valuebodies,
// valueliterals, expressions, etc.
let tern = ast::Ternary {
condition: conditional,
if_expr: ast::Expression {
values: tern_values,
operators: tern_operators,
},
else_expr: do_else,
};
let ternval_literal = ast::ValueLiteral {
body: ast::ValueBody::Ternary(tern),
};
//println!("operators: {:?} values: {:?}", old_operators, old_values);
old_values.push(ast::Variable {
operator: None,
value: ternval_literal,
path: vec![],
pos: (start_pos, end_pos),
//comment: (None, None),
tag: ast::Attribute::new(),
});
Ok(ast::Expression {
values: old_values,
operators: old_operators,
})
}
_ => {
tokens.previous_no_ignore(false);
Ok(express)
}
}
}
fn parse_operator(token: &Token) -> Option<ast::Operator> {
// its just a giant match statement
match token {
Token::DotDot => Some(ast::Operator::Range),
Token::Or => Some(ast::Operator::Or),
Token::And => Some(ast::Operator::And),
Token::Equal => Some(ast::Operator::Equal),
Token::NotEqual => Some(ast::Operator::NotEqual),
Token::MoreOrEqual => Some(ast::Operator::MoreOrEqual),
Token::LessOrEqual => Some(ast::Operator::LessOrEqual),
Token::LessThan => Some(ast::Operator::Less),
Token::MoreThan => Some(ast::Operator::More),
Token::Star => Some(ast::Operator::Star),
Token::Power | Token::DoubleStar => Some(ast::Operator::Power),
Token::Plus => Some(ast::Operator::Plus),
Token::Minus => Some(ast::Operator::Minus),
Token::Slash => Some(ast::Operator::Slash),
Token::IntDividedBy => Some(ast::Operator::IntDividedBy),
Token::Modulo => Some(ast::Operator::Modulo),
Token::Either => Some(ast::Operator::Either),
Token::Assign => Some(ast::Operator::Assign),
Token::Add => Some(ast::Operator::Add),
Token::Subtract => Some(ast::Operator::Subtract),
Token::Multiply => Some(ast::Operator::Multiply),
Token::Divide => Some(ast::Operator::Divide),
Token::IntDivide => Some(ast::Operator::IntDivide),
Token::Exponate => Some(ast::Operator::Exponate),
Token::Modulate => Some(ast::Operator::Modulate),
Token::Swap => Some(ast::Operator::Swap),
Token::Has => Some(ast::Operator::Has),
Token::As => Some(ast::Operator::As),
_ => None,
}
}
fn parse_dict(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<Vec<ast::DictDef>, SyntaxError> {
let mut defs = Vec::<ast::DictDef>::new();
loop {
match tokens.next(false) {
Some(Token::Symbol) | Some(Token::Type) => {
let symbol = tokens.slice();
is_valid_symbol(&symbol, tokens, notes)?;
let symbol = Intern::new(symbol);
match tokens.next(false) {
Some(Token::Colon) => {
let expr = parse_expr(tokens, notes, true, true)?;
defs.push(ast::DictDef::Def((symbol, expr)));
}
Some(Token::Comma) => {
if symbol.as_ref() == "type" {
return Err(SyntaxError::ExpectedErr {
expected: "':'".to_string(),
found: String::from("comma (',')"),
pos: tokens.position(),
file: notes.file.clone(),
});
}
tokens.previous();
defs.push(ast::DictDef::Def((
symbol,
ast::ValueBody::Symbol(symbol)
.to_variable(tokens.position())
.to_expression(),
)));
}
Some(Token::ClosingCurlyBracket) => {
if symbol.as_ref() == "type" {
return Err(SyntaxError::ExpectedErr {
expected: "':'".to_string(),
found: String::from("}"),
pos: tokens.position(),
file: notes.file.clone(),
});
}
defs.push(ast::DictDef::Def((
symbol,
ast::ValueBody::Symbol(symbol)
.to_variable(tokens.position())
.to_expression(),
)));
//tokens.previous();
break;
}
a => expected!("':'".to_string(), tokens, notes, a),
}
}
Some(Token::DotDot) => {
let expr = parse_expr(tokens, notes, true, true)?;
defs.push(ast::DictDef::Extract(expr))
}
Some(Token::ClosingCurlyBracket) => break,
a => expected!(
"member definition, '..' or '}'".to_string(),
tokens,
notes,
a
),
};
let next = tokens.next(false);
if next == Some(Token::ClosingCurlyBracket) {
break;
}
if next != Some(Token::Comma) {
return Err(SyntaxError::ExpectedErr {
expected: "comma (',')".to_string(),
found: format!("{:?}: {:?}", next, tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
});
}
}
Ok(defs)
}
fn parse_object(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<Vec<(ast::Expression, ast::Expression)>, SyntaxError> {
let mut defs = Vec::<(ast::Expression, ast::Expression)>::new();
match tokens.next(false) {
Some(Token::OpenCurlyBracket) => (),
a => expected!("'{'".to_string(), tokens, notes, a),
}
loop {
if tokens.next(false) == Some(Token::ClosingCurlyBracket) {
break;
} else {
tokens.previous();
}
let key = parse_expr(tokens, notes, true, true)?;
match tokens.next(false) {
Some(Token::Colon) => (),
a => expected!("':'".to_string(), tokens, notes, a),
}
let val = parse_expr(tokens, notes, true, true)?;
defs.push((key, val));
let next = tokens.next(false);
if next == Some(Token::ClosingCurlyBracket) {
break;
}
if next != Some(Token::Comma) {
return Err(SyntaxError::ExpectedErr {
expected: "comma (',')".to_string(),
found: format!("{:?}: {:?}", next, tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
});
}
}
Ok(defs)
}
fn parse_args(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<Vec<ast::Argument>, SyntaxError> {
let mut args = Vec::<ast::Argument>::new();
let opening_bracket = tokens.position();
loop {
if tokens.next(false) == Some(Token::ClosingBracket) {
break;
};
args.push(match tokens.next(false) {
Some(Token::Assign) => {
// println!("assign ");
match tokens.previous() {
Some(Token::Symbol) => (),
Some(a) => {
return Err(SyntaxError::ExpectedErr {
expected: "Argument name".to_string(),
found: format!("{}: \"{}\"", a.typ(), tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
})
}
None => unreachable!(),
};
let start = tokens.position().0;
let symbol = Some(Intern::new(tokens.slice()));
tokens.next(false);
let value = parse_expr(tokens, notes, true, true)?;
let end = tokens.position().1;
//tokens.previous();
ast::Argument {
symbol,
value,
pos: (start, end),
}
}
Some(_) => {
tokens.previous();
tokens.previous();
// println!("arg with no val");
let value = parse_expr(tokens, notes, true, true)?;
ast::Argument {
symbol: None,
pos: value.get_pos(),
value,
}
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching ')' for this '('".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
});
match tokens.next(false) {
Some(Token::Comma) => (),
Some(Token::ClosingBracket) => {
break;
}
Some(a) => {
return Err(SyntaxError::ExpectedErr {
expected: "comma (',') or ')'".to_string(),
found: format!("{}: \"{}\"", a.typ(), tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
})
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching ')' for this '('".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
}
}
//tokens.previous();
Ok(args)
}
fn parse_arg_def(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<Vec<ast::ArgDef>, SyntaxError> {
let mut args = Vec::<ast::ArgDef>::new();
let opening_bracket = tokens.position();
loop {
let properties = check_for_tag(tokens, notes)?;
let mut arg_tok = tokens.next(false);
let as_ref = match arg_tok {
Some(Token::ClosingBracket) => break,
Some(Token::Ampersand) => {
arg_tok = tokens.next(false);
true
}
_ => false,
};
let symbol = Intern::new(tokens.slice());
let start = tokens.position().0;
args.push(match tokens.next(false) {
Some(Token::Assign) => {
if arg_tok == Some(Token::SelfVal) {
return Err(SyntaxError::SyntaxError {
message: "\"self\" argument cannot have a default value".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
let value = Some(parse_expr(tokens, notes, true, true)?);
let end = tokens.position().1;
//xtokens.previous();
(symbol, value, properties, None, (start, end), as_ref)
}
Some(Token::Colon) => {
if arg_tok == Some(Token::SelfVal) {
return Err(SyntaxError::SyntaxError {
message: "\"self\" argument cannot have explicit type".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
let type_value = Some(parse_expr(tokens, notes, false, true)?);
//tokens.previous();
match tokens.next(false) {
Some(Token::Assign) => {
let value = Some(parse_expr(tokens, notes, true, true)?);
let end = tokens.position().1;
//tokens.previous();
(symbol, value, properties, type_value, (start, end), as_ref)
}
Some(_) => {
tokens.previous();
let end = tokens.position().1;
(symbol, None, properties, type_value, (start, end), as_ref)
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching ')' for this '('".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
}
}
Some(_) => {
tokens.previous();
if arg_tok == Some(Token::SelfVal) && !args.is_empty() {
return Err(SyntaxError::SyntaxError {
message: "\"self\" argument must be the first argument".to_string(),
pos: tokens.position(),
file: notes.file.clone(),
});
}
match arg_tok {
Some(Token::Symbol) | Some(Token::SelfVal) => (
symbol,
None,
properties,
None,
(start, tokens.position().1),
as_ref,
),
_ => expected!("symbol".to_string(), tokens, notes, arg_tok),
}
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching ')' for this '('".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
});
match tokens.next(false) {
Some(Token::Comma) => (),
Some(Token::ClosingBracket) => break,
Some(a) => {
return Err(SyntaxError::ExpectedErr {
expected: "comma (',') or ')'".to_string(),
found: format!("{}: \"{}\"", a.typ(), tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
})
}
None => {
return Err(SyntaxError::SyntaxError {
message: "Couldn't find matching ')' for this '('".to_string(),
pos: opening_bracket,
file: notes.file.clone(),
})
}
}
}
//tokens.previous();
Ok(args)
}
fn check_for_tag(
tokens: &mut Tokens,
notes: &mut ParseNotes,
) -> Result<ast::Attribute, SyntaxError> {
let first = tokens.next(false);
match first {
Some(Token::Hash) => {
//parse tag
match tokens.next(false) {
Some(Token::OpenSquareBracket) => (),
a => expected!("'['".to_string(), tokens, notes, a),
};
let mut contents = ast::Attribute::new();
loop {
match tokens.next(false) {
Some(Token::ClosingSquareBracket) => break,
Some(Token::Symbol) => {
let name = tokens.slice();
let args = match tokens.next(false) {
Some(Token::OpenBracket) => parse_args(tokens, notes)?,
Some(Token::Comma) => Vec::new(),
Some(Token::ClosingSquareBracket) => {
contents.tags.push((name, Vec::new()));
break;
}
a => expected!(
"either '(', ']' or comma (',')".to_string(),
tokens,
notes,
a
),
};
contents.tags.push((name, args));
}
a => expected!("either Symbol or ']'".to_string(), tokens, notes, a),
};
}
Ok(contents)
}
_ => {
tokens.previous_no_ignore(false);
Ok(ast::Attribute::new())
}
}
}
pub fn str_content(
mut inp: String,
tokens: &Tokens,
notes: &ParseNotes,
) -> Result<(String, Option<StringFlags>), SyntaxError> {
let first = inp.remove(0);
inp.remove(inp.len() - 1);
let mut out = (String::new(), None);
let mut chars = inp.chars();
match first {
'\'' | '"' => {
while let Some(c) = chars.next() {
out.0.push(if c == '\\' {
match chars.next() {
Some('n') => '\n',
Some('r') => '\r',
Some('t') => '\t',
Some('"') => '\"',
Some('\'') => '\'',
Some('\\') => '\\',
Some(a) => {
return Err(SyntaxError::SyntaxError {
message: format!("Invalid escape: \\{}", a),
pos: tokens.position(),
file: notes.file.clone(),
})
}
None => unreachable!(),
}
} else {
c
});
}
}
'r' => {
// remove "
chars.next();
out.1 = StringFlags::Raw.into();
for c in chars {
out.0.push(c);
}
}
_ => {
return Err(SyntaxError::SyntaxError {
file: notes.file.to_owned(),
pos: tokens.position(),
message: format!("Invalid string flag: {}", first),
})
}
}
Ok(out)
}
fn check_if_slice(mut tokens: Tokens, notes: &mut ParseNotes) -> Result<bool, SyntaxError> {
loop {
match tokens.next(false) {
Some(Token::Colon) => {
return Ok(true);
}
Some(Token::ClosingSquareBracket) => {
return Ok(false);
}
_ => {
tokens.previous_no_ignore(false);
parse_expr(&mut tokens, notes, true, true)?;
}
};
}
}
fn parse_macro(
tokens: &mut Tokens,
notes: &mut ParseNotes,
properties: ast::Attribute,
decorator: Option<Box<ast::Variable>>,
) -> Result<ast::ValueBody, SyntaxError> {
let parse_macro_def =
|tokens: &mut Tokens, notes: &mut ParseNotes| -> Result<ast::ValueBody, SyntaxError> {
let arg_start = tokens.position().0;
let args = parse_arg_def(tokens, notes)?;
let arg_end = tokens.position().1;
let body = match tokens.next(false) {
Some(Token::OpenCurlyBracket) => parse_cmp_stmt(tokens, notes)?,
Some(Token::ThickArrow) => {
let start = tokens.position().0;
let expr = parse_expr(tokens, notes, true, true)?;
let end = tokens.position().1;
vec![ast::Statement {
body: ast::StatementBody::Return(Some(expr)),
arrow: false,
//comment: (None, None),
pos: (start, end),
}]
}
a => expected!("'{'".to_string(), tokens, notes, a),
};
let m_value = ast::ValueBody::Macro(ast::Macro {
args,
body: ast::CompoundStatement { statements: body },
properties: properties.clone(),
arg_pos: (arg_start, arg_end),
});
if let Some(d) = decorator {
let mut unwrapped_deco = (*d).clone();
let m_var = ast::Variable {
value: ast::ValueLiteral::new(m_value),
path: Vec::new(),
operator: None,
pos: (arg_start, arg_end),
tag: properties.clone(),
};
let mut new_path = vec![ast::Argument {
symbol: None,
value: m_var.to_expression(),
pos: (arg_start, arg_end),
}];
if let Some(p_) = unwrapped_deco.path.pop() {
match p_ {
ast::Path::Call(v) => {
new_path.extend(v);
}
_ => {
unwrapped_deco.path.push(p_);
}
}
}
unwrapped_deco.path.push(ast::Path::Call(new_path));
Ok(ast::ValueBody::Expression(unwrapped_deco.to_expression()))
} else {
Ok(m_value)
}
};
let mut test_tokens = tokens.clone();
return match parse_expr(&mut test_tokens, notes, true, true) {
Ok(expr) => {
//macro def
Ok(match test_tokens.next(false) {
Some(Token::ClosingBracket) => match test_tokens.next(false) {
Some(Token::OpenCurlyBracket) => parse_macro_def(tokens, notes)?,
Some(Token::ThickArrow) => parse_macro_def(tokens, notes)?,
_ => {
test_tokens.previous();
(*tokens) = test_tokens;
ast::ValueBody::Expression(expr)
}
},
Some(Token::Comma) => parse_macro_def(tokens, notes)?,
Some(Token::Colon) => parse_macro_def(tokens, notes)?,
a => {
return Err(SyntaxError::ExpectedErr {
expected: "')', ':' or comma (',')".to_string(),
found: format!("{:?}: {:?}", a, test_tokens.slice()),
pos: tokens.position(),
file: notes.file.clone(),
})
}
})
}
Err(_) => match parse_macro_def(tokens, notes) {
Ok(mac) => Ok(mac),
Err(e) => return Err(e),
},
};
}
fn parse_variable(
tokens: &mut Tokens,
notes: &mut ParseNotes,
allow_macro_def: bool,
//check_for_comments: bool,
) -> Result<ast::Variable, SyntaxError> {
// for the vars and stuff that isnt operators
// let preceding_comment = if check_for_comments {
// check_for_comment(tokens)
// } else {
// None
// };
let properties = check_for_tag(tokens, notes)?;
let mut first_token = tokens.next(false);
let (start_pos, _) = tokens.position();
let operator = match first_token {
// does it start with an op? (e.g -3, let i)
Some(Token::Minus) => {
first_token = tokens.next(false);
Some(ast::UnaryOperator::Minus)
}
Some(Token::Exclamation) => {
if tokens.next(true) == Some(Token::OpenCurlyBracket) {
tokens.previous_no_ignore(true);
None
} else {
tokens.previous_no_ignore(true);
first_token = tokens.next(false);
Some(ast::UnaryOperator::Not)
}
}
Some(Token::DotDot) => {
first_token = tokens.next(false);
Some(ast::UnaryOperator::Range)
}
Some(Token::Let) => {
first_token = tokens.next(false);
Some(ast::UnaryOperator::Let)
}
Some(Token::Increment) => {
first_token = tokens.next(false);
Some(ast::UnaryOperator::Increment)
}
Some(Token::Decrement) => {
first_token = tokens.next(false);
Some(ast::UnaryOperator::Decrement)
}
_ => None,
};
let value = match first_token {
// what kind of variable is it?
Some(Token::Number) => ast::ValueBody::Number(match tokens.slice().parse() {
Ok(n) => n, // its a valid number
Err(err) => {
return Err(SyntaxError::SyntaxError {
message: format!("Error when parsing number: {}", err),
pos: tokens.position(),
file: notes.file.clone(),
});
}
}),
Some(Token::StringLiteral) => {
// is a string
let (content, flag) = str_content(tokens.slice(), tokens, notes)?;
let inner = StrInner {
inner: content,
flags: flag,
};
ast::ValueBody::Str(inner)
}
Some(Token::Id) => {
let mut text = tokens.slice();
let class_name = match text.pop().unwrap() {
'g' => ast::IdClass::Group,
'c' => ast::IdClass::Color,
'i' => ast::IdClass::Item,
'b' => ast::IdClass::Block,
_ => unreachable!(),
};
let (unspecified, number) = match text.as_ref() {
"?" => (true, 0),
_ => (
false,
match text.parse() {
Ok(n) => n,
Err(err) => {
return Err(SyntaxError::SyntaxError {
message: format!("Error when parsing number: {}", err),
pos: tokens.position(),
file: notes.file.clone(),
});
}
},
),
};
ast::ValueBody::Id(ast::Id {
class_name,
unspecified,
number,
})
}
Some(Token::True) => ast::ValueBody::Bool(true),
Some(Token::False) => ast::ValueBody::Bool(false),
Some(Token::Null) => ast::ValueBody::Null,
Some(Token::SelfVal) => ast::ValueBody::SelfVal,
Some(Token::Symbol) => {
let symbol = Intern::new(tokens.slice());
match tokens.next(false) {
Some(Token::ThickArrow) => {
// Woo macro shorthand
let arg = if symbol.as_ref() != "_" {
is_valid_symbol(&symbol, tokens, notes)?;
vec![(
symbol,
None,
properties.clone(),
None,
tokens.position(),
false,
)]
} else {
Vec::new()
};
let start = tokens.position();
let expr = parse_expr(tokens, notes, true, true)?;
let end = tokens.position().1;
let macro_body = vec![ast::Statement {
body: ast::StatementBody::Return(Some(expr)),
arrow: false,
//comment: (None, None),
pos: (start.0, end),
}];
ast::ValueBody::Macro(ast::Macro {
args: arg,
body: ast::CompoundStatement {
statements: macro_body,
},
arg_pos: start,
properties: properties.clone(),
})
}
_ => {
is_valid_symbol(&symbol, tokens, notes)?;
tokens.previous_no_ignore(false);
ast::ValueBody::Symbol(symbol)
}
}
}
Some(Token::OpenSquareBracket) => {
let mut potential_macro: Option<ast::ValueBody> = None;
if let Some(Token::OpenSquareBracket) = tokens.next(false) {
let mut test_tokens = tokens.clone();
if let Ok(mut v) = parse_variable(&mut test_tokens, notes, false) {
if let Some(Token::ClosingSquareBracket) = test_tokens.next(false) {
if let Some(Token::ClosingSquareBracket) = test_tokens.next(false) {
match test_tokens.next(false) {
Some(Token::OpenBracket) => {
// its a decorator on a macro
*tokens = test_tokens;
potential_macro = Some(parse_macro(
tokens,
notes,
properties.clone(),
Some(Box::new(v)),
)?);
}
Some(Token::Exclamation) => {
// its a decorator on a trigger function
match test_tokens.next(true) {
// fuck this, i ain't allowing !;{ }
Some(Token::OpenCurlyBracket) => (),
a => expected!("{".to_string(), tokens, notes, a),
}
*tokens = test_tokens;
let trig = ast::ValueBody::CmpStmt(ast::CompoundStatement {
statements: parse_cmp_stmt(tokens, notes)?,
});
let t_var = ast::Variable {
value: ast::ValueLiteral::new(trig),
path: Vec::new(),
operator: None,
pos: (0, 0),
tag: properties.clone(),
};
let mut new_path = vec![ast::Argument {
symbol: None,
value: t_var.to_expression(),
pos: (0, 0),
}];
if let Some(p_) = v.path.pop() {
match p_ {
ast::Path::Call(vv) => {
new_path.extend(vv);
}
_ => {
v.path.push(p_);
}
}
}
v.path.push(ast::Path::Call(new_path));
potential_macro =
Some(ast::ValueBody::Expression(v.to_expression()))
}
_ => (),
}
}
}
}
}
match potential_macro {
Some(x) => x,
None => {
tokens.previous_no_ignore(false);
//Array
let mut arr = Vec::new();
let mut potential_listcomp: Option<ast::Comprehension> = None;
if tokens.next(false) != Some(Token::ClosingSquareBracket) {
tokens.previous();
loop {
let item = parse_expr(tokens, notes, true, true)?;
match tokens.next(false) {
Some(Token::For) => {
if !arr.is_empty() {
expected!(
"comma (',') or ']'".to_string(),
tokens,
notes,
Some(Token::For)
);
}
match tokens.next(false) {
Some(Token::Symbol) => {
let tok_name = tokens.slice();
match tokens.next(false) {
Some(Token::In) => (),
a => {
expected!("'in'".to_string(), tokens, notes, a)
}
}
let iter = parse_expr(tokens, notes, true, true)?;
let mut potential_condition: Option<ast::Expression> =
None;
match tokens.next(false) {
Some(Token::ClosingSquareBracket) => (),
Some(Token::Comma) => match tokens.next(false) {
Some(Token::If) => {
potential_condition = Some(parse_expr(
tokens, notes, true, true,
)?);
match tokens.next(false) {
Some(Token::ClosingSquareBracket) => (),
a => expected!(
"]".to_string(),
tokens,
notes,
a
),
}
}
a => expected!(
"if".to_string(),
tokens,
notes,
a
),
},
a => expected!(
"']' or ','".to_string(),
tokens,
notes,
a
),
}
potential_listcomp = Some(ast::Comprehension {
body: item,
symbol: Intern::new(tok_name),
iterator: iter,
condition: potential_condition,
});
break;
}
a => expected!("identifier".to_string(), tokens, notes, a),
}
}
Some(Token::Comma) => {
//accounting for trailing comma
arr.push(item);
if let Some(Token::ClosingSquareBracket) = tokens.next(false) {
break;
} else {
tokens.previous();
}
}
Some(Token::ClosingSquareBracket) => {
arr.push(item);
break;
}
a => expected!(
"comma (','), ']', or 'for'".to_string(),
tokens,
notes,
a
),
}
}
}
if let Some(c) = potential_listcomp {
ast::ValueBody::ListComp(c)
} else {
ast::ValueBody::Array(arr)
}
}
}
}
Some(Token::Import) => {
let mut first = tokens.next(false);
let mut forced = false;
if first == Some(Token::Exclamation) {
forced = true;
first = tokens.next(false);
}
match first {
Some(Token::StringLiteral) => {
let (content, flag) = str_content(tokens.slice(), tokens, notes)?;
if flag.is_some() {
return Err(SyntaxError::UnexpectedErr {
file: notes.file.to_owned(),
pos: tokens.position(),
found: format!("string flag ({:?})", flag),
});
}
ast::ValueBody::Import(ImportType::Script(PathBuf::from(content)), forced)
}
Some(Token::Symbol) => {
ast::ValueBody::Import(ImportType::Lib(tokens.slice()), forced)
}
a => expected!("literal string".to_string(), tokens, notes, a),
}
}
Some(Token::At) => {
let type_name = match tokens.next(false) {
Some(Token::Symbol) => tokens.slice(),
a => expected!("type name".to_string(), tokens, notes, a),
};
ast::ValueBody::TypeIndicator(type_name)
}
Some(Token::Switch) => {
let value = parse_expr(tokens, notes, true, false)?; // what are we switching?
let cases = match tokens.next(false) {
Some(Token::OpenCurlyBracket) => parse_cases(tokens, notes)?, // check for {
a => expected!("'{'".to_string(), tokens, notes, a),
};
//ast::ValueBody::TypeIndicator("number".to_string())
ast::ValueBody::Switch(value, cases)
}
Some(Token::OpenBracket) => {
if allow_macro_def {
parse_macro(tokens, notes, properties.clone(), None)?
} else {
let expr = parse_expr(tokens, notes, true, true)?;
match tokens.next(false) {
Some(Token::ClosingBracket) => ast::ValueBody::Expression(expr),
a => expected!("')'".to_string(), tokens, notes, a),
}
}
}
Some(Token::OpenCurlyBracket) => ast::ValueBody::Dictionary(parse_dict(tokens, notes)?),
Some(Token::Exclamation) => {
// never assume what the next token could be, might lead to issues like !!a} being parsable
let check = tokens.next(false);
if let Some(Token::OpenCurlyBracket) = check {
ast::ValueBody::CmpStmt(ast::CompoundStatement {
statements: parse_cmp_stmt(tokens, notes)?,
})
} else {
expected!("{".to_string(), tokens, notes, check);
}
}
Some(Token::Object) => ast::ValueBody::Obj(ast::ObjectLiteral {
props: parse_object(tokens, notes)?,
mode: ast::ObjectMode::Object,
}),
Some(Token::Trigger) => ast::ValueBody::Obj(ast::ObjectLiteral {
props: parse_object(tokens, notes)?,
mode: ast::ObjectMode::Trigger,
}),
a => expected!("a value".to_string(), tokens, notes, a),
};
let mut path = Vec::<ast::Path>::new();
loop {
match tokens.next(true) {
Some(Token::OpenSquareBracket) => {
if check_if_slice(tokens.clone(), notes)? {
let mut slices = Vec::<ast::Slice>::new();
'main: loop {
let mut curr_slice = ast::Slice {
left: None,
right: None,
step: None,
};
let mut colon_pos = tokens.position();
let mut i: i32 = 0;
loop {
match tokens.next(false) {
Some(Token::Colon) => {
colon_pos = tokens.position();
if i == 1 {
curr_slice.step = curr_slice.right.clone();
curr_slice.right = None;
}
i += 1;
}
Some(Token::ClosingSquareBracket) => {
slices.push(curr_slice);
break 'main;
}
Some(Token::Comma) => {
slices.push(curr_slice);
continue 'main;
}
_ => {
tokens.previous_no_ignore(false);
let result = parse_expr(tokens, notes, true, true)?;
match i {
0 => curr_slice.left = Some(result),
1 | 2 => curr_slice.right = Some(result),
_ => {
return Err(SyntaxError::ExpectedErr {
expected: "]".to_string(),
found: ":".to_string(),
pos: colon_pos,
file: notes.file.clone(),
})
}
};
}
};
}
}
path.push(ast::Path::NSlice(slices));
} else {
let index = parse_expr(tokens, notes, true, true)?;
match tokens.next(false) {
Some(Token::ClosingSquareBracket) => path.push(ast::Path::Index(index)),
a => {
return Err(SyntaxError::ExpectedErr {
expected: "]".to_string(),
found: format!(
"{}: \"{}\"",
match a {
Some(t) => t.typ(),
None => "EOF",
},
tokens.slice()
),
pos: tokens.position(),
file: notes.file.clone(),
})
}
}
}
}
Some(Token::OpenBracket) => path.push(ast::Path::Call(parse_args(tokens, notes)?)),
Some(Token::Period) => match tokens.next(false) {
Some(Token::Symbol) | Some(Token::Type) => {
path.push(ast::Path::Member(Intern::new(tokens.slice())))
}
a => expected!("member name".to_string(), tokens, notes, a),
},
Some(Token::DoubleColon) => match tokens.next(false) {
Some(Token::Symbol) | Some(Token::Type) => {
path.push(ast::Path::Associated(Intern::new(tokens.slice())))
}
Some(Token::OpenCurlyBracket) => {
path.push(ast::Path::Constructor(parse_dict(tokens, notes)?))
}
a => {
return Err(SyntaxError::ExpectedErr {
expected: "associated member name".to_string(),
found: format!(
"{}: \"{}\"",
match a {
Some(t) => t.typ(),
None => "EOF",
},
tokens.slice()
),
pos: tokens.position(),
file: notes.file.clone(),
})
}
},
Some(Token::Increment) => path.push(ast::Path::Increment),
Some(Token::Decrement) => path.push(ast::Path::Decrement),
_ => break,
}
}
tokens.previous_no_ignore(false);
let (_, end_pos) = tokens.position();
// let comment_after = if check_for_comments {
// check_for_comment(tokens)
// } else {
// None
// };
/*if tokens.stack.len() - tokens.index > 0 {
println!("current token after val post comment: {}: ", tokens.slice());
}*/
Ok(ast::Variable {
operator,
value: ast::ValueLiteral { body: value },
pos: (start_pos, end_pos),
//comment: (preceding_comment, comment_after),
path,
tag: properties,
})
}