2022-11-26 23:57:50 +00:00
|
|
|
// Copyright 2020 The Jujutsu Authors
|
2020-12-12 08:00:42 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2023-02-12 08:21:06 +00:00
|
|
|
use std::collections::HashMap;
|
2024-05-17 08:27:52 +00:00
|
|
|
use std::{error, mem};
|
2023-02-02 08:57:55 +00:00
|
|
|
|
2023-01-28 11:09:13 +00:00
|
|
|
use itertools::Itertools as _;
|
2024-05-17 07:44:16 +00:00
|
|
|
use jj_lib::dsl_util::{
|
2024-05-18 03:44:48 +00:00
|
|
|
self, collect_similar, AliasDeclaration, AliasDeclarationParser, AliasDefinitionParser,
|
|
|
|
AliasExpandError, AliasExpandableExpression, AliasId, AliasesMap, ExpressionFolder,
|
|
|
|
FoldableExpression, InvalidArguments, StringLiteralParser,
|
2024-05-17 07:44:16 +00:00
|
|
|
};
|
2024-02-05 10:30:12 +00:00
|
|
|
use once_cell::sync::Lazy;
|
2021-03-14 17:46:35 +00:00
|
|
|
use pest::iterators::{Pair, Pairs};
|
2024-02-05 10:30:12 +00:00
|
|
|
use pest::pratt_parser::{Assoc, Op, PrattParser};
|
2020-12-12 08:00:42 +00:00
|
|
|
use pest::Parser;
|
2022-09-22 04:52:04 +00:00
|
|
|
use pest_derive::Parser;
|
2023-02-02 08:57:55 +00:00
|
|
|
use thiserror::Error;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
#[derive(Parser)]
|
|
|
|
#[grammar = "template.pest"]
|
2023-02-12 14:15:44 +00:00
|
|
|
struct TemplateParser;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2024-04-07 05:52:16 +00:00
|
|
|
const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser {
|
|
|
|
content_rule: Rule::string_content,
|
|
|
|
escape_rule: Rule::string_escape,
|
|
|
|
};
|
|
|
|
|
2024-02-07 03:08:03 +00:00
|
|
|
impl Rule {
|
|
|
|
fn to_symbol(self) -> Option<&'static str> {
|
|
|
|
match self {
|
|
|
|
Rule::EOI => None,
|
|
|
|
Rule::whitespace => None,
|
2024-04-07 06:38:20 +00:00
|
|
|
Rule::string_escape => None,
|
|
|
|
Rule::string_content_char => None,
|
|
|
|
Rule::string_content => None,
|
|
|
|
Rule::string_literal => None,
|
2024-04-17 12:17:40 +00:00
|
|
|
Rule::raw_string_content => None,
|
|
|
|
Rule::raw_string_literal => None,
|
2024-02-07 03:08:03 +00:00
|
|
|
Rule::integer_literal => None,
|
|
|
|
Rule::identifier => None,
|
2024-02-07 11:31:53 +00:00
|
|
|
Rule::concat_op => Some("++"),
|
2024-02-07 03:08:03 +00:00
|
|
|
Rule::logical_or_op => Some("||"),
|
|
|
|
Rule::logical_and_op => Some("&&"),
|
|
|
|
Rule::logical_not_op => Some("!"),
|
2024-02-06 12:57:59 +00:00
|
|
|
Rule::negate_op => Some("-"),
|
2024-02-07 03:08:03 +00:00
|
|
|
Rule::prefix_ops => None,
|
|
|
|
Rule::infix_ops => None,
|
|
|
|
Rule::function => None,
|
|
|
|
Rule::function_arguments => None,
|
|
|
|
Rule::lambda => None,
|
|
|
|
Rule::formal_parameters => None,
|
|
|
|
Rule::primary => None,
|
|
|
|
Rule::term => None,
|
|
|
|
Rule::expression => None,
|
|
|
|
Rule::template => None,
|
|
|
|
Rule::program => None,
|
|
|
|
Rule::function_alias_declaration => None,
|
|
|
|
Rule::alias_declaration => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-18 08:13:24 +00:00
|
|
|
pub type TemplateParseResult<T> = Result<T, TemplateParseError>;
|
2023-02-03 10:42:03 +00:00
|
|
|
|
2024-03-26 03:16:14 +00:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
#[error("{pest_error}")]
|
2023-02-02 08:57:55 +00:00
|
|
|
pub struct TemplateParseError {
|
|
|
|
kind: TemplateParseErrorKind,
|
|
|
|
pest_error: Box<pest::error::Error<Rule>>,
|
2024-03-26 02:34:35 +00:00
|
|
|
source: Option<Box<dyn error::Error + Send + Sync>>,
|
2023-02-02 08:57:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
|
|
|
pub enum TemplateParseErrorKind {
|
|
|
|
#[error("Syntax error")]
|
|
|
|
SyntaxError,
|
2024-02-26 05:49:46 +00:00
|
|
|
#[error(r#"Keyword "{name}" doesn't exist"#)]
|
|
|
|
NoSuchKeyword {
|
|
|
|
name: String,
|
|
|
|
candidates: Vec<String>,
|
|
|
|
},
|
|
|
|
#[error(r#"Function "{name}" doesn't exist"#)]
|
|
|
|
NoSuchFunction {
|
|
|
|
name: String,
|
|
|
|
candidates: Vec<String>,
|
|
|
|
},
|
2023-02-02 10:31:50 +00:00
|
|
|
#[error(r#"Method "{name}" doesn't exist for type "{type_name}""#)]
|
2024-02-26 05:49:46 +00:00
|
|
|
NoSuchMethod {
|
|
|
|
type_name: String,
|
|
|
|
name: String,
|
|
|
|
candidates: Vec<String>,
|
|
|
|
},
|
2023-03-15 03:22:41 +00:00
|
|
|
#[error(r#"Function "{name}": {message}"#)]
|
|
|
|
InvalidArguments { name: String, message: String },
|
2023-02-12 08:21:06 +00:00
|
|
|
#[error("Redefinition of function parameter")]
|
|
|
|
RedefinedFunctionParameter,
|
2023-03-15 06:02:44 +00:00
|
|
|
#[error("{0}")]
|
2024-03-29 11:35:36 +00:00
|
|
|
Expression(String),
|
2023-02-12 09:41:53 +00:00
|
|
|
#[error(r#"Alias "{0}" cannot be expanded"#)]
|
|
|
|
BadAliasExpansion(String),
|
2024-05-15 07:18:16 +00:00
|
|
|
#[error(r#"Function parameter "{0}" cannot be expanded"#)]
|
|
|
|
BadParameterExpansion(String),
|
2023-02-12 09:41:53 +00:00
|
|
|
#[error(r#"Alias "{0}" expanded recursively"#)]
|
|
|
|
RecursiveAlias(String),
|
2023-02-02 10:31:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TemplateParseError {
|
2023-02-18 08:13:24 +00:00
|
|
|
pub fn with_span(kind: TemplateParseErrorKind, span: pest::Span<'_>) -> Self {
|
2024-03-25 15:54:39 +00:00
|
|
|
let message = kind.to_string();
|
2023-02-02 10:31:50 +00:00
|
|
|
let pest_error = Box::new(pest::error::Error::new_from_span(
|
2024-02-03 08:03:58 +00:00
|
|
|
pest::error::ErrorVariant::CustomError { message },
|
2023-02-02 10:31:50 +00:00
|
|
|
span,
|
|
|
|
));
|
2023-02-12 09:48:12 +00:00
|
|
|
TemplateParseError {
|
|
|
|
kind,
|
|
|
|
pest_error,
|
2024-03-26 02:34:35 +00:00
|
|
|
source: None,
|
2023-02-12 09:48:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-29 03:39:34 +00:00
|
|
|
pub fn with_source(mut self, source: impl Into<Box<dyn error::Error + Send + Sync>>) -> Self {
|
|
|
|
self.source = Some(source.into());
|
|
|
|
self
|
2023-02-02 10:31:50 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 05:49:46 +00:00
|
|
|
// TODO: migrate all callers to table-based lookup_method()
|
|
|
|
pub(crate) fn no_such_method(
|
|
|
|
type_name: impl Into<String>,
|
|
|
|
function: &FunctionCallNode,
|
|
|
|
) -> Self {
|
2023-02-02 10:31:50 +00:00
|
|
|
TemplateParseError::with_span(
|
|
|
|
TemplateParseErrorKind::NoSuchMethod {
|
|
|
|
type_name: type_name.into(),
|
2023-02-11 12:19:15 +00:00
|
|
|
name: function.name.to_owned(),
|
2024-02-26 05:49:46 +00:00
|
|
|
candidates: vec![],
|
2023-02-02 10:31:50 +00:00
|
|
|
},
|
2023-02-11 12:19:15 +00:00
|
|
|
function.name_span,
|
2023-02-02 10:31:50 +00:00
|
|
|
)
|
|
|
|
}
|
2023-02-02 11:13:12 +00:00
|
|
|
|
2024-04-24 11:02:50 +00:00
|
|
|
pub fn expected_type(expected: &str, actual: &str, span: pest::Span<'_>) -> Self {
|
|
|
|
let message =
|
|
|
|
format!(r#"Expected expression of type "{expected}", but actual type is "{actual}""#);
|
2024-03-29 11:35:36 +00:00
|
|
|
TemplateParseError::expression(message, span)
|
2023-03-15 06:02:44 +00:00
|
|
|
}
|
|
|
|
|
2024-03-29 11:35:36 +00:00
|
|
|
/// Some other expression error.
|
|
|
|
pub fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
|
|
|
|
TemplateParseError::with_span(TemplateParseErrorKind::Expression(message.into()), span)
|
2024-03-25 15:54:39 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 05:49:46 +00:00
|
|
|
/// If this is a `NoSuchKeyword` error, expands the candidates list with the
|
|
|
|
/// given `other_keywords`.
|
|
|
|
pub fn extend_keyword_candidates<I>(mut self, other_keywords: I) -> Self
|
|
|
|
where
|
|
|
|
I: IntoIterator,
|
|
|
|
I::Item: AsRef<str>,
|
|
|
|
{
|
|
|
|
if let TemplateParseErrorKind::NoSuchKeyword { name, candidates } = &mut self.kind {
|
|
|
|
let other_candidates = collect_similar(name, other_keywords);
|
|
|
|
*candidates = itertools::merge(mem::take(candidates), other_candidates)
|
|
|
|
.dedup()
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-02-26 07:06:51 +00:00
|
|
|
/// If this is a `NoSuchFunction` error, expands the candidates list with
|
|
|
|
/// the given `other_functions`.
|
|
|
|
pub fn extend_function_candidates<I>(mut self, other_functions: I) -> Self
|
|
|
|
where
|
|
|
|
I: IntoIterator,
|
|
|
|
I::Item: AsRef<str>,
|
|
|
|
{
|
|
|
|
if let TemplateParseErrorKind::NoSuchFunction { name, candidates } = &mut self.kind {
|
|
|
|
let other_candidates = collect_similar(name, other_functions);
|
|
|
|
*candidates = itertools::merge(mem::take(candidates), other_candidates)
|
|
|
|
.dedup()
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Expands keyword/function candidates with the given aliases.
|
|
|
|
pub fn extend_alias_candidates(self, aliases_map: &TemplateAliasesMap) -> Self {
|
2024-05-17 08:27:52 +00:00
|
|
|
self.extend_keyword_candidates(aliases_map.symbol_names())
|
|
|
|
.extend_function_candidates(aliases_map.function_names())
|
2024-02-26 07:06:51 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 05:49:46 +00:00
|
|
|
pub fn kind(&self) -> &TemplateParseErrorKind {
|
|
|
|
&self.kind
|
|
|
|
}
|
|
|
|
|
2023-02-12 09:48:12 +00:00
|
|
|
/// Original parsing error which typically occurred in an alias expression.
|
|
|
|
pub fn origin(&self) -> Option<&Self> {
|
2024-03-26 02:34:35 +00:00
|
|
|
self.source.as_ref().and_then(|e| e.downcast_ref())
|
2023-02-12 09:48:12 +00:00
|
|
|
}
|
2023-02-02 08:57:55 +00:00
|
|
|
}
|
|
|
|
|
2024-05-18 03:17:06 +00:00
|
|
|
impl AliasExpandError for TemplateParseError {
|
|
|
|
fn invalid_arguments(err: InvalidArguments<'_>) -> Self {
|
|
|
|
err.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn recursive_expansion(id: AliasId<'_>, span: pest::Span<'_>) -> Self {
|
|
|
|
Self::with_span(TemplateParseErrorKind::RecursiveAlias(id.to_string()), span)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn within_alias_expansion(self, id: AliasId<'_>, span: pest::Span<'_>) -> Self {
|
|
|
|
let kind = match id {
|
|
|
|
AliasId::Symbol(_) | AliasId::Function(_) => {
|
|
|
|
TemplateParseErrorKind::BadAliasExpansion(id.to_string())
|
|
|
|
}
|
|
|
|
AliasId::Parameter(_) => TemplateParseErrorKind::BadParameterExpansion(id.to_string()),
|
|
|
|
};
|
|
|
|
Self::with_span(kind, span).with_source(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-02 08:57:55 +00:00
|
|
|
impl From<pest::error::Error<Rule>> for TemplateParseError {
|
|
|
|
fn from(err: pest::error::Error<Rule>) -> Self {
|
|
|
|
TemplateParseError {
|
|
|
|
kind: TemplateParseErrorKind::SyntaxError,
|
2024-02-07 03:08:03 +00:00
|
|
|
pest_error: Box::new(rename_rules_in_pest_error(err)),
|
2024-03-26 02:34:35 +00:00
|
|
|
source: None,
|
2023-02-02 08:57:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-20 08:31:35 +00:00
|
|
|
impl From<InvalidArguments<'_>> for TemplateParseError {
|
|
|
|
fn from(err: InvalidArguments<'_>) -> Self {
|
|
|
|
let kind = TemplateParseErrorKind::InvalidArguments {
|
|
|
|
name: err.name.to_owned(),
|
|
|
|
message: err.message,
|
|
|
|
};
|
|
|
|
Self::with_span(kind, err.span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 03:08:03 +00:00
|
|
|
fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Error<Rule> {
|
|
|
|
err.renamed_rules(|rule| {
|
|
|
|
rule.to_symbol()
|
|
|
|
.map(|sym| format!("`{sym}`"))
|
|
|
|
.unwrap_or_else(|| format!("<{rule:?}>"))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2023-03-12 11:10:19 +00:00
|
|
|
pub enum ExpressionKind<'i> {
|
2023-02-11 11:24:26 +00:00
|
|
|
Identifier(&'i str),
|
2023-09-02 08:49:21 +00:00
|
|
|
Boolean(bool),
|
2023-02-11 11:24:26 +00:00
|
|
|
Integer(i64),
|
|
|
|
String(String),
|
2024-02-05 10:30:12 +00:00
|
|
|
Unary(UnaryOp, Box<ExpressionNode<'i>>),
|
|
|
|
Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
|
2023-03-07 09:11:54 +00:00
|
|
|
Concat(Vec<ExpressionNode<'i>>),
|
2024-05-16 13:38:27 +00:00
|
|
|
FunctionCall(Box<FunctionCallNode<'i>>),
|
|
|
|
MethodCall(Box<MethodCallNode<'i>>),
|
|
|
|
Lambda(Box<LambdaNode<'i>>),
|
2023-02-12 12:44:33 +00:00
|
|
|
/// Identity node to preserve the span in the source template text.
|
2024-05-17 08:27:52 +00:00
|
|
|
AliasExpanded(AliasId<'i>, Box<ExpressionNode<'i>>),
|
2023-02-11 11:24:26 +00:00
|
|
|
}
|
|
|
|
|
2024-05-18 03:27:52 +00:00
|
|
|
impl<'i> FoldableExpression<'i> for ExpressionKind<'i> {
|
|
|
|
fn fold<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error>
|
|
|
|
where
|
|
|
|
F: ExpressionFolder<'i, Self> + ?Sized,
|
|
|
|
{
|
|
|
|
match self {
|
|
|
|
ExpressionKind::Identifier(name) => folder.fold_identifier(name, span),
|
|
|
|
ExpressionKind::Boolean(_) | ExpressionKind::Integer(_) | ExpressionKind::String(_) => {
|
|
|
|
Ok(self)
|
|
|
|
}
|
|
|
|
ExpressionKind::Unary(op, arg) => {
|
|
|
|
let arg = Box::new(folder.fold_expression(*arg)?);
|
|
|
|
Ok(ExpressionKind::Unary(op, arg))
|
|
|
|
}
|
|
|
|
ExpressionKind::Binary(op, lhs, rhs) => {
|
|
|
|
let lhs = Box::new(folder.fold_expression(*lhs)?);
|
|
|
|
let rhs = Box::new(folder.fold_expression(*rhs)?);
|
|
|
|
Ok(ExpressionKind::Binary(op, lhs, rhs))
|
|
|
|
}
|
|
|
|
ExpressionKind::Concat(nodes) => Ok(ExpressionKind::Concat(
|
|
|
|
dsl_util::fold_expression_nodes(folder, nodes)?,
|
|
|
|
)),
|
|
|
|
ExpressionKind::FunctionCall(function) => folder.fold_function_call(function, span),
|
|
|
|
ExpressionKind::MethodCall(method) => {
|
|
|
|
// Method call is syntactically different from function call.
|
|
|
|
let method = Box::new(MethodCallNode {
|
|
|
|
object: folder.fold_expression(method.object)?,
|
|
|
|
function: dsl_util::fold_function_call_args(folder, method.function)?,
|
|
|
|
});
|
|
|
|
Ok(ExpressionKind::MethodCall(method))
|
|
|
|
}
|
|
|
|
ExpressionKind::Lambda(lambda) => {
|
|
|
|
let lambda = Box::new(LambdaNode {
|
|
|
|
params: lambda.params,
|
|
|
|
params_span: lambda.params_span,
|
|
|
|
body: folder.fold_expression(lambda.body)?,
|
|
|
|
});
|
|
|
|
Ok(ExpressionKind::Lambda(lambda))
|
|
|
|
}
|
|
|
|
ExpressionKind::AliasExpanded(id, subst) => {
|
|
|
|
let subst = Box::new(folder.fold_expression(*subst)?);
|
|
|
|
Ok(ExpressionKind::AliasExpanded(id, subst))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-18 03:07:42 +00:00
|
|
|
impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> {
|
|
|
|
fn identifier(name: &'i str) -> Self {
|
|
|
|
ExpressionKind::Identifier(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn function_call(function: Box<FunctionCallNode<'i>>) -> Self {
|
|
|
|
ExpressionKind::FunctionCall(function)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alias_expanded(id: AliasId<'i>, subst: Box<ExpressionNode<'i>>) -> Self {
|
|
|
|
ExpressionKind::AliasExpanded(id, subst)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-05 10:30:12 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
pub enum UnaryOp {
|
|
|
|
/// `!`
|
|
|
|
LogicalNot,
|
2024-02-06 12:57:59 +00:00
|
|
|
/// `-`
|
|
|
|
Negate,
|
2024-02-05 10:30:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
pub enum BinaryOp {
|
|
|
|
/// `||`
|
|
|
|
LogicalOr,
|
|
|
|
/// `&&`
|
|
|
|
LogicalAnd,
|
|
|
|
}
|
|
|
|
|
2024-05-13 12:57:01 +00:00
|
|
|
pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
|
|
|
|
pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;
|
2023-02-11 11:24:26 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2023-03-12 11:10:19 +00:00
|
|
|
pub struct MethodCallNode<'i> {
|
2024-05-16 13:38:27 +00:00
|
|
|
pub object: ExpressionNode<'i>,
|
2023-03-12 11:10:19 +00:00
|
|
|
pub function: FunctionCallNode<'i>,
|
2023-02-11 11:24:26 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
pub struct LambdaNode<'i> {
|
|
|
|
pub params: Vec<&'i str>,
|
|
|
|
pub params_span: pest::Span<'i>,
|
2024-05-16 13:38:27 +00:00
|
|
|
pub body: ExpressionNode<'i>,
|
2023-03-07 10:14:00 +00:00
|
|
|
}
|
|
|
|
|
2023-09-02 08:49:21 +00:00
|
|
|
fn parse_identifier_or_literal(pair: Pair<Rule>) -> ExpressionKind {
|
|
|
|
assert_eq!(pair.as_rule(), Rule::identifier);
|
|
|
|
match pair.as_str() {
|
|
|
|
"false" => ExpressionKind::Boolean(false),
|
|
|
|
"true" => ExpressionKind::Boolean(true),
|
|
|
|
name => ExpressionKind::Identifier(name),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_identifier_name(pair: Pair<Rule>) -> TemplateParseResult<&str> {
|
|
|
|
let span = pair.as_span();
|
|
|
|
if let ExpressionKind::Identifier(name) = parse_identifier_or_literal(pair) {
|
|
|
|
Ok(name)
|
|
|
|
} else {
|
2024-03-29 11:35:36 +00:00
|
|
|
Err(TemplateParseError::expression("Expected identifier", span))
|
2023-09-02 08:49:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
fn parse_formal_parameters(params_pair: Pair<Rule>) -> TemplateParseResult<Vec<&str>> {
|
|
|
|
assert_eq!(params_pair.as_rule(), Rule::formal_parameters);
|
|
|
|
let params_span = params_pair.as_span();
|
2023-09-02 08:49:21 +00:00
|
|
|
let params: Vec<_> = params_pair
|
2023-03-07 10:14:00 +00:00
|
|
|
.into_inner()
|
2023-09-02 08:49:21 +00:00
|
|
|
.map(parse_identifier_name)
|
|
|
|
.try_collect()?;
|
2023-03-07 10:14:00 +00:00
|
|
|
if params.iter().all_unique() {
|
|
|
|
Ok(params)
|
|
|
|
} else {
|
|
|
|
Err(TemplateParseError::with_span(
|
|
|
|
TemplateParseErrorKind::RedefinedFunctionParameter,
|
|
|
|
params_span,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
fn parse_function_call_node(pair: Pair<Rule>) -> TemplateParseResult<FunctionCallNode> {
|
|
|
|
assert_eq!(pair.as_rule(), Rule::function);
|
|
|
|
let mut inner = pair.into_inner();
|
2023-09-02 08:49:21 +00:00
|
|
|
let name_pair = inner.next().unwrap();
|
|
|
|
let name_span = name_pair.as_span();
|
2023-02-11 11:24:26 +00:00
|
|
|
let args_pair = inner.next().unwrap();
|
|
|
|
let args_span = args_pair.as_span();
|
|
|
|
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
|
2023-09-02 08:49:21 +00:00
|
|
|
let name = parse_identifier_name(name_pair)?;
|
2023-02-11 11:24:26 +00:00
|
|
|
let args = args_pair
|
|
|
|
.into_inner()
|
|
|
|
.map(parse_template_node)
|
|
|
|
.try_collect()?;
|
|
|
|
Ok(FunctionCallNode {
|
2023-09-02 08:49:21 +00:00
|
|
|
name,
|
|
|
|
name_span,
|
2023-02-11 11:24:26 +00:00
|
|
|
args,
|
|
|
|
args_span,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
fn parse_lambda_node(pair: Pair<Rule>) -> TemplateParseResult<LambdaNode> {
|
|
|
|
assert_eq!(pair.as_rule(), Rule::lambda);
|
|
|
|
let mut inner = pair.into_inner();
|
|
|
|
let params_pair = inner.next().unwrap();
|
|
|
|
let params_span = params_pair.as_span();
|
|
|
|
let body_pair = inner.next().unwrap();
|
|
|
|
let params = parse_formal_parameters(params_pair)?;
|
|
|
|
let body = parse_template_node(body_pair)?;
|
|
|
|
Ok(LambdaNode {
|
|
|
|
params,
|
|
|
|
params_span,
|
2024-05-16 13:38:27 +00:00
|
|
|
body,
|
2023-03-07 10:14:00 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
fn parse_term_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
|
2023-02-11 14:44:24 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::term);
|
|
|
|
let mut inner = pair.into_inner();
|
|
|
|
let expr = inner.next().unwrap();
|
2023-02-11 11:24:26 +00:00
|
|
|
let span = expr.as_span();
|
2023-02-11 14:44:24 +00:00
|
|
|
let primary = match expr.as_rule() {
|
2024-04-07 06:38:20 +00:00
|
|
|
Rule::string_literal => {
|
2024-04-07 05:52:16 +00:00
|
|
|
let text = STRING_LITERAL_PARSER.parse(expr.into_inner());
|
2023-02-11 11:24:26 +00:00
|
|
|
ExpressionNode::new(ExpressionKind::String(text), span)
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
2024-04-17 12:17:40 +00:00
|
|
|
Rule::raw_string_literal => {
|
|
|
|
let (content,) = expr.into_inner().collect_tuple().unwrap();
|
|
|
|
assert_eq!(content.as_rule(), Rule::raw_string_content);
|
|
|
|
let text = content.as_str().to_owned();
|
|
|
|
ExpressionNode::new(ExpressionKind::String(text), span)
|
|
|
|
}
|
2023-02-11 14:44:24 +00:00
|
|
|
Rule::integer_literal => {
|
|
|
|
let value = expr.as_str().parse().map_err(|err| {
|
2024-03-29 14:00:07 +00:00
|
|
|
TemplateParseError::expression("Invalid integer literal", span).with_source(err)
|
2023-02-11 14:44:24 +00:00
|
|
|
})?;
|
2023-02-11 11:24:26 +00:00
|
|
|
ExpressionNode::new(ExpressionKind::Integer(value), span)
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
2023-09-02 08:49:21 +00:00
|
|
|
Rule::identifier => ExpressionNode::new(parse_identifier_or_literal(expr), span),
|
2023-02-11 14:44:24 +00:00
|
|
|
Rule::function => {
|
2024-05-16 13:38:27 +00:00
|
|
|
let function = Box::new(parse_function_call_node(expr)?);
|
2023-02-11 11:24:26 +00:00
|
|
|
ExpressionNode::new(ExpressionKind::FunctionCall(function), span)
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
2023-03-07 10:14:00 +00:00
|
|
|
Rule::lambda => {
|
2024-05-16 13:38:27 +00:00
|
|
|
let lambda = Box::new(parse_lambda_node(expr)?);
|
2023-03-07 10:14:00 +00:00
|
|
|
ExpressionNode::new(ExpressionKind::Lambda(lambda), span)
|
|
|
|
}
|
2023-02-11 11:24:26 +00:00
|
|
|
Rule::template => parse_template_node(expr)?,
|
2023-02-11 14:44:24 +00:00
|
|
|
other => panic!("unexpected term: {other:?}"),
|
|
|
|
};
|
2023-02-11 11:24:26 +00:00
|
|
|
inner.try_fold(primary, |object, chain| {
|
|
|
|
assert_eq!(chain.as_rule(), Rule::function);
|
2023-03-18 07:36:57 +00:00
|
|
|
let span = object.span.start_pos().span(&chain.as_span().end_pos());
|
2024-05-16 13:38:27 +00:00
|
|
|
let method = Box::new(MethodCallNode {
|
|
|
|
object,
|
2023-02-11 11:24:26 +00:00
|
|
|
function: parse_function_call_node(chain)?,
|
2024-05-16 13:38:27 +00:00
|
|
|
});
|
2023-02-11 11:24:26 +00:00
|
|
|
Ok(ExpressionNode::new(
|
|
|
|
ExpressionKind::MethodCall(method),
|
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
|
|
|
|
2024-02-05 10:30:12 +00:00
|
|
|
fn parse_expression_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
|
|
|
|
assert_eq!(pair.as_rule(), Rule::expression);
|
|
|
|
static PRATT: Lazy<PrattParser<Rule>> = Lazy::new(|| {
|
|
|
|
PrattParser::new()
|
|
|
|
.op(Op::infix(Rule::logical_or_op, Assoc::Left))
|
|
|
|
.op(Op::infix(Rule::logical_and_op, Assoc::Left))
|
2024-02-06 12:57:59 +00:00
|
|
|
.op(Op::prefix(Rule::logical_not_op) | Op::prefix(Rule::negate_op))
|
2024-02-05 10:30:12 +00:00
|
|
|
});
|
|
|
|
PRATT
|
|
|
|
.map_primary(parse_term_node)
|
|
|
|
.map_prefix(|op, rhs| {
|
|
|
|
let op_kind = match op.as_rule() {
|
|
|
|
Rule::logical_not_op => UnaryOp::LogicalNot,
|
2024-02-06 12:57:59 +00:00
|
|
|
Rule::negate_op => UnaryOp::Negate,
|
2024-02-05 10:30:12 +00:00
|
|
|
r => panic!("unexpected prefix operator rule {r:?}"),
|
|
|
|
};
|
|
|
|
let rhs = Box::new(rhs?);
|
|
|
|
let span = op.as_span().start_pos().span(&rhs.span.end_pos());
|
|
|
|
let expr = ExpressionKind::Unary(op_kind, rhs);
|
|
|
|
Ok(ExpressionNode::new(expr, span))
|
|
|
|
})
|
|
|
|
.map_infix(|lhs, op, rhs| {
|
|
|
|
let op_kind = match op.as_rule() {
|
|
|
|
Rule::logical_or_op => BinaryOp::LogicalOr,
|
|
|
|
Rule::logical_and_op => BinaryOp::LogicalAnd,
|
|
|
|
r => panic!("unexpected infix operator rule {r:?}"),
|
|
|
|
};
|
|
|
|
let lhs = Box::new(lhs?);
|
|
|
|
let rhs = Box::new(rhs?);
|
|
|
|
let span = lhs.span.start_pos().span(&rhs.span.end_pos());
|
|
|
|
let expr = ExpressionKind::Binary(op_kind, lhs, rhs);
|
|
|
|
Ok(ExpressionNode::new(expr, span))
|
|
|
|
})
|
|
|
|
.parse(pair.into_inner())
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
fn parse_template_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
|
2023-02-11 14:44:24 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::template);
|
2023-02-11 11:24:26 +00:00
|
|
|
let span = pair.as_span();
|
2023-02-11 14:44:24 +00:00
|
|
|
let inner = pair.into_inner();
|
2024-02-05 10:30:12 +00:00
|
|
|
let mut nodes: Vec<_> = inner
|
2024-02-07 11:31:53 +00:00
|
|
|
.filter_map(|pair| match pair.as_rule() {
|
|
|
|
Rule::concat_op => None,
|
|
|
|
Rule::expression => Some(parse_expression_node(pair)),
|
2024-02-05 10:30:12 +00:00
|
|
|
r => panic!("unexpected template item rule {r:?}"),
|
|
|
|
})
|
|
|
|
.try_collect()?;
|
2023-02-11 11:24:26 +00:00
|
|
|
if nodes.len() == 1 {
|
|
|
|
Ok(nodes.pop().unwrap())
|
2023-02-11 14:44:24 +00:00
|
|
|
} else {
|
2023-03-07 09:11:54 +00:00
|
|
|
Ok(ExpressionNode::new(ExpressionKind::Concat(nodes), span))
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
/// Parses text into AST nodes. No type/name checking is made at this stage.
|
2023-02-12 14:15:44 +00:00
|
|
|
pub fn parse_template(template_text: &str) -> TemplateParseResult<ExpressionNode> {
|
2023-02-11 14:44:24 +00:00
|
|
|
let mut pairs: Pairs<Rule> = TemplateParser::parse(Rule::program, template_text)?;
|
|
|
|
let first_pair = pairs.next().unwrap();
|
|
|
|
if first_pair.as_rule() == Rule::EOI {
|
2023-02-11 11:24:26 +00:00
|
|
|
let span = first_pair.as_span();
|
2023-03-07 09:11:54 +00:00
|
|
|
Ok(ExpressionNode::new(ExpressionKind::Concat(vec![]), span))
|
2023-02-11 14:44:24 +00:00
|
|
|
} else {
|
2023-02-11 11:24:26 +00:00
|
|
|
parse_template_node(first_pair)
|
2023-02-11 14:44:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 08:27:52 +00:00
|
|
|
pub type TemplateAliasesMap = AliasesMap<TemplateAliasParser>;
|
2023-02-12 08:21:06 +00:00
|
|
|
|
2024-05-17 08:27:52 +00:00
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct TemplateAliasParser;
|
2024-05-17 07:44:16 +00:00
|
|
|
|
|
|
|
impl AliasDeclarationParser for TemplateAliasParser {
|
|
|
|
type Error = TemplateParseError;
|
2023-02-12 08:21:06 +00:00
|
|
|
|
2024-05-17 07:44:16 +00:00
|
|
|
fn parse_declaration(&self, source: &str) -> Result<AliasDeclaration, Self::Error> {
|
2023-02-12 08:21:06 +00:00
|
|
|
let mut pairs = TemplateParser::parse(Rule::alias_declaration, source)?;
|
|
|
|
let first = pairs.next().unwrap();
|
|
|
|
match first.as_rule() {
|
2023-09-02 08:49:21 +00:00
|
|
|
Rule::identifier => {
|
|
|
|
let name = parse_identifier_name(first)?.to_owned();
|
2024-05-17 07:44:16 +00:00
|
|
|
Ok(AliasDeclaration::Symbol(name))
|
2023-09-02 08:49:21 +00:00
|
|
|
}
|
2023-02-12 08:21:06 +00:00
|
|
|
Rule::function_alias_declaration => {
|
|
|
|
let mut inner = first.into_inner();
|
|
|
|
let name_pair = inner.next().unwrap();
|
|
|
|
let params_pair = inner.next().unwrap();
|
2023-09-02 08:49:21 +00:00
|
|
|
let name = parse_identifier_name(name_pair)?.to_owned();
|
2023-03-07 10:14:00 +00:00
|
|
|
let params = parse_formal_parameters(params_pair)?
|
|
|
|
.into_iter()
|
|
|
|
.map(|s| s.to_owned())
|
|
|
|
.collect();
|
2024-05-17 07:44:16 +00:00
|
|
|
Ok(AliasDeclaration::Function(name, params))
|
2023-02-12 08:21:06 +00:00
|
|
|
}
|
|
|
|
r => panic!("unexpected alias declaration rule {r:?}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-18 03:44:48 +00:00
|
|
|
impl AliasDefinitionParser for TemplateAliasParser {
|
|
|
|
type Output<'i> = ExpressionKind<'i>;
|
|
|
|
type Error = TemplateParseError;
|
|
|
|
|
|
|
|
fn parse_definition<'i>(&self, source: &'i str) -> Result<ExpressionNode<'i>, Self::Error> {
|
|
|
|
parse_template(source)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-12 11:02:32 +00:00
|
|
|
/// Parses text into AST nodes, and expands aliases.
|
|
|
|
///
|
|
|
|
/// No type/name checking is made at this stage.
|
|
|
|
pub fn parse<'i>(
|
|
|
|
template_text: &'i str,
|
|
|
|
aliases_map: &'i TemplateAliasesMap,
|
|
|
|
) -> TemplateParseResult<ExpressionNode<'i>> {
|
|
|
|
let node = parse_template(template_text)?;
|
2024-05-18 03:44:48 +00:00
|
|
|
dsl_util::expand_aliases(node, aliases_map)
|
2023-03-12 11:02:32 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 12:40:36 +00:00
|
|
|
/// Applies the given function if the `node` is a string literal.
|
|
|
|
pub fn expect_string_literal_with<'a, 'i, T>(
|
|
|
|
node: &'a ExpressionNode<'i>,
|
|
|
|
f: impl FnOnce(&'a str, pest::Span<'i>) -> TemplateParseResult<T>,
|
|
|
|
) -> TemplateParseResult<T> {
|
|
|
|
match &node.kind {
|
|
|
|
ExpressionKind::String(s) => f(s, node.span),
|
|
|
|
ExpressionKind::Identifier(_)
|
2023-09-02 08:49:21 +00:00
|
|
|
| ExpressionKind::Boolean(_)
|
2023-03-14 12:40:36 +00:00
|
|
|
| ExpressionKind::Integer(_)
|
2024-02-05 10:30:12 +00:00
|
|
|
| ExpressionKind::Unary(..)
|
|
|
|
| ExpressionKind::Binary(..)
|
2023-03-14 12:40:36 +00:00
|
|
|
| ExpressionKind::Concat(_)
|
|
|
|
| ExpressionKind::FunctionCall(_)
|
2023-03-07 10:14:00 +00:00
|
|
|
| ExpressionKind::MethodCall(_)
|
2024-03-29 11:35:36 +00:00
|
|
|
| ExpressionKind::Lambda(_) => Err(TemplateParseError::expression(
|
2023-03-15 06:02:44 +00:00
|
|
|
"Expected string literal",
|
2023-03-14 12:40:36 +00:00
|
|
|
node.span,
|
|
|
|
)),
|
|
|
|
ExpressionKind::AliasExpanded(id, subst) => expect_string_literal_with(subst, f)
|
|
|
|
.map_err(|e| e.within_alias_expansion(*id, node.span)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-07 10:55:28 +00:00
|
|
|
/// Applies the given function if the `node` is a lambda.
|
|
|
|
pub fn expect_lambda_with<'a, 'i, T>(
|
|
|
|
node: &'a ExpressionNode<'i>,
|
|
|
|
f: impl FnOnce(&'a LambdaNode<'i>, pest::Span<'i>) -> TemplateParseResult<T>,
|
|
|
|
) -> TemplateParseResult<T> {
|
|
|
|
match &node.kind {
|
|
|
|
ExpressionKind::Lambda(lambda) => f(lambda, node.span),
|
2024-02-06 03:32:19 +00:00
|
|
|
ExpressionKind::Identifier(_)
|
2023-09-02 08:49:21 +00:00
|
|
|
| ExpressionKind::Boolean(_)
|
2023-03-07 10:55:28 +00:00
|
|
|
| ExpressionKind::Integer(_)
|
2024-02-06 03:32:19 +00:00
|
|
|
| ExpressionKind::String(_)
|
2024-02-05 10:30:12 +00:00
|
|
|
| ExpressionKind::Unary(..)
|
|
|
|
| ExpressionKind::Binary(..)
|
2023-03-07 10:55:28 +00:00
|
|
|
| ExpressionKind::Concat(_)
|
|
|
|
| ExpressionKind::FunctionCall(_)
|
2024-03-29 11:35:36 +00:00
|
|
|
| ExpressionKind::MethodCall(_) => Err(TemplateParseError::expression(
|
2023-03-07 10:55:28 +00:00
|
|
|
"Expected lambda expression",
|
|
|
|
node.span,
|
|
|
|
)),
|
|
|
|
ExpressionKind::AliasExpanded(id, subst) => {
|
|
|
|
expect_lambda_with(subst, f).map_err(|e| e.within_alias_expansion(*id, node.span))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-14 10:42:08 +00:00
|
|
|
/// Looks up `table` by the given function name.
|
|
|
|
pub fn lookup_function<'a, V>(
|
|
|
|
table: &'a HashMap<&str, V>,
|
|
|
|
function: &FunctionCallNode,
|
|
|
|
) -> TemplateParseResult<&'a V> {
|
|
|
|
if let Some(value) = table.get(function.name) {
|
|
|
|
Ok(value)
|
|
|
|
} else {
|
|
|
|
let candidates = collect_similar(function.name, table.keys());
|
|
|
|
Err(TemplateParseError::with_span(
|
|
|
|
TemplateParseErrorKind::NoSuchFunction {
|
|
|
|
name: function.name.to_owned(),
|
|
|
|
candidates,
|
|
|
|
},
|
|
|
|
function.name_span,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 01:46:23 +00:00
|
|
|
/// Looks up `table` by the given method name.
|
|
|
|
pub fn lookup_method<'a, V>(
|
|
|
|
type_name: impl Into<String>,
|
|
|
|
table: &'a HashMap<&str, V>,
|
|
|
|
function: &FunctionCallNode,
|
|
|
|
) -> TemplateParseResult<&'a V> {
|
|
|
|
if let Some(value) = table.get(function.name) {
|
|
|
|
Ok(value)
|
|
|
|
} else {
|
2024-02-26 05:49:46 +00:00
|
|
|
let candidates = collect_similar(function.name, table.keys());
|
|
|
|
Err(TemplateParseError::with_span(
|
|
|
|
TemplateParseErrorKind::NoSuchMethod {
|
|
|
|
type_name: type_name.into(),
|
|
|
|
name: function.name.to_owned(),
|
|
|
|
candidates,
|
|
|
|
},
|
|
|
|
function.name_span,
|
|
|
|
))
|
2024-02-25 01:46:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-04 12:36:11 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-03-03 06:48:35 +00:00
|
|
|
use assert_matches::assert_matches;
|
2023-02-17 12:29:56 +00:00
|
|
|
|
2023-03-03 06:48:35 +00:00
|
|
|
use super::*;
|
2023-02-16 12:27:49 +00:00
|
|
|
|
2023-02-12 09:41:53 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct WithTemplateAliasesMap(TemplateAliasesMap);
|
|
|
|
|
|
|
|
impl WithTemplateAliasesMap {
|
|
|
|
fn parse<'i>(&'i self, template_text: &'i str) -> TemplateParseResult<ExpressionNode<'i>> {
|
2023-03-12 11:02:32 +00:00
|
|
|
parse(template_text, &self.0)
|
2023-02-12 09:41:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_normalized<'i>(
|
|
|
|
&'i self,
|
|
|
|
template_text: &'i str,
|
|
|
|
) -> TemplateParseResult<ExpressionNode<'i>> {
|
|
|
|
self.parse(template_text).map(normalize_tree)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn with_aliases(
|
|
|
|
aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
|
|
|
|
) -> WithTemplateAliasesMap {
|
|
|
|
let mut aliases_map = TemplateAliasesMap::new();
|
|
|
|
for (decl, defn) in aliases {
|
|
|
|
aliases_map.insert(decl, defn).unwrap();
|
|
|
|
}
|
|
|
|
WithTemplateAliasesMap(aliases_map)
|
|
|
|
}
|
|
|
|
|
2023-03-03 06:48:35 +00:00
|
|
|
fn parse_into_kind(template_text: &str) -> Result<ExpressionKind, TemplateParseErrorKind> {
|
|
|
|
parse_template(template_text)
|
|
|
|
.map(|node| node.kind)
|
|
|
|
.map_err(|err| err.kind)
|
2023-02-04 12:36:11 +00:00
|
|
|
}
|
|
|
|
|
2023-02-12 09:41:53 +00:00
|
|
|
fn parse_normalized(template_text: &str) -> TemplateParseResult<ExpressionNode> {
|
|
|
|
parse_template(template_text).map(normalize_tree)
|
|
|
|
}
|
|
|
|
|
2023-02-11 11:24:26 +00:00
|
|
|
/// Drops auxiliary data of AST so it can be compared with other node.
|
|
|
|
fn normalize_tree(node: ExpressionNode) -> ExpressionNode {
|
|
|
|
fn empty_span() -> pest::Span<'static> {
|
|
|
|
pest::Span::new("", 0, 0).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn normalize_list(nodes: Vec<ExpressionNode>) -> Vec<ExpressionNode> {
|
|
|
|
nodes.into_iter().map(normalize_tree).collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn normalize_function_call(function: FunctionCallNode) -> FunctionCallNode {
|
|
|
|
FunctionCallNode {
|
|
|
|
name: function.name,
|
|
|
|
name_span: empty_span(),
|
|
|
|
args: normalize_list(function.args),
|
|
|
|
args_span: empty_span(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let normalized_kind = match node.kind {
|
|
|
|
ExpressionKind::Identifier(_)
|
2023-09-02 08:49:21 +00:00
|
|
|
| ExpressionKind::Boolean(_)
|
2023-02-11 11:24:26 +00:00
|
|
|
| ExpressionKind::Integer(_)
|
|
|
|
| ExpressionKind::String(_) => node.kind,
|
2024-02-05 10:30:12 +00:00
|
|
|
ExpressionKind::Unary(op, arg) => {
|
|
|
|
let arg = Box::new(normalize_tree(*arg));
|
|
|
|
ExpressionKind::Unary(op, arg)
|
|
|
|
}
|
|
|
|
ExpressionKind::Binary(op, lhs, rhs) => {
|
|
|
|
let lhs = Box::new(normalize_tree(*lhs));
|
|
|
|
let rhs = Box::new(normalize_tree(*rhs));
|
|
|
|
ExpressionKind::Binary(op, lhs, rhs)
|
|
|
|
}
|
2023-03-07 09:11:54 +00:00
|
|
|
ExpressionKind::Concat(nodes) => ExpressionKind::Concat(normalize_list(nodes)),
|
2023-02-11 11:24:26 +00:00
|
|
|
ExpressionKind::FunctionCall(function) => {
|
2024-05-16 13:38:27 +00:00
|
|
|
let function = Box::new(normalize_function_call(*function));
|
|
|
|
ExpressionKind::FunctionCall(function)
|
2023-02-11 11:24:26 +00:00
|
|
|
}
|
|
|
|
ExpressionKind::MethodCall(method) => {
|
2024-05-16 13:38:27 +00:00
|
|
|
let method = Box::new(MethodCallNode {
|
|
|
|
object: normalize_tree(method.object),
|
|
|
|
function: normalize_function_call(method.function),
|
|
|
|
});
|
|
|
|
ExpressionKind::MethodCall(method)
|
2023-02-11 11:24:26 +00:00
|
|
|
}
|
2023-03-07 10:14:00 +00:00
|
|
|
ExpressionKind::Lambda(lambda) => {
|
2024-05-16 13:38:27 +00:00
|
|
|
let lambda = Box::new(LambdaNode {
|
2023-03-07 10:14:00 +00:00
|
|
|
params: lambda.params,
|
|
|
|
params_span: empty_span(),
|
2024-05-16 13:38:27 +00:00
|
|
|
body: normalize_tree(lambda.body),
|
|
|
|
});
|
|
|
|
ExpressionKind::Lambda(lambda)
|
2023-03-07 10:14:00 +00:00
|
|
|
}
|
2023-02-12 12:44:33 +00:00
|
|
|
ExpressionKind::AliasExpanded(_, subst) => normalize_tree(*subst).kind,
|
2023-02-11 11:24:26 +00:00
|
|
|
};
|
|
|
|
ExpressionNode {
|
|
|
|
kind: normalized_kind,
|
|
|
|
span: empty_span(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_tree_eq() {
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
normalize_tree(parse_template(r#" commit_id.short(1 ) ++ description"#).unwrap()),
|
|
|
|
normalize_tree(parse_template(r#"commit_id.short( 1 )++(description)"#).unwrap()),
|
2023-02-11 11:24:26 +00:00
|
|
|
);
|
|
|
|
assert_ne!(
|
|
|
|
normalize_tree(parse_template(r#" "ab" "#).unwrap()),
|
2023-02-28 11:30:57 +00:00
|
|
|
normalize_tree(parse_template(r#" "a" ++ "b" "#).unwrap()),
|
2023-02-11 11:24:26 +00:00
|
|
|
);
|
|
|
|
assert_ne!(
|
2023-02-28 11:30:57 +00:00
|
|
|
normalize_tree(parse_template(r#" "foo" ++ "0" "#).unwrap()),
|
|
|
|
normalize_tree(parse_template(r#" "foo" ++ 0 "#).unwrap()),
|
2023-02-11 11:24:26 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-03 07:13:22 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_whitespace() {
|
|
|
|
let ascii_whitespaces: String = ('\x00'..='\x7f')
|
|
|
|
.filter(char::is_ascii_whitespace)
|
|
|
|
.collect();
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized(&format!("{ascii_whitespaces}f()")).unwrap(),
|
|
|
|
parse_normalized("f()").unwrap(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-05 10:30:12 +00:00
|
|
|
#[test]
|
|
|
|
fn test_parse_operator_syntax() {
|
|
|
|
// Operator precedence
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("!!x").unwrap(),
|
|
|
|
parse_normalized("!(!x)").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("!x.f() || !g()").unwrap(),
|
|
|
|
parse_normalized("(!(x.f())) || (!(g()))").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("x.f() || y || z").unwrap(),
|
|
|
|
parse_normalized("((x.f()) || y) || z").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("x || y && z.h()").unwrap(),
|
|
|
|
parse_normalized("x || (y && (z.h()))").unwrap(),
|
|
|
|
);
|
|
|
|
|
2024-04-03 15:25:14 +00:00
|
|
|
// Logical operator bounds more tightly than concatenation. This might
|
|
|
|
// not be so intuitive, but should be harmless.
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized(r"x && y ++ z").unwrap(),
|
|
|
|
parse_normalized(r"(x && y) ++ z").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized(r"x ++ y || z").unwrap(),
|
|
|
|
parse_normalized(r"x ++ (y || z)").unwrap(),
|
|
|
|
);
|
2024-02-05 10:30:12 +00:00
|
|
|
|
|
|
|
// Expression span
|
|
|
|
assert_eq!(parse_template(" ! x ").unwrap().span.as_str(), "! x");
|
|
|
|
assert_eq!(parse_template(" x ||y ").unwrap().span.as_str(), "x ||y");
|
|
|
|
}
|
|
|
|
|
2023-02-07 09:33:26 +00:00
|
|
|
#[test]
|
|
|
|
fn test_function_call_syntax() {
|
|
|
|
// Trailing comma isn't allowed for empty argument
|
2023-03-03 06:48:35 +00:00
|
|
|
assert!(parse_template(r#" "".first_line() "#).is_ok());
|
|
|
|
assert!(parse_template(r#" "".first_line(,) "#).is_err());
|
2023-02-07 09:33:26 +00:00
|
|
|
|
|
|
|
// Trailing comma is allowed for the last argument
|
2023-03-03 06:48:35 +00:00
|
|
|
assert!(parse_template(r#" "".contains("") "#).is_ok());
|
|
|
|
assert!(parse_template(r#" "".contains("",) "#).is_ok());
|
|
|
|
assert!(parse_template(r#" "".contains("" , ) "#).is_ok());
|
|
|
|
assert!(parse_template(r#" "".contains(,"") "#).is_err());
|
|
|
|
assert!(parse_template(r#" "".contains("",,) "#).is_err());
|
|
|
|
assert!(parse_template(r#" "".contains("" , , ) "#).is_err());
|
|
|
|
assert!(parse_template(r#" label("","") "#).is_ok());
|
|
|
|
assert!(parse_template(r#" label("","",) "#).is_ok());
|
|
|
|
assert!(parse_template(r#" label("",,"") "#).is_err());
|
2023-09-02 08:49:21 +00:00
|
|
|
|
|
|
|
// Boolean literal cannot be used as a function name
|
|
|
|
assert!(parse_template("false()").is_err());
|
|
|
|
// Function arguments can be any expression
|
|
|
|
assert!(parse_template("f(false)").is_ok());
|
2023-02-07 09:33:26 +00:00
|
|
|
}
|
|
|
|
|
2023-03-18 07:36:57 +00:00
|
|
|
#[test]
|
|
|
|
fn test_method_call_syntax() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("x.f().g()").unwrap(),
|
|
|
|
parse_normalized("(x.f()).g()").unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Expression span
|
|
|
|
assert_eq!(parse_template(" x.f() ").unwrap().span.as_str(), "x.f()");
|
|
|
|
assert_eq!(
|
|
|
|
parse_template(" x.f().g() ").unwrap().span.as_str(),
|
|
|
|
"x.f().g()",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
#[test]
|
|
|
|
fn test_lambda_syntax() {
|
2024-05-16 13:38:27 +00:00
|
|
|
fn unwrap_lambda(node: ExpressionNode<'_>) -> Box<LambdaNode<'_>> {
|
2023-03-07 10:14:00 +00:00
|
|
|
match node.kind {
|
|
|
|
ExpressionKind::Lambda(lambda) => lambda,
|
|
|
|
_ => panic!("unexpected expression: {node:?}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let lambda = unwrap_lambda(parse_template("|| a").unwrap());
|
|
|
|
assert_eq!(lambda.params.len(), 0);
|
|
|
|
assert_eq!(lambda.body.kind, ExpressionKind::Identifier("a"));
|
|
|
|
let lambda = unwrap_lambda(parse_template("|foo| a").unwrap());
|
|
|
|
assert_eq!(lambda.params.len(), 1);
|
|
|
|
let lambda = unwrap_lambda(parse_template("|foo, b| a").unwrap());
|
|
|
|
assert_eq!(lambda.params.len(), 2);
|
|
|
|
|
|
|
|
// No body
|
|
|
|
assert!(parse_template("||").is_err());
|
|
|
|
|
|
|
|
// Binding
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("|| x ++ y").unwrap(),
|
|
|
|
parse_normalized("|| (x ++ y)").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("f( || x, || y)").unwrap(),
|
|
|
|
parse_normalized("f((|| x), (|| y))").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("|| x ++ || y").unwrap(),
|
|
|
|
parse_normalized("|| (x ++ (|| y))").unwrap(),
|
|
|
|
);
|
|
|
|
|
2024-02-05 10:30:12 +00:00
|
|
|
// Lambda vs logical operator: weird, but this is type error anyway
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("x||||y").unwrap(),
|
|
|
|
parse_normalized("x || (|| y)").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_normalized("||||x").unwrap(),
|
|
|
|
parse_normalized("|| (|| x)").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
// Trailing comma
|
|
|
|
assert!(parse_template("|,| a").is_err());
|
|
|
|
assert!(parse_template("|x,| a").is_ok());
|
|
|
|
assert!(parse_template("|x , | a").is_ok());
|
|
|
|
assert!(parse_template("|,x| a").is_err());
|
|
|
|
assert!(parse_template("| x,y,| a").is_ok());
|
|
|
|
assert!(parse_template("|x,,y| a").is_err());
|
|
|
|
|
|
|
|
// Formal parameter can't be redefined
|
|
|
|
assert_eq!(
|
|
|
|
parse_template("|x, x| a").unwrap_err().kind,
|
|
|
|
TemplateParseErrorKind::RedefinedFunctionParameter
|
|
|
|
);
|
2023-09-02 08:49:21 +00:00
|
|
|
|
|
|
|
// Boolean literal cannot be used as a parameter name
|
|
|
|
assert!(parse_template("|false| a").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_keyword_literal() {
|
|
|
|
assert_eq!(parse_into_kind("false"), Ok(ExpressionKind::Boolean(false)));
|
|
|
|
assert_eq!(parse_into_kind("(true)"), Ok(ExpressionKind::Boolean(true)));
|
|
|
|
// Keyword literals are case sensitive
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind("False"),
|
|
|
|
Ok(ExpressionKind::Identifier("False")),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind("tRue"),
|
|
|
|
Ok(ExpressionKind::Identifier("tRue")),
|
|
|
|
);
|
2023-03-07 10:14:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 06:41:12 +00:00
|
|
|
#[test]
|
|
|
|
fn test_string_literal() {
|
|
|
|
// "\<char>" escapes
|
|
|
|
assert_eq!(
|
2023-09-13 17:50:12 +00:00
|
|
|
parse_into_kind(r#" "\t\r\n\"\\\0" "#),
|
|
|
|
Ok(ExpressionKind::String("\t\r\n\"\\\0".to_owned())),
|
2023-03-03 06:41:12 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Invalid "\<char>" escape
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(r#" "\y" "#),
|
|
|
|
Err(TemplateParseErrorKind::SyntaxError),
|
|
|
|
);
|
2024-04-17 12:17:40 +00:00
|
|
|
|
|
|
|
// Single-quoted raw string
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(r#" '' "#),
|
|
|
|
Ok(ExpressionKind::String("".to_owned())),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(r#" 'a\n' "#),
|
|
|
|
Ok(ExpressionKind::String(r"a\n".to_owned())),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(r#" '\' "#),
|
|
|
|
Ok(ExpressionKind::String(r"\".to_owned())),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(r#" '"' "#),
|
|
|
|
Ok(ExpressionKind::String(r#"""#.to_owned())),
|
|
|
|
);
|
2023-03-03 06:41:12 +00:00
|
|
|
}
|
|
|
|
|
2023-02-04 12:36:11 +00:00
|
|
|
#[test]
|
|
|
|
fn test_integer_literal() {
|
2023-03-03 06:48:35 +00:00
|
|
|
assert_eq!(parse_into_kind("0"), Ok(ExpressionKind::Integer(0)));
|
|
|
|
assert_eq!(parse_into_kind("(42)"), Ok(ExpressionKind::Integer(42)));
|
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind("00"),
|
|
|
|
Err(TemplateParseErrorKind::SyntaxError),
|
|
|
|
);
|
2023-02-04 12:36:11 +00:00
|
|
|
|
2023-03-03 06:48:35 +00:00
|
|
|
assert_eq!(
|
|
|
|
parse_into_kind(&format!("{}", i64::MAX)),
|
|
|
|
Ok(ExpressionKind::Integer(i64::MAX)),
|
|
|
|
);
|
|
|
|
assert_matches!(
|
|
|
|
parse_into_kind(&format!("{}", (i64::MAX as u64) + 1)),
|
2024-03-29 14:00:07 +00:00
|
|
|
Err(TemplateParseErrorKind::Expression(_))
|
2023-03-03 06:48:35 +00:00
|
|
|
);
|
2023-02-04 12:36:11 +00:00
|
|
|
}
|
2023-02-12 08:21:06 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_alias_decl() {
|
|
|
|
let mut aliases_map = TemplateAliasesMap::new();
|
|
|
|
aliases_map.insert("sym", r#""is symbol""#).unwrap();
|
|
|
|
aliases_map.insert("func(a)", r#""is function""#).unwrap();
|
|
|
|
|
|
|
|
let (id, defn) = aliases_map.get_symbol("sym").unwrap();
|
2024-05-17 08:27:52 +00:00
|
|
|
assert_eq!(id, AliasId::Symbol("sym"));
|
2023-02-12 08:21:06 +00:00
|
|
|
assert_eq!(defn, r#""is symbol""#);
|
|
|
|
|
|
|
|
let (id, params, defn) = aliases_map.get_function("func").unwrap();
|
2024-05-17 08:27:52 +00:00
|
|
|
assert_eq!(id, AliasId::Function("func"));
|
2023-02-12 08:21:06 +00:00
|
|
|
assert_eq!(params, ["a"]);
|
|
|
|
assert_eq!(defn, r#""is function""#);
|
|
|
|
|
|
|
|
// Formal parameter 'a' can't be redefined
|
|
|
|
assert_eq!(
|
|
|
|
aliases_map.insert("f(a, a)", r#""""#).unwrap_err().kind,
|
|
|
|
TemplateParseErrorKind::RedefinedFunctionParameter
|
|
|
|
);
|
|
|
|
|
2023-09-02 08:49:21 +00:00
|
|
|
// Boolean literal cannot be used as a symbol, function, or parameter name
|
|
|
|
assert!(aliases_map.insert("false", r#"""#).is_err());
|
|
|
|
assert!(aliases_map.insert("true()", r#"""#).is_err());
|
|
|
|
assert!(aliases_map.insert("f(false)", r#"""#).is_err());
|
|
|
|
|
2023-02-12 08:21:06 +00:00
|
|
|
// Trailing comma isn't allowed for empty parameter
|
|
|
|
assert!(aliases_map.insert("f(,)", r#"""#).is_err());
|
|
|
|
// Trailing comma is allowed for the last parameter
|
|
|
|
assert!(aliases_map.insert("g(a,)", r#"""#).is_ok());
|
|
|
|
assert!(aliases_map.insert("h(a , )", r#"""#).is_ok());
|
|
|
|
assert!(aliases_map.insert("i(,a)", r#"""#).is_err());
|
|
|
|
assert!(aliases_map.insert("j(a,,)", r#"""#).is_err());
|
|
|
|
assert!(aliases_map.insert("k(a , , )", r#"""#).is_err());
|
|
|
|
assert!(aliases_map.insert("l(a,b,)", r#"""#).is_ok());
|
|
|
|
assert!(aliases_map.insert("m(a,,b)", r#"""#).is_err());
|
|
|
|
}
|
2023-02-12 09:41:53 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_expand_symbol_alias() {
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("AB", "a ++ b")])
|
|
|
|
.parse_normalized("AB ++ c")
|
2023-02-12 09:41:53 +00:00
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("(a ++ b) ++ c").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("AB", "a ++ b")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("if(AB, label(c, AB))")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("if((a ++ b), label(c, (a ++ b)))").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Multi-level substitution.
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("A", "BC"), ("BC", "b ++ C"), ("C", "c")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("A")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("b ++ c").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
2024-02-05 10:30:12 +00:00
|
|
|
// Operator expression can be expanded in concatenation.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("AB", "a || b")])
|
|
|
|
.parse_normalized("AB ++ c")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("(a || b) ++ c").unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Operands should be expanded.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a"), ("B", "b")])
|
|
|
|
.parse_normalized("A || !B")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("a || !b").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-02-12 09:41:53 +00:00
|
|
|
// Method receiver and arguments should be expanded.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a")])
|
|
|
|
.parse_normalized("A.f()")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("a.f()").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a"), ("B", "b")])
|
|
|
|
.parse_normalized("x.f(A, B)")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("x.f(a, b)").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
// Lambda expression body should be expanded.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a")]).parse_normalized("|| A").unwrap(),
|
|
|
|
parse_normalized("|| a").unwrap(),
|
|
|
|
);
|
|
|
|
// No matter if 'A' is a formal parameter. Alias substitution isn't scoped.
|
|
|
|
// If we don't like this behavior, maybe we can turn off alias substitution
|
|
|
|
// for lambda parameters.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a ++ b")])
|
|
|
|
.parse_normalized("|A| A")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("|A| (a ++ b)").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-02-12 09:41:53 +00:00
|
|
|
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "A")]).parse("A").unwrap_err().kind,
|
|
|
|
TemplateParseErrorKind::BadAliasExpansion("A".to_owned()),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("A", "B"), ("B", "b ++ C"), ("C", "c ++ B")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse("A")
|
|
|
|
.unwrap_err()
|
|
|
|
.kind,
|
|
|
|
TemplateParseErrorKind::BadAliasExpansion("A".to_owned()),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Error in alias definition.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A", "a(")]).parse("A").unwrap_err().kind,
|
|
|
|
TemplateParseErrorKind::BadAliasExpansion("A".to_owned()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_expand_function_alias() {
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F( )", "a")])
|
|
|
|
.parse_normalized("F()")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("a").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F( x )", "x")])
|
|
|
|
.parse_normalized("F(a)")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("a").unwrap(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("F( x, y )", "x ++ y")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("F(a, b)")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("a ++ b").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Arguments should be resolved in the current scope.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F(x,y)", "if(x, y)")])
|
2023-02-28 11:30:57 +00:00
|
|
|
.parse_normalized("F(a ++ y, b ++ x)")
|
2023-02-12 09:41:53 +00:00
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("if((a ++ y), (b ++ x))").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
2023-02-28 11:30:57 +00:00
|
|
|
// F(a) -> if(G(a), y) -> if((x ++ a), y)
|
2023-02-12 09:41:53 +00:00
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("F(x)", "if(G(x), y)"), ("G(y)", "x ++ y")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("F(a)")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("if((x ++ a), y)").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
2023-02-28 11:30:57 +00:00
|
|
|
// F(G(a)) -> F(x ++ a) -> if(G(x ++ a), y) -> if((x ++ (x ++ a)), y)
|
2023-02-12 09:41:53 +00:00
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("F(x)", "if(G(x), y)"), ("G(y)", "x ++ y")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("F(G(a))")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("if((x ++ (x ++ a)), y)").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Function parameter should precede the symbol alias.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F(X)", "X"), ("X", "x")])
|
2023-02-28 11:30:57 +00:00
|
|
|
.parse_normalized("F(a) ++ X")
|
2023-02-12 09:41:53 +00:00
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("a ++ x").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Function parameter shouldn't be expanded in symbol alias.
|
|
|
|
assert_eq!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("F(x)", "x ++ A"), ("A", "x")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse_normalized("F(a)")
|
|
|
|
.unwrap(),
|
2023-02-28 11:30:57 +00:00
|
|
|
parse_normalized("a ++ x").unwrap(),
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Function and symbol aliases reside in separate namespaces.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("A()", "A"), ("A", "a")])
|
|
|
|
.parse_normalized("A()")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("a").unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Method call shouldn't be substituted by function alias.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F()", "f()")])
|
|
|
|
.parse_normalized("x.F()")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("x.F()").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-03-07 10:14:00 +00:00
|
|
|
// Formal parameter shouldn't be substituted by alias parameter, but
|
|
|
|
// the expression should be substituted.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F(x)", "|x| x")])
|
|
|
|
.parse_normalized("F(a ++ b)")
|
|
|
|
.unwrap(),
|
|
|
|
parse_normalized("|x| (a ++ b)").unwrap(),
|
|
|
|
);
|
|
|
|
|
2023-02-12 09:41:53 +00:00
|
|
|
// Invalid number of arguments.
|
2023-03-15 03:22:41 +00:00
|
|
|
assert_matches!(
|
2023-02-12 09:41:53 +00:00
|
|
|
with_aliases([("F()", "x")]).parse("F(a)").unwrap_err().kind,
|
2023-03-15 03:22:41 +00:00
|
|
|
TemplateParseErrorKind::InvalidArguments { .. }
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
2023-03-15 03:22:41 +00:00
|
|
|
assert_matches!(
|
2023-02-12 09:41:53 +00:00
|
|
|
with_aliases([("F(x)", "x")]).parse("F()").unwrap_err().kind,
|
2023-03-15 03:22:41 +00:00
|
|
|
TemplateParseErrorKind::InvalidArguments { .. }
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
2023-03-15 03:22:41 +00:00
|
|
|
assert_matches!(
|
2023-02-28 11:30:57 +00:00
|
|
|
with_aliases([("F(x,y)", "x ++ y")])
|
2023-02-12 09:41:53 +00:00
|
|
|
.parse("F(a,b,c)")
|
|
|
|
.unwrap_err()
|
|
|
|
.kind,
|
2023-03-15 03:22:41 +00:00
|
|
|
TemplateParseErrorKind::InvalidArguments { .. }
|
2023-02-12 09:41:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
|
|
|
|
assert_eq!(
|
|
|
|
with_aliases([("F(x)", "G(x)"), ("G(x)", "H(x)"), ("H(x)", "F(x)")])
|
|
|
|
.parse("F(a)")
|
|
|
|
.unwrap_err()
|
|
|
|
.kind,
|
|
|
|
TemplateParseErrorKind::BadAliasExpansion("F()".to_owned()),
|
|
|
|
);
|
|
|
|
}
|
2023-02-04 12:36:11 +00:00
|
|
|
}
|