evscript 0.1.0

An extensible bytecode-based scripting engine
Documentation
use crate::types::*;
use lalrpop_util::ParseError;

use std::collections::VecDeque;
use std::io::Read;
use std::str::FromStr;

grammar;

match {
	"+", "-", "*", "/", "%", "&", "^", "|", "<<", ">>", "!",
	"==", "!=", "<", ">", "<=", ">=", "&&", "||",
	"=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=",
	"...", "$", ":",
	"(", ")", "{", "}", "[", "]",
	r"[a-zA-Z_.][a-zA-Z0-9_.]*" => identifier,
	r#""([^"]|\.)*""# => string,
	r"-?[0-9]+" => number,
	";", ",",
	"env", "use", "include", "def", "alias", "macro", "pool", "const",
	"return", "yield", "typedef", "struct", "ptr",
	"if", "else", "while", "do", "for", "repeat", "loop",
	r"#asm[^#]*#end" => raw_assembly,

	// Skip whitespace and comments
	r"\s*" => { },
	r"//[^\n\r]*[\n\r]*" => { }, // `// comment`
	r"/\*([^*]|\*[^/])*\*/" => { }, // `/* comment */`
}

pub File = { <Root*> }

Root: Root = {
	<start:@L> <environment:Iden> <name:Iden> <end:@L> "{" <contents:Statement*> "}" => {
		Root::Function( name, Function { environment, contents, start, end } )
	},
	"env" <name:Iden> "{" <contents:Statement*> "}" => {
		Root::Environment( name, Environment { contents } )
	},
	raw_assembly =>? {
		let mut bytes = <>.bytes().collect::<VecDeque<u8>>();
		bytes.pop_front();
		bytes.pop_front();
		bytes.pop_front();
		bytes.pop_front();
		bytes.pop_back();
		bytes.pop_back();
		bytes.pop_back();
		bytes.pop_back();
		let mut result = String::new();
		bytes.read_to_string(&mut result)
			.map_err(|err| ParseError::User {
				error: "Invalid UTF8"
			})?;
		Ok(Root::Assembly(result))
	},
	"include" <path:String> ";" => Root::Include(path),
	"typedef" <name:Iden> "=" <t:Iden> ";" => Root::Typedef { name, t },
	"struct" <name:Iden> "{" <contents:Comma<StructMember>> "}" => Root::Struct { name, contents },
}

StructMember: StructMember = {
	<name:Iden> ":" <t:Iden> => StructMember { name, t },
}

Statement: Statement = {
	<start:@L> "def" <name:Iden> "(" <args:Comma<DefinitionParam>> ")" <end:@R> ";" => {
		Statement { t: StatementType::Definition(name, Definition::Def(Def { args, bytecode: 0 })), start, end }
	},
	<start:@L> "alias" <name:Iden> "(" <args:Comma<DefinitionParam>> ")" "=" <target:Iden> "(" <target_args:Comma<AliasParam>> ")" <end:@R> ";" => {
		Statement { t: StatementType::Definition(name, Definition::Alias(Alias { args, target, target_args })), start, end }
	},
	<start:@L> "macro" <name:Iden> "(" <args:Comma<DefinitionParam>> ")" "=" <target:Iden> <end:@R> ";" => {
		Statement { t: StatementType::Definition(name, Definition::Macro(Macro { args, target })), start, end }
	},
	<start:@L> "use" <env:Iden> <end:@R> ";" => Statement { t: StatementType::Use(env), start, end },
	<start:@L> "pool" "=" <expr:Expr> <end:@R> ";" => Statement { t: StatementType::Pool(expr), start, end },
	<start:@L> <expr:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(expr), start, end },
	<start:@L> <t:Iden> <i:Iden> <end:@R> ";" => Statement { t: StatementType::Declaration(t, i), start, end },
	<start:@L> <t:Iden> "ptr" <i:Iden> <end:@R> ";" => Statement { t: StatementType::PointerDeclaration(t, i), start, end },
	<start:@L> <t:Iden> <l:Iden> "=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::DeclareAssign(t, l, r), start, end },
	<start:@L> <t:Iden> "ptr" <l:Iden> "=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::PointerDeclareAssign(t, l, r), start, end },
	Assignment,
	IfContainer,
	<start:@L> "while" <cond:Expr> <end:@R> "{" <contents:Statement*> "}" => Statement { t: StatementType::While(cond, contents), start, end },
	"do" "{" <contents:Statement*> "}" <start:@L> "while" <cond:Expr> <end:@R> ";" => Statement { t: StatementType::Do(cond, contents), start, end },
	<start:@L> "for" <pro:Statement> <cond:Expr> ";" <epi:Statement> <end:@R> "{" <contents:Statement*> "}" => Statement { t: StatementType::For(Box::new(pro), cond, Box::new(epi), contents), start, end },
	<start:@L> "repeat" <cond:Expr> <end:@R> "{" <contents:Statement*> "}" => Statement { t: StatementType::Repeat(cond, contents), start, end },
	<start:@L> "loop" <end:@R> "{" <contents:Statement*> "}" => Statement { t: StatementType::Loop(contents), start, end },
	<start:@L> "return" <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Call(String::from("ret"), vec![])), start, end },
	<start:@L> "yield" <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Call(String::from("yld"), vec![])), start, end },
}

DefinitionParam: DefinitionParam = {
	"return" <i:Iden> => DefinitionParam::Return(i),
	"const" <i:Iden> => DefinitionParam::Const(i),
	Iden => DefinitionParam::Type(<>),
}

AliasParam: AliasParam = {
	"$" <i:number> =>? Ok(AliasParam::ArgId(usize::from_str(i)
		.map_err(|_| ParseError::User {
			error: "Argument index is too large"
		})?)),
	Expr => AliasParam::Expression(<>),
	"const" <expr:Expr> => AliasParam::Const(expr),
}

IfContainer: Statement = {
	<start:@L> "if" <cond:Expr> <end:@R> "{" <contents:Statement*> "}" => Statement { t: StatementType::If(cond, contents, None), start, end },
	<start:@L> "if" <cond:Expr> <end:@R> "{" <contents:Statement*> "}" "else" "{" <else_contents:Statement*> "}" => Statement { t: StatementType::If(cond, contents, Some(else_contents)), start, end },
	<start:@L> "if" <cond:Expr> <end:@R> "{" <contents:Statement*> "}" "else" <else_contents:IfContainer> => Statement { t: StatementType::If(cond, contents, Some(vec![else_contents])), start, end },
}

Assignment: Statement = {
	<start:@L> <l:Iden> "=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l, Box::new(r))), start, end },
	<start:@L> <l:Iden> "+=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::Add(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "-=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::Sub(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "*=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::Mul(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "/=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::Div(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "%=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::Mod(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "&=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::BinaryAnd(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "|=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::BinaryOr(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "^=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::BinaryXor(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> "<<=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::ShiftLeft(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
	<start:@L> <l:Iden> ">>=" <r:Expr> <end:@R> ";" => Statement { t: StatementType::Expression(Rpn::Set(l.clone(), Box::new(Rpn::ShiftRight(Box::new(Rpn::Variable(l)), Box::new(r))))), start, end },
}

Expr = { LogicalOr }

LogicalOr: Rpn = {
	<l:LogicalOr> "||" <r:LogicalAnd> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l != 0 || r != 0) as i64);
			}
		}
		Rpn::LogicalOr(Box::new(l), Box::new(r))
	},
	LogicalAnd,
}

LogicalAnd: Rpn = {
	<l:LogicalAnd> "&&" <r:Compare> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l != 0 && r != 0) as i64);
			}
		}
		Rpn::LogicalAnd(Box::new(l), Box::new(r))
	},
	Compare,
}

Compare: Rpn = {
	<l:Compare> "==" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l == r) as i64);
			}
		}
		Rpn::Equ(Box::new(l), Box::new(r))
	},
	<l:Compare> "!=" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l != r) as i64);
			}
		}
		Rpn::NotEqu(Box::new(l), Box::new(r))
	},
	<l:Compare> "<" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l < r) as i64);
			}
		}
		Rpn::LessThan(Box::new(l), Box::new(r))
	},
	<l:Compare> "<=" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l <= r) as i64);
			}
		}
		Rpn::LessThanEqu(Box::new(l), Box::new(r))
	},
	<l:Compare> ">" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l > r) as i64);
			}
		}
		Rpn::GreaterThan(Box::new(l), Box::new(r))
	},
	<l:Compare> ">=" <r:BinaryOr> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed((l >= r) as i64);
			}
		}
		Rpn::GreaterThanEqu(Box::new(l), Box::new(r))
	},
	BinaryOr,
}

BinaryOr: Rpn = {
	<l:BinaryOr> "|" <r:BinaryXor> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l | r);
			}
		}
		Rpn::BinaryOr(Box::new(l), Box::new(r))
	},
	BinaryXor,
}

BinaryXor: Rpn = {
	<l:BinaryAnd> "^" <r:BinaryAnd> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l ^ r);
			}
		}
		Rpn::BinaryXor(Box::new(l), Box::new(r))
	},
	BinaryAnd,
}

BinaryAnd: Rpn = {
	<l:BinaryAnd> "&" <r:Shift> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l & r);
			}
		}
		Rpn::BinaryAnd(Box::new(l), Box::new(r))
	},
	Shift,
}

Shift: Rpn = {
	<l:Shift> "<<" <r:Addition> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l << r);
			}
		}
		Rpn::ShiftLeft(Box::new(l), Box::new(r))
	},
	<l:Shift> ">>" <r:Addition> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l >> r);
			}
		}
		Rpn::ShiftRight(Box::new(l), Box::new(r))
	},
	Addition,
}

Addition: Rpn = {
	<l:Addition> "+" <r:Factor> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l + r);
			}
		}
		Rpn::Add(Box::new(l), Box::new(r))
	},
	<l:Addition> "-" <r:Factor> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l - r);
			}
		}
		Rpn::Sub(Box::new(l), Box::new(r))
	},
	Factor,
};

Factor: Rpn = {
	<l:Factor> "*" <r:Unary> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l * r);
			}
		}
		Rpn::Mul(Box::new(l), Box::new(r))
	},
	<l:Factor> "/" <r:Unary> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l / r);
			}
		}
		Rpn::Div(Box::new(l), Box::new(r))
	},
	<l:Factor> "%" <r:Unary> => {
		if let Rpn::Signed(l) = l {
			if let Rpn::Signed(r) = r {
				return Rpn::Signed(l % r);
			}
		}
		Rpn::Mod(Box::new(l), Box::new(r))
	},
	Unary,
};

Unary: Rpn = {
	"-" <l:Term> => {
		if let Rpn::Signed(i) = l {
			return Rpn::Signed(-i)
		}
		Rpn::Negate(Box::new(l))
	},
	"!" <l:Term> => {
		if let Rpn::Signed(i) = l {
			return Rpn::Signed(!i)
		}
		Rpn::Not(Box::new(l))
	},
	"&" <l:Iden> => Rpn::Address(l),
	Term,
};

Term: Rpn = {
	Num,
	String => Rpn::String(<>),
	Iden => Rpn::Variable(<>),
	"(" <Expr> ")",
	"[" <e:Expr> "]" => Rpn::Deref(Box::new(e)),
	<i:Iden> "(" <args:Comma<Expr>> ")" => Rpn::Call(i, args)
};

Num: Rpn = {
	number =>? Ok(Rpn::Signed(i64::from_str(<>)
		.map_err(|_| ParseError::User {
			error: "Integer is too large (maximum of 64 bits, signed)"
		})?)),
};

Iden: String = {
	identifier => String::from(<>),
};

String: String = {
	string =>? {
		let mut bytes = <>.bytes().collect::<VecDeque<u8>>();
		bytes.pop_front();
		bytes.pop_back();
		let mut result = String::new();
		bytes.read_to_string(&mut result)
			.map_err(|err| ParseError::User {
				error: "Invalid UTF8"
			})?;
		Ok(result)
	},
}

Comma<T>: Vec<T> = {
    <mut v:(<T> ",")*> <e:T?> => match e {
        None => v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};