#![allow(clippy::empty_docs)]
#[derive(Parser)]
#[grammar = "parser/ms_grammar.pest"] pub struct MsParser;
use crate::ast::StringSpan;
use pest::error::Error;
use pest::iterators::Pair;
use pest::Parser;
use std::boxed::Box;
use crate::ast;
use crate::ast::Ast;
use crate::ast::AstKind;
fn parse_sign(pair: Pair<Rule>) -> char {
*pair
.as_str()
.chars()
.collect::<Vec<char>>()
.first()
.unwrap()
}
fn parse_name(pair: Pair<'_, Rule>) -> &str {
pair.as_str()
}
fn parse_value(pair: Pair<'_, Rule>) -> Ast<'_> {
let span = Some(StringSpan {
pos_start: pair.as_span().start(),
pos_end: pair.as_span().end(),
});
match pair.as_rule() {
Rule::name | Rule::domain_name => Ast {
kind: AstKind::Name(ast::Name {
name: pair.as_str(),
indice: None,
indices: vec![],
is_tangent: false,
}),
span,
},
Rule::integer | Rule::real => Ast {
kind: AstKind::Number(pair.as_str().parse().unwrap()),
span,
},
Rule::model => {
let mut inner = pair.into_inner();
let name = parse_name(inner.next().unwrap());
let mut unknowns: Vec<Box<Ast>> = Vec::new();
while inner.peek().is_some() && inner.peek().unwrap().as_rule() == Rule::unknown {
unknowns.push(Box::new(parse_value(inner.next().unwrap())));
}
let statements = inner.map(parse_value).map(Box::new).collect();
Ast {
kind: AstKind::Model(ast::Model {
name,
unknowns,
statements,
}),
span,
}
}
Rule::definition => {
let mut inner = pair.into_inner();
let name = parse_name(inner.next().unwrap());
let rhs = Box::new(parse_value(inner.next().unwrap()));
Ast {
kind: AstKind::Definition(ast::Definition { name, rhs }),
span,
}
}
Rule::range => {
let mut inner = pair.into_inner();
Ast {
kind: AstKind::Range(ast::Range {
lower: inner.next().unwrap().as_str().parse().unwrap(),
upper: inner.next().unwrap().as_str().parse().unwrap(),
}),
span,
}
}
Rule::domain => parse_value(pair.into_inner().next().unwrap()),
Rule::codomain => parse_value(pair.into_inner().next().unwrap()),
Rule::unknown => {
let mut inner = pair.into_inner();
let name = parse_name(inner.next().unwrap());
let dependents =
if inner.peek().is_some() && inner.peek().unwrap().as_rule() == Rule::dependents {
inner.next().unwrap().into_inner().map(parse_name).collect()
} else {
Vec::new()
};
let codomain = if inner.peek().is_some() {
Some(Box::new(parse_value(inner.next().unwrap())))
} else {
None
};
Ast {
kind: AstKind::Unknown(ast::Unknown {
name,
dependents,
codomain,
}),
span,
}
}
Rule::statement => parse_value(pair.into_inner().next().unwrap()),
Rule::call_arg => {
let mut inner = pair.into_inner();
let name = if inner.peek().unwrap().as_rule() == Rule::name {
Some(parse_name(inner.next().unwrap()))
} else {
None
};
Ast {
kind: AstKind::CallArg(ast::CallArg {
name,
expression: Box::new(parse_value(inner.next().unwrap())),
}),
span,
}
}
Rule::call => {
let mut inner = pair.into_inner();
Ast {
kind: AstKind::Call(ast::Call {
fn_name: parse_name(inner.next().unwrap()),
args: inner.map(parse_value).map(Box::new).collect(),
is_tangent: false,
}),
span,
}
}
Rule::submodel => {
let mut inner = pair.into_inner();
let (name, args) = if let Ast {
kind:
AstKind::Call(ast::Call {
fn_name,
args,
is_tangent: _,
}),
span: _,
} = parse_value(inner.next().unwrap())
{
(fn_name, args)
} else {
unreachable!()
};
let local_name = if inner.peek().is_some() {
parse_name(inner.next().unwrap())
} else {
name
};
Ast {
kind: AstKind::Submodel(ast::Submodel {
name,
local_name,
args,
}),
span,
}
}
Rule::rate_equation => {
let mut inner = pair.into_inner();
let name = parse_name(inner.next().unwrap());
Ast {
kind: AstKind::RateEquation(ast::RateEquation {
name,
rhs: Box::new(parse_value(inner.next().unwrap())),
}),
span,
}
}
Rule::equation => {
let mut inner = pair.into_inner();
Ast {
kind: AstKind::Equation(ast::Equation {
lhs: Box::new(parse_value(inner.next().unwrap())),
rhs: Box::new(parse_value(inner.next().unwrap())),
}),
span,
}
}
Rule::expression => {
let mut inner = pair.into_inner();
let sign = if inner.peek().unwrap().as_rule() == Rule::sign {
Some(parse_sign(inner.next().unwrap()))
} else {
None
};
let mut head_term = parse_value(inner.next().unwrap());
while inner.peek().is_some() {
let term_op = parse_sign(inner.next().unwrap());
let rhs_term = parse_value(inner.next().unwrap());
let subspan = Some(StringSpan {
pos_start: head_term.span.unwrap().pos_start,
pos_end: rhs_term.span.unwrap().pos_end,
});
head_term = Ast {
kind: AstKind::Binop(ast::Binop {
op: term_op,
left: Box::new(head_term),
right: Box::new(rhs_term),
}),
span: subspan,
};
}
if let Some(sign) = sign {
Ast {
kind: AstKind::Monop(ast::Monop {
op: sign,
child: Box::new(head_term),
}),
span,
}
} else {
head_term
}
}
Rule::term => {
let mut inner = pair.into_inner();
let mut head_factor = parse_value(inner.next().unwrap());
while inner.peek().is_some() {
let factor_op = parse_sign(inner.next().unwrap());
let rhs_factor = parse_value(inner.next().unwrap());
let subspan = Some(StringSpan {
pos_start: head_factor.span.unwrap().pos_start,
pos_end: rhs_factor.span.unwrap().pos_end,
});
head_factor = Ast {
kind: AstKind::Binop(ast::Binop {
op: factor_op,
left: Box::new(head_factor),
right: Box::new(rhs_factor),
}),
span: subspan,
};
}
head_factor
}
Rule::factor => parse_value(pair.into_inner().next().unwrap()),
_ => unreachable!("{:?}", pair.to_string()),
}
}
pub fn parse_string(text: &str) -> Result<Vec<Ast<'_>>, Box<Error<Rule>>> {
let main = MsParser::parse(Rule::main, text)?.next().unwrap();
let ast_nodes = main
.into_inner()
.take_while(|pair| pair.as_rule() != Rule::EOI)
.map(parse_value)
.collect();
Ok(ast_nodes)
}
#[cfg(test)]
mod tests {
use super::parse_string;
use crate::{ast::Ast, ast::AstKind, ast::Model};
fn ast_to_model(node: Ast) -> Model {
if let AstKind::Model(model) = node.kind {
model
} else {
unreachable!()
}
}
#[test]
fn empty_model() {
const TEXT: &str = "model test() {}";
let models: Vec<Model> = parse_string(TEXT)
.unwrap()
.into_iter()
.map(ast_to_model)
.collect();
assert_eq!(models.len(), 1);
assert_eq!(models[0].name, "test");
assert!(models[0].unknowns.is_empty());
assert!(models[0].statements.is_empty());
}
#[test]
fn two_models() {
let text = "
model capacitor( i(t), v(t), c -> NonNegative) {
i = c * dot(v)
}
model resistor( i(t), v(t), r -> NonNegative) {
v = i * r
}
";
let models: Vec<Model> = parse_string(text)
.unwrap()
.into_iter()
.map(ast_to_model)
.collect();
assert_eq!(models.len(), 2);
assert_eq!(models[0].name, "capacitor");
assert_eq!(models[0].unknowns.len(), 3);
if let AstKind::Unknown(unknown) = &models[0].unknowns[0].kind {
assert_eq!(unknown.name, "i");
assert_eq!(unknown.dependents.len(), 1);
assert!(unknown.codomain.is_none());
} else {
panic!("should be unknown");
}
if let AstKind::Unknown(unknown) = &models[0].unknowns[1].kind {
assert_eq!(unknown.name, "v");
assert_eq!(unknown.dependents.len(), 1);
assert!(unknown.codomain.is_none());
} else {
panic!("should be unknown");
}
if let AstKind::Unknown(unknown) = &models[0].unknowns[2].kind {
assert_eq!(unknown.name, "c");
assert_eq!(unknown.dependents.len(), 0);
assert!(unknown.codomain.is_some());
} else {
panic!("should be unknown");
}
assert_eq!(models[0].statements.len(), 1);
if let AstKind::Equation(eqn) = &models[0].statements[0].kind {
assert!(matches!(&eqn.lhs.kind, AstKind::Name(name) if name.name == "i"));
assert!(matches!(&eqn.rhs.kind, AstKind::Binop(binop) if binop.op == '*'));
} else {
panic!("not an equation")
}
}
#[test]
fn rate_equation() {
let text = "
model diffusion( x -> Omega, d -> NonNegative, y(x) ) {
dot(y) = d * div(grad(y, x), x)
}
";
let models: Vec<Model> = parse_string(text)
.unwrap()
.into_iter()
.map(ast_to_model)
.collect();
assert_eq!(models.len(), 1);
assert_eq!(models[0].name, "diffusion");
assert_eq!(models[0].unknowns.len(), 3);
assert_eq!(models[0].statements.len(), 1);
if let AstKind::RateEquation(reqn) = &models[0].statements[0].kind {
assert_eq!(reqn.name, "y");
assert!(matches!(&reqn.rhs.kind, AstKind::Binop(binop) if binop.op == '*'));
} else {
panic!("not a rate equation")
}
}
#[test]
fn submodel_and_let() {
let text = "
model resistor( i(t), v(t), r -> NonNegative) {
v = i * r
}
model circuit(i1(t), i2(t), i3(t)) {
let inputVoltage = sin(t)
use resistor(v = inputVoltage)
}
";
let models: Vec<Model> = parse_string(text)
.unwrap()
.into_iter()
.map(ast_to_model)
.collect();
assert_eq!(models.len(), 2);
assert_eq!(models[1].name, "circuit");
assert_eq!(models[1].unknowns.len(), 3);
assert_eq!(models[1].statements.len(), 2);
if let AstKind::Definition(dfn) = &models[1].statements[0].kind {
assert_eq!(dfn.name, "inputVoltage");
assert!(
matches!(&dfn.rhs.kind, AstKind::Call(call) if call.fn_name == "sin" && call.args.len() == 1)
);
} else {
panic!("not an definition")
}
if let AstKind::Submodel(submodel) = &models[1].statements[1].kind {
assert_eq!(submodel.name, "resistor");
assert_eq!(submodel.local_name, "resistor");
assert_eq!(submodel.args.len(), 1);
if let AstKind::CallArg(arg) = &submodel.args[0].kind {
assert_eq!(arg.name.unwrap(), "v");
assert!(
matches!(&arg.expression.kind, AstKind::Name(name) if name.name == "inputVoltage")
);
} else {
unreachable!("not a call arg")
}
} else {
panic!("not an definition")
}
}
#[test]
fn comments_are_ignored() {
let text = "
// leading comment
/* leading block comment */
model test(x, y) { // trailing model comment
let a = 1 /* trailing definition block comment */
// comment between statements
/* block comment between statements */
x = a + y // trailing equation comment
}
/* trailing block comment */
// trailing comment
";
let models: Vec<Model> = parse_string(text)
.unwrap()
.into_iter()
.map(ast_to_model)
.collect();
assert_eq!(models.len(), 1);
assert_eq!(models[0].name, "test");
assert_eq!(models[0].unknowns.len(), 2);
assert_eq!(models[0].statements.len(), 2);
if let AstKind::Definition(dfn) = &models[0].statements[0].kind {
assert_eq!(dfn.name, "a");
assert_eq!(dfn.rhs.to_string(), "1");
} else {
panic!("not a definition")
}
if let AstKind::Equation(eqn) = &models[0].statements[1].kind {
assert!(matches!(&eqn.lhs.kind, AstKind::Name(name) if name.name == "x"));
assert!(matches!(&eqn.rhs.kind, AstKind::Binop(binop) if binop.op == '+'));
} else {
panic!("not an equation")
}
}
}