difftreelog
refactor only keep used spans in IR
in: master
12 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -730,6 +730,7 @@
version = "0.5.0-pre97"
dependencies = [
"insta",
+ "jrsonnet-gcmodule",
"jrsonnet-ir",
"peg",
]
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -38,7 +38,7 @@
Self::new(RangeArray::empty())
}
- pub fn expr(ctx: Context, exprs: Rc<Vec<Spanned<Expr>>>) -> Self {
+ pub fn expr(ctx: Context, exprs: Rc<Vec<Expr>>) -> Self {
Self::new(ExprArray::new(ctx, exprs))
}
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -118,11 +118,11 @@
#[derive(Debug, Trace, Clone)]
pub struct ExprArray {
ctx: Context,
- src: Rc<Vec<Spanned<Expr>>>,
+ src: Rc<Vec<Expr>>,
cached: Cc<RefCell<Vec<ArrayThunk>>>,
}
impl ExprArray {
- pub fn new(ctx: Context, src: Rc<Vec<Spanned<Expr>>>) -> Self {
+ pub fn new(ctx: Context, src: Rc<Vec<Expr>>) -> Self {
Self {
ctx,
cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),
crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -139,7 +139,7 @@
if let Expr::Str(s) = &***v {
out.0.push(Import {
path: ResolvePathOwned::Str(s.to_string()),
- expression: matches!(&**expr, Expr::Import(ImportKind::Normal, _)),
+ expression: todo!(),
});
}
// Non-string import will fail in runtime
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -53,7 +53,7 @@
Expr::Str(_)
| Expr::Num(_)
| Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,
- Expr::Arr(a) => a.iter().all(|e| is_trivial(&**e)),
+ Expr::Arr(a) => a.iter().all(|e| is_trivial(&*e)),
_ => false,
}
}
@@ -71,7 +71,7 @@
}
Val::Arr(ArrValue::eager(
n.iter()
- .map(|e| evaluate_trivial(&**e))
+ .map(|e| evaluate_trivial(&*e))
.map(|e| e.expect("checked trivial"))
.collect(),
))
@@ -80,12 +80,7 @@
})
}
-pub fn evaluate_method(
- ctx: Context,
- name: IStr,
- params: ExprParams,
- body: Rc<Spanned<Expr>>,
-) -> Val {
+pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {
Val::Func(FuncVal::Normal(Cc::new(FuncDesc {
name,
ctx,
@@ -97,18 +92,21 @@
pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {
Ok(match field_name {
FieldName::Fixed(n) => Some(n.clone()),
- FieldName::Dyn(expr) => in_frame(
- CallLocation::new(&expr.span()),
- || "evaluating field name".to_string(),
- || {
- let value = evaluate(ctx, expr)?;
- if matches!(value, Val::Null) {
- Ok(None)
- } else {
- Ok(Some(IStr::from_untyped(value)?))
- }
- },
- )?,
+ FieldName::Dyn(expr) => {
+ // FIXME: Span
+ let value = evaluate(ctx, expr)?;
+ if matches!(value, Val::Null) {
+ None
+ } else {
+ Some(IStr::from_untyped(value)?)
+ }
+ } //
+ // in_frame(
+ // CallLocation::new(&expr.span()),
+ // || "evaluating field name".to_string(),
+ // || {
+ // },
+ // )?,
})
}
@@ -119,46 +117,48 @@
) -> Result<()> {
match specs.first() {
None => callback(ctx)?,
- Some(CompSpec::IfSpec(IfSpecData(cond))) => {
+ Some(CompSpec::IfSpec(Spanned(IfSpecData(cond), _))) => {
if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {
evaluate_comp(ctx, &specs[1..], callback)?;
}
}
- Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {
- Val::Arr(list) => {
- for item in list.iter_lazy() {
- let fctx = Pending::new();
- let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
- destruct(var, item, fctx.clone(), &mut new_bindings)?;
- let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
+ Some(CompSpec::ForSpec(Spanned(ForSpecData(var, expr), _))) => {
+ match evaluate(ctx.clone(), expr)? {
+ Val::Arr(list) => {
+ for item in list.iter_lazy() {
+ let fctx = Pending::new();
+ let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
+ destruct(var, item, fctx.clone(), &mut new_bindings)?;
+ let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
- evaluate_comp(ctx, &specs[1..], callback)?;
+ evaluate_comp(ctx, &specs[1..], callback)?;
+ }
}
- }
- #[cfg(feature = "exp-object-iteration")]
- Val::Obj(obj) => {
- for field in obj.fields(
- // TODO: Should there be ability to preserve iteration order?
- #[cfg(feature = "exp-preserve-order")]
- false,
- ) {
- let fctx = Pending::new();
- let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
- let obj = obj.clone();
- let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
- Thunk::evaluated(Val::string(field.clone())),
- Thunk!(move || obj.get(field).transpose().expect(
- "field exists, as field name was obtained from object.fields()",
- )),
- ])));
- destruct(var, value, fctx.clone(), &mut new_bindings)?;
- let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
+ #[cfg(feature = "exp-object-iteration")]
+ Val::Obj(obj) => {
+ for field in obj.fields(
+ // TODO: Should there be ability to preserve iteration order?
+ #[cfg(feature = "exp-preserve-order")]
+ false,
+ ) {
+ let fctx = Pending::new();
+ let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
+ let obj = obj.clone();
+ let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
+ Thunk::evaluated(Val::string(field.clone())),
+ Thunk!(move || obj.get(field).transpose().expect(
+ "field exists, as field name was obtained from object.fields()",
+ )),
+ ])));
+ destruct(var, value, fctx.clone(), &mut new_bindings)?;
+ let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
- evaluate_comp(ctx, &specs[1..], callback)?;
+ evaluate_comp(ctx, &specs[1..], callback)?;
+ }
}
+ _ => bail!(InComprehensionCanOnlyIterateOverArray),
}
- _ => bail!(InComprehensionCanOnlyIterateOverArray),
- },
+ }
}
Ok(())
}
@@ -221,7 +221,7 @@
#[derive(Trace)]
struct UnboundValue<B: Trace> {
uctx: B,
- value: Rc<Spanned<Expr>>,
+ value: Rc<Expr>,
name: IStr,
}
impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {
@@ -235,7 +235,8 @@
.field(name.clone())
.with_add(*plus)
.with_visibility(*visibility)
- .with_location(value.span())
+ // FIXME
+ // .with_location(value.span())
.bindable(UnboundValue {
uctx,
value: value.clone(),
@@ -251,7 +252,7 @@
#[derive(Trace)]
struct UnboundMethod<B: Trace> {
uctx: B,
- value: Rc<Spanned<Expr>>,
+ value: Rc<Expr>,
params: ExprParams,
name: IStr,
}
@@ -270,7 +271,7 @@
builder
.field(name.clone())
.with_visibility(*visibility)
- .with_location(value.span())
+ // .with_location(value.span())
.bindable(UnboundMethod {
uctx,
value: value.clone(),
@@ -337,7 +338,7 @@
pub fn evaluate_apply(
ctx: Context,
- value: &Spanned<Expr>,
+ value: &Expr,
args: &ArgsDesc,
loc: CallLocation<'_>,
tailstrict: bool,
@@ -379,16 +380,16 @@
Ok(())
}
-pub fn evaluate_named_param(ctx: Context, expr: &Spanned<Expr>, name: ParamName) -> Result<Val> {
+pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {
match name {
ParamName::Named(name) => evaluate_named(ctx, expr, name),
ParamName::Unnamed => evaluate(ctx, expr),
}
}
-pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {
+pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {
use Expr::*;
- Ok(match &**expr {
+ Ok(match &*expr {
Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),
_ => evaluate(ctx, expr)?,
})
@@ -417,7 +418,7 @@
// because the standalone super literal is not supported, that is because in other
// implementations `in super` treated differently from `in smth_else`.
BinaryOp(bin)
- if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))
+ if matches!(&bin.rhs, Expr::Literal(LiteralType::Super))
&& bin.op == BinaryOpType::In =>
{
let sup_this = ctx.try_sup_this()?;
@@ -433,12 +434,12 @@
UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,
Var(name) => in_frame(
CallLocation::new(&name.span()),
- || format!("local <{name}> access"),
+ || format!("local <{}> access", &**name),
|| ctx.binding((**name).clone())?.evaluate(),
)?,
Index { indexable, parts } => ensure_sufficient_stack(|| {
let mut parts = parts.iter();
- let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {
+ let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {
let part = parts.next().expect("at least part should exist");
// sup_this existence check might also be skipped here for null-coalesce...
// But I believe this might cause errors.
@@ -463,7 +464,7 @@
let name = name.into_flat();
match sup_this
.get_super(name.clone())
- .with_description_src(&part.value, || format!("field <{name}> access"))?
+ .with_description_src(&part.span, || format!("field <{name}> access"))?
{
Some(v) => v,
#[cfg(feature = "exp-null-coaelse")]
@@ -485,7 +486,7 @@
indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {
(Val::Obj(v), Val::Str(key)) => match v
.get(key.clone().into_flat())
- .with_description_src(&part.value, || format!("field <{key}> access"))?
+ .with_description_src(&part.span, || format!("field <{key}> access"))?
{
Some(v) => v,
#[cfg(feature = "exp-null-coaelse")]
@@ -497,7 +498,7 @@
key.clone().into_flat(),
suggestions,
)))
- .with_description_src(&part.value, || format!("field <{key}> access"));
+ .with_description_src(&part.span, || format!("field <{key}> access"));
}
},
(Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(
@@ -605,17 +606,21 @@
evaluate_assert(ctx.clone(), &assert.assert)?;
evaluate(ctx, &assert.rest)?
}
- ErrorStmt(e) => in_frame(
- CallLocation::new(&e.span()),
+ ErrorStmt(s, e) => in_frame(
+ CallLocation::new(&s),
|| "error statement".to_owned(),
|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
)?,
IfElse(if_else) => {
- if in_frame(
- CallLocation::new(&if_else.cond.0.span()),
- || "if condition".to_owned(),
- || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),
- )? {
+ if
+ // FIXME
+ //in_frame(
+ // CallLocation::new(&if_else.cond.0.span()),
+ // || "if condition".to_owned(),
+ // ||
+ bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?)?
+ // )?
+ {
evaluate(ctx, &if_else.cond_then)?
} else {
match &if_else.cond_else {
@@ -626,14 +631,13 @@
}
Slice(slice) => {
fn parse_idx<T: Typed + FromUntyped>(
- loc: CallLocation<'_>,
ctx: Context,
expr: Option<&Spanned<Expr>>,
desc: &'static str,
) -> Result<Option<T>> {
if let Some(value) = expr {
Ok(in_frame(
- loc,
+ CallLocation::new(&value.span()),
|| format!("slice {desc}"),
|| <Option<T>>::from_untyped(evaluate(ctx, value)?),
)?)
@@ -643,24 +647,23 @@
}
let indexable = evaluate(ctx.clone(), &slice.value)?;
- let loc = CallLocation::new(&loc);
- let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;
- let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;
- let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;
+ let start = parse_idx(ctx.clone(), slice.slice.start.as_ref(), "start")?;
+ let end = parse_idx(ctx.clone(), slice.slice.end.as_ref(), "end")?;
+ let step = parse_idx(ctx, slice.slice.step.as_ref(), "step")?;
IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?
}
Import(kind, path) => {
- let Expr::Str(path) = &***path else {
+ let Expr::Str(path) = &**path else {
bail!("computed imports are not supported")
};
- let tmp = loc.clone().0;
with_state(|s| {
- let resolved_path = s.resolve_from(tmp.source_path(), path)?;
- Ok(match kind {
+ let span = kind.span();
+ let resolved_path = s.resolve_from(span.0.source_path(), path)?;
+ Ok(match &**kind {
ImportKind::Normal => in_frame(
- CallLocation::new(&loc),
+ CallLocation::new(&span),
|| format!("import {:?}", path.clone()),
|| s.import_resolved(resolved_path),
)?,
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -147,9 +147,9 @@
pub fn evaluate_binary_op_special(
ctx: Context,
- a: &Spanned<Expr>,
+ a: &Expr,
op: BinaryOpType,
- b: &Spanned<Expr>,
+ b: &Expr,
) -> Result<Val> {
use BinaryOpType::*;
use Val::*;
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -3,8 +3,8 @@
use educe::Educe;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
+use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned};
pub use jrsonnet_macros::builtin;
-use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned};
use self::{
builtin::{Builtin, StaticBuiltin},
@@ -71,7 +71,7 @@
/// Function parameter definition
pub params: ExprParams,
/// Function body
- pub body: Rc<Spanned<Expr>>,
+ pub body: Rc<Expr>,
}
impl FuncDesc {
/// Create body context, but fill arguments without defaults with lazy error
@@ -256,7 +256,7 @@
#[cfg(feature = "exp-destruct")]
_ => return false,
};
- **desc.body == Expr::Var(id.clone())
+ matches!(&*desc.body, Expr::Var(v) if &**v == id)
}
_ => false,
}
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -2,7 +2,7 @@
use jrsonnet_ir::{
function::{FunctionSignature, ParamName},
- ArgsDesc, Expr, ExprParams, Spanned,
+ ArgsDesc, Expr, ExprParams,
};
use rustc_hash::FxHashMap;
@@ -15,7 +15,7 @@
Context, Pending, Thunk, Val,
};
-fn eval_arg(ctx: Context, arg: &Rc<Spanned<Expr>>, tailstrict: bool) -> Result<Thunk<Val>> {
+fn eval_arg(ctx: Context, arg: &Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {
if tailstrict {
Ok(Thunk::evaluated(evaluate(ctx, arg)?))
} else {
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -5,7 +5,7 @@
extern crate self as jrsonnet_evaluator;
mod arr;
-pub mod async_import;
+// pub mod async_import;
mod ctx;
mod dynamic;
pub mod error;
@@ -187,7 +187,7 @@
struct FileData {
string: Option<IStr>,
bytes: Option<IBytes>,
- parsed: Option<Rc<Spanned<Expr>>>,
+ parsed: Option<Rc<Expr>>,
evaluated: Option<Val>,
evaluating: bool,
crates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -17,7 +17,7 @@
/// {fixed: 2}
Fixed(IStr),
/// {["dyn"+"amic"]: 3}
- Dyn(Spanned<Expr>),
+ Dyn(Expr),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
@@ -46,7 +46,7 @@
pub plus: bool,
pub params: Option<ExprParams>,
pub visibility: Visibility,
- pub value: Rc<Spanned<Expr>>,
+ pub value: Rc<Expr>,
}
#[derive(Debug, PartialEq, Acyclic)]
@@ -152,7 +152,7 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct ExprParam {
pub destruct: Destruct,
- pub default: Option<Rc<Spanned<Expr>>>,
+ pub default: Option<Rc<Expr>>,
}
/// Defined function parameters
@@ -194,11 +194,11 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct ArgsDesc {
- pub unnamed: Vec<Rc<Spanned<Expr>>>,
- pub named: Vec<(IStr, Rc<Spanned<Expr>>)>,
+ pub unnamed: Vec<Rc<Expr>>,
+ pub named: Vec<(IStr, Rc<Expr>)>,
}
impl ArgsDesc {
- pub fn new(unnamed: Vec<Rc<Spanned<Expr>>>, named: Vec<(IStr, Rc<Spanned<Expr>>)>) -> Self {
+ pub fn new(unnamed: Vec<Rc<Expr>>, named: Vec<(IStr, Rc<Expr>)>) -> Self {
Self { unnamed, named }
}
}
@@ -277,12 +277,12 @@
pub enum BindSpec {
Field {
into: Destruct,
- value: Rc<Spanned<Expr>>,
+ value: Rc<Expr>,
},
Function {
name: IStr,
params: ExprParams,
- value: Rc<Spanned<Expr>>,
+ value: Rc<Expr>,
},
}
impl BindSpec {
@@ -295,15 +295,15 @@
}
#[derive(Debug, PartialEq, Acyclic)]
-pub struct IfSpecData(pub Spanned<Expr>);
+pub struct IfSpecData(pub Expr);
#[derive(Debug, PartialEq, Acyclic)]
-pub struct ForSpecData(pub Destruct, pub Spanned<Expr>);
+pub struct ForSpecData(pub Destruct, pub Expr);
#[derive(Debug, PartialEq, Acyclic)]
pub enum CompSpec {
- IfSpec(IfSpecData),
- ForSpec(ForSpecData),
+ IfSpec(Spanned<IfSpecData>),
+ ForSpec(Spanned<ForSpecData>),
}
#[derive(Debug, PartialEq, Acyclic)]
@@ -346,14 +346,14 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct AssertExpr {
pub assert: AssertStmt,
- pub rest: Spanned<Expr>,
+ pub rest: Expr,
}
#[derive(Debug, PartialEq, Acyclic)]
pub struct BinaryOp {
- pub lhs: Spanned<Expr>,
+ pub lhs: Expr,
pub op: BinaryOpType,
- pub rhs: Spanned<Expr>,
+ pub rhs: Expr,
}
#[derive(Debug, PartialEq, Acyclic)]
@@ -366,13 +366,13 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct IfElse {
pub cond: IfSpecData,
- pub cond_then: Spanned<Expr>,
- pub cond_else: Option<Spanned<Expr>>,
+ pub cond_then: Expr,
+ pub cond_else: Option<Expr>,
}
#[derive(Debug, PartialEq, Acyclic)]
pub struct Slice {
- pub value: Spanned<Expr>,
+ pub value: Expr,
pub slice: SliceDesc,
}
@@ -389,7 +389,7 @@
Var(Spanned<IStr>),
/// Array of expressions: [1, 2, "Hello"]
- Arr(Rc<Vec<Spanned<Expr>>>),
+ Arr(Rc<Vec<Expr>>),
/// Array comprehension:
/// ```jsonnet
/// ingredients: [
@@ -401,35 +401,35 @@
/// ]
/// ],
/// ```
- ArrComp(Rc<Spanned<Expr>>, Vec<CompSpec>),
+ ArrComp(Rc<Expr>, Vec<CompSpec>),
/// Object: {a: 2}
Obj(ObjBody),
/// Object extension: var1 {b: 2}
- ObjExtend(Rc<Spanned<Expr>>, ObjBody),
+ ObjExtend(Rc<Expr>, ObjBody),
/// -2
- UnaryOp(UnaryOpType, Box<Spanned<Expr>>),
+ UnaryOp(UnaryOpType, Box<Expr>),
/// 2 - 2
BinaryOp(Box<BinaryOp>),
/// assert 2 == 2 : "Math is broken"
AssertExpr(Rc<AssertExpr>),
/// local a = 2; { b: a }
- LocalExpr(Vec<BindSpec>, Box<Spanned<Expr>>),
+ LocalExpr(Vec<BindSpec>, Box<Expr>),
/// import* "hello"
- Import(ImportKind, Box<Spanned<Expr>>),
+ Import(Spanned<ImportKind>, Box<Expr>),
/// error "I'm broken"
- ErrorStmt(Box<Spanned<Expr>>),
+ ErrorStmt(Span, Box<Expr>),
/// a(b, c)
- Apply(Box<Spanned<Expr>>, Spanned<ArgsDesc>, bool),
+ Apply(Box<Expr>, Spanned<ArgsDesc>, bool),
/// a[b], a.b, a?.b
Index {
- indexable: Box<Spanned<Expr>>,
+ indexable: Box<Expr>,
parts: Vec<IndexPart>,
},
/// function(x) x
- Function(ExprParams, Rc<Spanned<Expr>>),
+ Function(ExprParams, Rc<Expr>),
/// if true == false then 1 else 2
IfElse(Box<IfElse>),
Slice(Box<Slice>),
@@ -437,7 +437,8 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct IndexPart {
- pub value: Spanned<Expr>,
+ pub span: Span,
+ pub value: Expr,
#[cfg(feature = "exp-null-coaelse")]
pub null_coaelse: bool,
}
@@ -461,7 +462,7 @@
}
#[derive(Clone, PartialEq, Acyclic)]
-pub struct Spanned<T: Acyclic>(T, Span);
+pub struct Spanned<T: Acyclic>(pub T, pub Span);
impl<T: Acyclic> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
crates/jrsonnet-peg-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-peg-parser/Cargo.toml
+++ b/crates/jrsonnet-peg-parser/Cargo.toml
@@ -7,6 +7,7 @@
version.workspace = true
[dependencies]
+jrsonnet-gcmodule.workspace = true
jrsonnet-ir.workspace = true
peg.workspace = true
crates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth1use jrsonnet_ir::{2 BinaryOp, Expr, ExprParams, IStr, IndexPart, Member, Slice, SliceDesc, Source, Span, Spanned,3 ExprParam, ArgsDesc, AssertExpr, ImportKind, LiteralType, IfElse, CompSpec, ForSpecData, IfSpecData, ObjMembers, ObjBody,4 ObjComp, FieldMember, Visibility, FieldName, unescape, AssertStmt, BindSpec, Destruct, DestructRest,5};6use peg::parser;7use std::rc::Rc;89pub struct ParserSettings {10 pub source: Source,11}1213macro_rules! expr_bin {14 ($a:ident $op:ident $b:ident) => {15 Expr::BinaryOp(Box::new(BinaryOp {16 lhs: $a,17 op: $op,18 rhs: $b,19 }))20 };21}22macro_rules! expr_un {23 ($op:ident $a:ident) => {24 Expr::UnaryOp($op, Box::new($a))25 };26}2728parser! {29 grammar jsonnet_parser() for str {30 use peg::ParseLiteral;3132 rule eof() = quiet!{![_]} / expected!("<eof>")33 rule eol() = "\n" / eof()3435 /// Standard C-like comments36 rule comment()37 = "//" (!eol()[_])* eol()38 / "/*" (!("*/")[_])* "*/"39 / "#" (!eol()[_])* eol()4041 rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")42 rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4344 /// For comma-delimited elements45 rule comma() = quiet!{_ "," _} / expected!("<comma>")46 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}47 rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}48 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']49 /// Sequence of digits50 rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a }51 /// Number in scientific notation format52 rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "<number>") }} / expected!("<number>")5354 /// Reserved word followed by any non-alphanumberic55 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()56 rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")) { v.into() }5758 rule keyword(id: &'static str) -> ()59 = ##parse_string_literal(id) end_of_ident()6061 pub rule param(s: &ParserSettings) -> ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { ExprParam { destruct, default: expr.map(Rc::new) } }62 pub rule params(s: &ParserSettings) -> ExprParams63 = params:param(s) ** comma() comma()? { ExprParams::new(params) }64 / { ExprParams::new(Vec::new()) }6566 pub rule arg(s: &ParserSettings) -> (Option<IStr>, Rc<Spanned<Expr>>)67 = name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("<argument name>")) expr:expr(s) {(name, Rc::new(expr))}6869 pub rule args(s: &ParserSettings) -> ArgsDesc70 = args:arg(s)**comma() comma()? {?71 let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();72 let mut unnamed = Vec::with_capacity(unnamed_count);73 let mut named = Vec::with_capacity(args.len() - unnamed_count);74 let mut named_started = false;75 for (name, value) in args {76 if let Some(name) = name {77 named_started = true;78 named.push((name, value));79 } else {80 if named_started {81 return Err("<named argument>")82 }83 unnamed.push(value);84 }85 }86 Ok(ArgsDesc::new(unnamed, named))87 }8889 pub rule destruct_rest() -> DestructRest90 = "..." into:(_ into:id() {into})? {if let Some(into) = into {91 DestructRest::Keep(into)92 } else {DestructRest::Drop}}93 pub rule destruct_array(s: &ParserSettings) -> Destruct94 = "[" _ start:destruct(s)**comma() rest:(95 comma() _ rest:destruct_rest()? end:(96 comma() end:destruct(s)**comma() (_ comma())? {end}97 / comma()? {Vec::new()}98 ) {(rest, end)}99 / comma()? {(None, Vec::new())}100 ) _ "]" {?101 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array {102 start,103 rest: rest.0,104 end: rest.1,105 });106 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")107 }108 pub rule destruct_object(s: &ParserSettings) -> Destruct109 = "{" _110 fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma()111 rest:(112 comma() rest:destruct_rest()? {rest}113 / comma()? {None}114 )115 _ "}" {?116 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object {117 fields,118 rest,119 });120 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")121 }122 pub rule destruct(s: &ParserSettings) -> Destruct123 = v:id() {Destruct::Full(v)}124 / "?" {?125 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip);126 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")127 }128 / arr:destruct_array(s) {arr}129 / obj:destruct_object(s) {obj}130131 pub rule bind(s: &ParserSettings) -> BindSpec132 = into:destruct(s) _ "=" _ value:expr(s) {BindSpec::Field{into, value: Rc::new(value)}}133 / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value: Rc::new(value)}}134135 pub rule assertion(s: &ParserSettings) -> AssertStmt136 = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { AssertStmt(cond, msg) }137138 pub rule whole_line() -> &'input str139 = str:$((!['\n'][_])* "\n") {str}140 pub rule string_block() -> String141 = "|||" chomped:"-"? (!['\n']single_whitespace())* "\n"142 empty_lines:$(['\n']*)143 prefix:[' ' | '\t']+ first_line:whole_line()144 lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*145 [' ' | '\t']*<, {prefix.len() - 1}> "|||"146 {147 let mut l = empty_lines.to_owned();148 l.push_str(first_line);149 l.extend(lines);150 if chomped.is_some() {151 debug_assert!(l.ends_with('\n'));152 l.truncate(l.len() - 1);153 }154 l155 }156157 rule hex_char()158 = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")159160 rule string_char(c: rule<()>)161 = (!['\\']!c()[_])+162 / "\\\\"163 / "\\u" hex_char() hex_char() hex_char() hex_char()164 / "\\x" hex_char() hex_char()165 / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))166 pub rule string() -> String167 = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}168 / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}169 / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}170 / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}171 / string_block() } / expected!("<string>")172173 pub rule field_name(s: &ParserSettings) -> FieldName174 = name:id() {FieldName::Fixed(name)}175 / name:string() {FieldName::Fixed(name.into())}176 / "[" _ expr:expr(s) _ "]" {FieldName::Dyn(expr)}177 pub rule visibility() -> Visibility178 = ":::" {Visibility::Unhide}179 / "::" {Visibility::Hidden}180 / ":" {Visibility::Normal}181 pub rule field(s: &ParserSettings) -> FieldMember182 = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {FieldMember{183 name,184 plus: plus.is_some(),185 params: None,186 visibility,187 value: Rc::new(value),188 }}189 / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {FieldMember{190 name,191 plus: false,192 params: Some(params),193 visibility,194 value: Rc::new(value),195 }}196 pub rule obj_local(s: &ParserSettings) -> BindSpec197 = keyword("local") _ bind:bind(s) {bind}198 pub rule member(s: &ParserSettings) -> Member199 = bind:obj_local(s) {Member::BindStmt(bind)}200 / assertion:assertion(s) {Member::AssertStmt(assertion)}201 / field:field(s) {Member::Field(field)}202 pub rule objinside(s: &ParserSettings) -> ObjBody203 = members:(member(s) ** comma()) comma()? _ compspecs:compspecs(s)? {?204 Ok(if let Some(compspecs) = compspecs {205 let mut locals = Vec::new();206 let mut field = None;207 for member in members {208 match member {209 Member::Field(field_member) => if field.replace(field_member).is_some() {210 return Err("<object comprehension can only contain one field>")211 },212 Member::BindStmt(bind_spec) => locals.push(bind_spec),213 Member::AssertStmt(assert_stmt) => return Err("<asserts are unsupported in object comprehension>"),214 }215 }216 ObjBody::ObjComp(ObjComp {217 locals: Rc::new(locals),218 field: field.map(Rc::new).ok_or("<missing object comprehension field>")?,219 compspecs220 })221 } else {222 let mut locals = Vec::new();223 let mut asserts = Vec::new();224 let mut fields = Vec::new();225 for member in members {226 match member {227 Member::Field(field_member) => fields.push(field_member),228 Member::BindStmt(bind_spec) => locals.push(bind_spec),229 Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),230 }231 }232 ObjBody::MemberList(ObjMembers {233 locals: Rc::new(locals),234 asserts: Rc::new(asserts),235 fields236 })237 })238 }239 pub rule ifspec(s: &ParserSettings) -> IfSpecData240 = keyword("if") _ expr:expr(s) {IfSpecData(expr)}241 pub rule forspec(s: &ParserSettings) -> ForSpecData242 = keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}243 rule compspec(s: &ParserSettings) -> CompSpec244 = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forspec(s) {CompSpec::ForSpec(f)}245 pub rule compspecs(s: &ParserSettings) -> Vec<CompSpec>246 = specs:compspec(s) ++ _ {?247 if !matches!(specs[0], CompSpec::ForSpec(_)) {248 return Err("<first compspec should be for>")249 }250 Ok(specs)251 }252 pub rule local_expr(s: &ParserSettings) -> Expr253 = keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) }254 pub rule string_expr(s: &ParserSettings) -> Expr255 = s:string() {Expr::Str(s.into())}256 pub rule obj_expr(s: &ParserSettings) -> Expr257 = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}258 pub rule array_expr(s: &ParserSettings) -> Expr259 = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))}260 pub rule array_comp_expr(s: &ParserSettings) -> Expr261 = "[" _ expr:expr(s) _ comma()? _ specs:(r: compspecs(s) _ {r}) "]" {262 Expr::ArrComp(Rc::new(expr), specs)263 }264 pub rule number_expr(s: &ParserSettings) -> Expr265 = n:number() {? if n.is_finite() {266 Ok(Expr::Num(n))267 } else {268 Err("!!!numbers are finite")269 }}270 pub rule var_expr(s: &ParserSettings) -> Expr271 = n:id() { Expr::Var(n) }272 pub rule id_loc(s: &ParserSettings) -> Spanned<Expr>273 = a:position!() n:id() b:position!() { Spanned::new(Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) }274 pub rule if_then_else_expr(s: &ParserSettings) -> Expr275 = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{276 cond,277 cond_then,278 cond_else,279 }))}280281 pub rule literal(s: &ParserSettings) -> Expr282 = v:(283 keyword("null") {LiteralType::Null}284 / keyword("true") {LiteralType::True}285 / keyword("false") {LiteralType::False}286 / keyword("self") {LiteralType::This}287 / keyword("$") {LiteralType::Dollar}288 / keyword("super") {LiteralType::Super}289 ) {Expr::Literal(v)}290291 rule import_kind() -> ImportKind292 = keyword("importstr") { ImportKind::Str }293 / keyword("importbin") { ImportKind::Bin }294 / keyword("import") { ImportKind::Normal }295296 pub rule expr_basic(s: &ParserSettings) -> Expr297 = literal(s)298299 / string_expr(s) / number_expr(s)300 / array_expr(s)301 / obj_expr(s)302 / array_expr(s)303 / array_comp_expr(s)304305 / kind:import_kind() _ path:expr(s) {Expr::Import(kind, Box::new(path))}306307 / var_expr(s)308 / local_expr(s)309 / if_then_else_expr(s)310311 / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))}312 / assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{313 assert, rest314 })) }315316 / keyword("error") _ expr:expr(s) { Expr::ErrorStmt(Box::new(expr)) }317318 rule slice_part(s: &ParserSettings) -> Option<Spanned<Expr>>319 = _ e:(e:expr(s) _{e})? {e}320 pub rule slice_desc(s: &ParserSettings) -> SliceDesc321 = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {322 let (end, step) = if let Some((end, step)) = pair {323 (end, step)324 }else{325 (None, None)326 };327328 SliceDesc { start, end, step }329 }330331 rule binop(x: rule<()>) -> ()332 = quiet!{ x() } / expected!("<binary op>")333 rule unaryop(x: rule<()>) -> ()334 = quiet!{ x() } / expected!("<unary op>")335336 rule ensure_null_coaelse()337 = "" {?338 #[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled");339 #[cfg(feature = "exp-null-coaelse")] Ok(())340 }341 use jrsonnet_ir::BinaryOpType::*;342 use jrsonnet_ir::UnaryOpType::*;343 rule expr(s: &ParserSettings) -> Spanned<Expr>344 = precedence! {345 "(" _ e:expr(s) _ ")" {e}346 start:position!() v:@ end:position!() { Spanned::new(v, Span(s.source.clone(), start as u32, end as u32)) }347 --348 a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}349 a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ {350 #[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b);351 unreachable!("ensure_null_coaelse will fail if feature is not enabled")352 }353 --354 a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}355 --356 a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}357 --358 a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}359 --360 a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}361 --362 a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}363 a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}364 --365 a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}366 a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}367 a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}368 a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}369 a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}370 --371 a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}372 a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}373 --374 a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}375 a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}376 --377 a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}378 a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}379 a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}380 --381 unaryop(<"+">) _ b:@ {expr_un!(Plus b)}382 unaryop(<"-">) _ b:@ {expr_un!(Minus b)}383 unaryop(<"!">) _ b:@ {expr_un!(Not b)}384 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}385 --386 value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))}387 indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}}388 a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())}389 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)}390 --391 e:expr_basic(s) {e}392 }393 pub rule index_part(s: &ParserSettings) -> IndexPart394 = n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {395 value,396 #[cfg(feature = "exp-null-coaelse")]397 null_coaelse: n.is_some(),398 }}399 / n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart {400 value,401 #[cfg(feature = "exp-null-coaelse")]402 null_coaelse: n.is_some(),403 }}404405 pub rule jsonnet(s: &ParserSettings) -> Spanned<Expr> = _ e:expr(s) _ {e}406 }407}408409pub type ParseError = peg::error::ParseError<peg::str::LineCol>;410pub fn parse(str: &str, settings: &ParserSettings) -> Result<Spanned<Expr>, ParseError> {411 jsonnet_parser::jsonnet(str, settings)412}413/// Used for importstr values414pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned<Expr> {415 let len = str.len();416 Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32))417}418419#[cfg(test)]420pub mod tests {421 use insta::assert_snapshot;422 use jrsonnet_ir::{IStr, Source};423424 use super::parse;425 use crate::ParserSettings;426427 fn parsep(s: &str) -> String {428 let v = parse(429 s,430 &ParserSettings {431 source: Source::new_virtual("<test>".into(), IStr::empty()),432 },433 )434 .unwrap();435 format!("{v:#?}")436 }437438 macro_rules! parse {439 ($s:expr) => {440 assert_snapshot!(parsep($s));441 };442 }443444 #[test]445 fn multiline_string() {446 parse!("|||\n Hello world!\n a\n|||");447 parse!("|||\n Hello world!\n a\n|||");448 parse!("|||\n\t\tHello world!\n\t\t\ta\n|||");449 parse!("|||\n Hello world!\n a\n |||");450 }451452 #[test]453 fn slice() {454 parse!("a[1:]");455 parse!("a[1::]");456 parse!("a[:1:]");457 parse!("a[::1]");458 parse!("str[:len - 1]");459 }460461 #[test]462 fn string_escaping() {463 parse!(r#""Hello, \"world\"!""#);464 parse!(r#"'Hello \'world\'!'"#);465 parse!(r#"'\\\\'"#);466 }467468 #[test]469 fn string_unescaping() {470 parse!(r#""Hello\nWorld""#);471 }472473 #[test]474 fn string_verbantim() {475 parse!(r#"@"Hello\n""World""""#);476 }477478 #[test]479 fn imports() {480 parse!("import \"hello\"");481 parse!("importstr \"garnish.txt\"");482 parse!("importbin \"garnish.bin\"");483 }484485 #[test]486 fn empty_object() {487 parse!("{}");488 }489490 #[test]491 fn basic_math() {492 parse!("2+2*2");493 parse!("2 + 2 * 2 ");494 parse!("2+(2+2*2)");495 parse!("2//comment\n+//comment\n3/*test*/*/*test*/4");496 }497498 #[test]499 fn suffix() {500 parse!("std.test");501 parse!("std(2)");502 parse!("std.test(2)");503 parse!("a[b]");504 }505506 #[test]507 fn array_comp() {508 parse!("[std.deepJoin(x) for x in arr]");509 }510511 #[test]512 fn reserved() {513 parse!("null");514 parse!("nulla");515 }516517 #[test]518 fn multiple_args_buf() {519 parse!("a(b, null_fields)");520 }521522 #[test]523 fn infix_precedence() {524 parse!("!a && !b");525 parse!("!a / !b");526 }527528 #[test]529 fn double_negation() {530 parse!("!!a");531 }532533 #[test]534 fn array_test_error() {535 parse!("[a for a in b if c for e in f]");536 }537538 #[test]539 fn missing_newline_between_comment_and_eof() {540 parse!(541 "{a:1}542543 //+213"544 );545 }546547 #[test]548 fn default_param_before_nondefault() {549 parse!("local x(foo = 'foo', bar) = null; null");550 }551552 #[test]553 fn add_location_info_to_all_sub_expressions() {554 parse!("{} { local x = 1, x: x } + {}");555 }556}1use jrsonnet_gcmodule::Acyclic;2use jrsonnet_ir::{3 unescape, ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct,4 DestructRest, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr, IfElse,5 IfSpecData, ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers, Slice,6 SliceDesc, Source, Span, Spanned, Visibility,7};8use peg::parser;9use std::rc::Rc;1011pub struct ParserSettings {12 pub source: Source,13}1415macro_rules! expr_bin {16 ($a:ident $op:ident $b:ident) => {17 Expr::BinaryOp(Box::new(BinaryOp {18 lhs: $a,19 op: $op,20 rhs: $b,21 }))22 };23}24macro_rules! expr_un {25 ($op:ident $a:ident) => {26 Expr::UnaryOp($op, Box::new($a))27 };28}2930parser! {31 grammar jsonnet_parser() for str {32 use peg::ParseLiteral;3334 rule eof() = quiet!{![_]} / expected!("<eof>")35 rule eol() = "\n" / eof()3637 /// Standard C-like comments38 rule comment()39 = "//" (!eol()[_])* eol()40 / "/*" (!("*/")[_])* "*/"41 / "#" (!eol()[_])* eol()4243 rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")44 rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4546 /// For comma-delimited elements47 rule comma() = quiet!{_ "," _} / expected!("<comma>")48 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}49 rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}50 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']51 /// Sequence of digits52 rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a }53 /// Number in scientific notation format54 rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "<number>") }} / expected!("<number>")5556 /// Reserved word followed by any non-alphanumberic57 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()58 rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")) { v.into() }5960 rule keyword(id: &'static str) -> ()61 = ##parse_string_literal(id) end_of_ident()6263 pub rule param(s: &ParserSettings) -> ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { ExprParam { destruct, default: expr.map(Rc::new) } }64 pub rule params(s: &ParserSettings) -> ExprParams65 = params:param(s) ** comma() comma()? { ExprParams::new(params) }66 / { ExprParams::new(Vec::new()) }6768 pub rule arg(s: &ParserSettings) -> (Option<IStr>, Rc<Expr>)69 = name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("<argument name>")) expr:expr(s) {(name, Rc::new(expr))}7071 pub rule args(s: &ParserSettings) -> ArgsDesc72 = args:arg(s)**comma() comma()? {?73 let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();74 let mut unnamed = Vec::with_capacity(unnamed_count);75 let mut named = Vec::with_capacity(args.len() - unnamed_count);76 let mut named_started = false;77 for (name, value) in args {78 if let Some(name) = name {79 named_started = true;80 named.push((name, value));81 } else {82 if named_started {83 return Err("<named argument>")84 }85 unnamed.push(value);86 }87 }88 Ok(ArgsDesc::new(unnamed, named))89 }9091 pub rule destruct_rest() -> DestructRest92 = "..." into:(_ into:id() {into})? {if let Some(into) = into {93 DestructRest::Keep(into)94 } else {DestructRest::Drop}}95 pub rule destruct_array(s: &ParserSettings) -> Destruct96 = "[" _ start:destruct(s)**comma() rest:(97 comma() _ rest:destruct_rest()? end:(98 comma() end:destruct(s)**comma() (_ comma())? {end}99 / comma()? {Vec::new()}100 ) {(rest, end)}101 / comma()? {(None, Vec::new())}102 ) _ "]" {?103 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array {104 start,105 rest: rest.0,106 end: rest.1,107 });108 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")109 }110 pub rule destruct_object(s: &ParserSettings) -> Destruct111 = "{" _112 fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma()113 rest:(114 comma() rest:destruct_rest()? {rest}115 / comma()? {None}116 )117 _ "}" {?118 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object {119 fields,120 rest,121 });122 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")123 }124 pub rule destruct(s: &ParserSettings) -> Destruct125 = v:id() {Destruct::Full(v)}126 / "?" {?127 #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip);128 #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")129 }130 / arr:destruct_array(s) {arr}131 / obj:destruct_object(s) {obj}132133 pub rule bind(s: &ParserSettings) -> BindSpec134 = into:destruct(s) _ "=" _ value:expr(s) {BindSpec::Field{into, value: Rc::new(value)}}135 / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value: Rc::new(value)}}136137 pub rule assertion(s: &ParserSettings) -> AssertStmt138 = keyword("assert") _ cond:spanned(<expr(s)>, s) msg:(_ ":" _ e:spanned(<expr(s)>, s) {e})? { AssertStmt(cond, msg) }139140 pub rule whole_line() -> &'input str141 = str:$((!['\n'][_])* "\n") {str}142 pub rule string_block() -> String143 = "|||" chomped:"-"? (!['\n']single_whitespace())* "\n"144 empty_lines:$(['\n']*)145 prefix:[' ' | '\t']+ first_line:whole_line()146 lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*147 [' ' | '\t']*<, {prefix.len() - 1}> "|||"148 {149 let mut l = empty_lines.to_owned();150 l.push_str(first_line);151 l.extend(lines);152 if chomped.is_some() {153 debug_assert!(l.ends_with('\n'));154 l.truncate(l.len() - 1);155 }156 l157 }158159 rule hex_char()160 = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")161162 rule string_char(c: rule<()>)163 = (!['\\']!c()[_])+164 / "\\\\"165 / "\\u" hex_char() hex_char() hex_char() hex_char()166 / "\\x" hex_char() hex_char()167 / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))168 pub rule string() -> String169 = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}170 / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}171 / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}172 / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}173 / string_block() } / expected!("<string>")174175 pub rule field_name(s: &ParserSettings) -> FieldName176 = name:id() {FieldName::Fixed(name)}177 / name:string() {FieldName::Fixed(name.into())}178 / "[" _ expr:expr(s) _ "]" {FieldName::Dyn(expr)}179 pub rule visibility() -> Visibility180 = ":::" {Visibility::Unhide}181 / "::" {Visibility::Hidden}182 / ":" {Visibility::Normal}183 pub rule field(s: &ParserSettings) -> FieldMember184 = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {FieldMember{185 name,186 plus: plus.is_some(),187 params: None,188 visibility,189 value: Rc::new(value),190 }}191 / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {FieldMember{192 name,193 plus: false,194 params: Some(params),195 visibility,196 value: Rc::new(value),197 }}198 pub rule obj_local(s: &ParserSettings) -> BindSpec199 = keyword("local") _ bind:bind(s) {bind}200 pub rule member(s: &ParserSettings) -> Member201 = bind:obj_local(s) {Member::BindStmt(bind)}202 / assertion:assertion(s) {Member::AssertStmt(assertion)}203 / field:field(s) {Member::Field(field)}204 pub rule objinside(s: &ParserSettings) -> ObjBody205 = members:(member(s) ** comma()) comma()? _ compspecs:compspecs(s)? {?206 Ok(if let Some(compspecs) = compspecs {207 let mut locals = Vec::new();208 let mut field = None;209 for member in members {210 match member {211 Member::Field(field_member) => if field.replace(field_member).is_some() {212 return Err("<object comprehension can only contain one field>")213 },214 Member::BindStmt(bind_spec) => locals.push(bind_spec),215 Member::AssertStmt(assert_stmt) => return Err("<asserts are unsupported in object comprehension>"),216 }217 }218 ObjBody::ObjComp(ObjComp {219 locals: Rc::new(locals),220 field: field.map(Rc::new).ok_or("<missing object comprehension field>")?,221 compspecs222 })223 } else {224 let mut locals = Vec::new();225 let mut asserts = Vec::new();226 let mut fields = Vec::new();227 for member in members {228 match member {229 Member::Field(field_member) => fields.push(field_member),230 Member::BindStmt(bind_spec) => locals.push(bind_spec),231 Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),232 }233 }234 ObjBody::MemberList(ObjMembers {235 locals: Rc::new(locals),236 asserts: Rc::new(asserts),237 fields238 })239 })240 }241 pub rule ifspec(s: &ParserSettings) -> IfSpecData242 = keyword("if") _ expr:expr(s) {IfSpecData(expr)}243 pub rule forspec(s: &ParserSettings) -> ForSpecData244 = keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}245 rule compspec(s: &ParserSettings) -> CompSpec246 = i:spanned(<ifspec(s)>, s) { CompSpec::IfSpec(i) } / f:spanned(<forspec(s)>, s) {CompSpec::ForSpec(f)}247 pub rule compspecs(s: &ParserSettings) -> Vec<CompSpec>248 = specs:compspec(s) ++ _ {?249 if !matches!(specs[0], CompSpec::ForSpec(_)) {250 return Err("<first compspec should be for>")251 }252 Ok(specs)253 }254 pub rule local_expr(s: &ParserSettings) -> Expr255 = keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) }256 pub rule string_expr(s: &ParserSettings) -> Expr257 = s:string() {Expr::Str(s.into())}258 pub rule obj_expr(s: &ParserSettings) -> Expr259 = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}260 pub rule array_expr(s: &ParserSettings) -> Expr261 = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))}262 pub rule array_comp_expr(s: &ParserSettings) -> Expr263 = "[" _ expr:expr(s) _ comma()? _ specs:(r: compspecs(s) _ {r}) "]" {264 Expr::ArrComp(Rc::new(expr), specs)265 }266 pub rule number_expr(s: &ParserSettings) -> Expr267 = n:number() {? if n.is_finite() {268 Ok(Expr::Num(n))269 } else {270 Err("!!!numbers are finite")271 }}272273 rule spanned<T: Acyclic>(x: rule<T>, s: &ParserSettings) -> Spanned<T>274 = a:position!() n:x() b:position!() { Spanned::new(n, Span(s.source.clone(), a as u32, b as u32)) }275276 pub rule var_expr(s: &ParserSettings) -> Expr277 = n:spanned(<id()>, s) { Expr::Var(n) }278 pub rule id_loc(s: &ParserSettings) -> Spanned<Expr>279 = a:position!() n:id() b:position!() { Spanned::new(Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) }280 pub rule if_then_else_expr(s: &ParserSettings) -> Expr281 = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{282 cond,283 cond_then,284 cond_else,285 }))}286287 pub rule literal(s: &ParserSettings) -> Expr288 = v:(289 keyword("null") {LiteralType::Null}290 / keyword("true") {LiteralType::True}291 / keyword("false") {LiteralType::False}292 / keyword("self") {LiteralType::This}293 / keyword("$") {LiteralType::Dollar}294 / keyword("super") {LiteralType::Super}295 ) {Expr::Literal(v)}296297 rule import_kind() -> ImportKind298 = keyword("importstr") { ImportKind::Str }299 / keyword("importbin") { ImportKind::Bin }300 / keyword("import") { ImportKind::Normal }301302 pub rule expr_basic(s: &ParserSettings) -> Expr303 = literal(s)304305 / string_expr(s) / number_expr(s)306 / array_expr(s)307 / obj_expr(s)308 / array_expr(s)309 / array_comp_expr(s)310311 / kind:spanned(<import_kind()>, s) _ path:expr(s) {Expr::Import(kind, Box::new(path))}312313 / var_expr(s)314 / local_expr(s)315 / if_then_else_expr(s)316317 / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))}318 / assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{319 assert, rest320 })) }321322 / err_kw:spanned(<keyword("error")>, s) _ expr:expr(s) { Expr::ErrorStmt(err_kw.1, Box::new(expr)) }323324 rule slice_part(s: &ParserSettings) -> Option<Spanned<Expr>>325 = _ e:(e:spanned(<expr(s)>, s) _{e})? {e}326 pub rule slice_desc(s: &ParserSettings) -> SliceDesc327 = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {328 let (end, step) = if let Some((end, step)) = pair {329 (end, step)330 }else{331 (None, None)332 };333334 SliceDesc { start, end, step }335 }336337 rule binop(x: rule<()>) -> ()338 = quiet!{ x() } / expected!("<binary op>")339 rule unaryop(x: rule<()>) -> ()340 = quiet!{ x() } / expected!("<unary op>")341342 rule ensure_null_coaelse()343 = "" {?344 #[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled");345 #[cfg(feature = "exp-null-coaelse")] Ok(())346 }347 use jrsonnet_ir::BinaryOpType::*;348 use jrsonnet_ir::UnaryOpType::*;349 rule expr(s: &ParserSettings) -> Expr350 = precedence! {351 a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}352 a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ {353 #[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b);354 unreachable!("ensure_null_coaelse will fail if feature is not enabled")355 }356 --357 a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}358 --359 a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}360 --361 a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}362 --363 a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}364 --365 a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}366 a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}367 --368 a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}369 a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}370 a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}371 a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}372 a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}373 --374 a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}375 a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}376 --377 a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}378 a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}379 --380 a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}381 a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}382 a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}383 --384 unaryop(<"+">) _ b:@ {expr_un!(Plus b)}385 unaryop(<"-">) _ b:@ {expr_un!(Minus b)}386 unaryop(<"!">) _ b:@ {expr_un!(Not b)}387 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}388 --389 value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))}390 indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}}391 a:(@) _ args:spanned(<"(" _ a:args(s) _ ")" {a}>, s) ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())}392 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)}393 --394 e:expr_basic(s) {e}395 "(" _ e:expr(s) _ ")" {e}396 }397 pub rule index_part(s: &ParserSettings) -> IndexPart398 = n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {399 span: value.1,400 value: value.0,401 #[cfg(feature = "exp-null-coaelse")]402 null_coaelse: n.is_some(),403 }}404 / n:("?" _ "." _ ensure_null_coaelse())? value:spanned(<"[" _ v:expr(s) _ "]" {v}>, s) {IndexPart {405 span: value.1,406 value: value.0,407 #[cfg(feature = "exp-null-coaelse")]408 null_coaelse: n.is_some(),409 }}410411 pub rule jsonnet(s: &ParserSettings) -> Expr = _ e:expr(s) _ {e}412 }413}414415pub type ParseError = peg::error::ParseError<peg::str::LineCol>;416pub fn parse(str: &str, settings: &ParserSettings) -> Result<Expr, ParseError> {417 jsonnet_parser::jsonnet(str, settings)418}419/// Used for importstr values420pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned<Expr> {421 let len = str.len();422 Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32))423}424425#[cfg(test)]426pub mod tests {427 use insta::assert_snapshot;428 use jrsonnet_ir::{IStr, Source};429430 use super::parse;431 use crate::ParserSettings;432433 fn parsep(s: &str) -> String {434 let v = parse(435 s,436 &ParserSettings {437 source: Source::new_virtual("<test>".into(), IStr::empty()),438 },439 )440 .unwrap();441 format!("{v:#?}")442 }443444 macro_rules! parse {445 ($s:expr) => {446 assert_snapshot!(parsep($s));447 };448 }449450 #[test]451 fn multiline_string() {452 parse!("|||\n Hello world!\n a\n|||");453 parse!("|||\n Hello world!\n a\n|||");454 parse!("|||\n\t\tHello world!\n\t\t\ta\n|||");455 parse!("|||\n Hello world!\n a\n |||");456 }457458 #[test]459 fn slice() {460 parse!("a[1:]");461 parse!("a[1::]");462 parse!("a[:1:]");463 parse!("a[::1]");464 parse!("str[:len - 1]");465 }466467 #[test]468 fn string_escaping() {469 parse!(r#""Hello, \"world\"!""#);470 parse!(r#"'Hello \'world\'!'"#);471 parse!(r#"'\\\\'"#);472 }473474 #[test]475 fn string_unescaping() {476 parse!(r#""Hello\nWorld""#);477 }478479 #[test]480 fn string_verbantim() {481 parse!(r#"@"Hello\n""World""""#);482 }483484 #[test]485 fn imports() {486 parse!("import \"hello\"");487 parse!("importstr \"garnish.txt\"");488 parse!("importbin \"garnish.bin\"");489 }490491 #[test]492 fn empty_object() {493 parse!("{}");494 }495496 #[test]497 fn basic_math() {498 parse!("2+2*2");499 parse!("2 + 2 * 2 ");500 parse!("2+(2+2*2)");501 parse!("2//comment\n+//comment\n3/*test*/*/*test*/4");502 }503504 #[test]505 fn suffix() {506 parse!("std.test");507 parse!("std(2)");508 parse!("std.test(2)");509 parse!("a[b]");510 }511512 #[test]513 fn array_comp() {514 parse!("[std.deepJoin(x) for x in arr]");515 }516517 #[test]518 fn reserved() {519 parse!("null");520 parse!("nulla");521 }522523 #[test]524 fn multiple_args_buf() {525 parse!("a(b, null_fields)");526 }527528 #[test]529 fn infix_precedence() {530 parse!("!a && !b");531 parse!("!a / !b");532 }533534 #[test]535 fn double_negation() {536 parse!("!!a");537 }538539 #[test]540 fn array_test_error() {541 parse!("[a for a in b if c for e in f]");542 }543544 #[test]545 fn missing_newline_between_comment_and_eof() {546 parse!(547 "{a:1}548549 //+213"550 );551 }552553 #[test]554 fn default_param_before_nondefault() {555 parse!("local x(foo = 'foo', bar) = null; null");556 }557558 #[test]559 fn add_location_info_to_all_sub_expressions() {560 parse!("{} { local x = 1, x: x } + {}");561 }562}