mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-21 18:10:42 -04:00
parent
d2a5a1619a
commit
6f9ba2ac33
@ -223,6 +223,16 @@ should be placed. Any primitive value can be used as an argument.
|
||||
"https://@:@/" % (host, port)
|
||||
```
|
||||
|
||||
Range Expression
|
||||
----------------
|
||||
|
||||
UCG can generate lists from a range with an optional step.
|
||||
|
||||
```
|
||||
1:10 == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
0:2:10 == [0, 2, 4, 6, 8, 10];
|
||||
```
|
||||
|
||||
Functional processing expressions
|
||||
---------------------------------
|
||||
|
||||
|
@ -141,6 +141,12 @@ reduce_expr: reduce_keyword, bareword, dot, bareword, expr, expr ;
|
||||
processing_expr: map_or_filter_expr | reduce_expr
|
||||
```
|
||||
|
||||
### Range Expression
|
||||
|
||||
```
|
||||
range_expr: expr, ':', [int, ':'], expr ;
|
||||
```
|
||||
|
||||
#### Include Expression
|
||||
|
||||
```
|
||||
@ -155,6 +161,7 @@ non_operator_expr: literal
|
||||
| macrodef
|
||||
| module_def
|
||||
| format_expr
|
||||
| range_expr
|
||||
| include_expr
|
||||
| copy_expr
|
||||
| processing_expr
|
||||
|
@ -4,10 +4,25 @@ let concatenated = ["foo"] + ["bar"];
|
||||
|
||||
assert {
|
||||
ok = concatenated == ["foo", "bar"],
|
||||
desc = "concatenated == [\"foo\", \"bar\"]",
|
||||
desc = "Successfully concatenated",
|
||||
};
|
||||
|
||||
assert {
|
||||
ok = concatenated + empty_list == ["foo", "bar"],
|
||||
desc = "concatenated + empty_list == [\"foo\", \"bar\"]",
|
||||
desc = "successfully concatenated empty list",
|
||||
};
|
||||
|
||||
assert {
|
||||
ok = 1:5 == [1, 2, 3, 4, 5],
|
||||
desc = "expected list of 5 but got @" % (1:5),
|
||||
};
|
||||
|
||||
assert {
|
||||
ok = 0:2:6 == [0, 2, 4, 6],
|
||||
desc = "Expected evens up to 6 but got @" % (0:2:6),
|
||||
};
|
||||
|
||||
assert {
|
||||
ok = 0:(1+3) == [0, 1, 2, 3, 4],
|
||||
desc = "Expected 0 through 4 but got @" % (0:(1+3)),
|
||||
};
|
@ -61,4 +61,10 @@ let mymodule = module { foo = "bar" } => {
|
||||
assert {
|
||||
ok = mymodule{}.foo == "bar",
|
||||
desc = "mymodule{}.foo == \"bar\"",
|
||||
};
|
||||
|
||||
let idx = 1;
|
||||
assert {
|
||||
ok = list.(idx) == 2,
|
||||
desc = "expected 2, got @" % (list.(idx)),
|
||||
};
|
@ -456,6 +456,7 @@ impl MacroDef {
|
||||
&Expression::Macro(_)
|
||||
| &Expression::Copy(_)
|
||||
| &Expression::Module(_)
|
||||
| &Expression::Range(_)
|
||||
| &Expression::FuncOp(_)
|
||||
| &Expression::Include(_) => {
|
||||
// noop
|
||||
@ -633,6 +634,15 @@ impl ModuleDef {
|
||||
}
|
||||
}
|
||||
|
||||
/// RangeDef defines a range with optional step.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct RangeDef {
|
||||
pub pos: Position,
|
||||
pub start: Box<Expression>,
|
||||
pub step: Option<Box<Expression>>,
|
||||
pub end: Box<Expression>,
|
||||
}
|
||||
|
||||
/// Encodes a ucg expression. Expressions compute a value from.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expression {
|
||||
@ -644,6 +654,7 @@ pub enum Expression {
|
||||
|
||||
// Complex Expressions
|
||||
Copy(CopyDef),
|
||||
Range(RangeDef),
|
||||
// TODO(jwall): This should really store it's position :-(
|
||||
Grouped(Box<Expression>),
|
||||
Format(FormatDef),
|
||||
@ -662,6 +673,7 @@ impl Expression {
|
||||
&Expression::Simple(ref v) => v.pos(),
|
||||
&Expression::Binary(ref def) => &def.pos,
|
||||
&Expression::Copy(ref def) => &def.pos,
|
||||
&Expression::Range(ref def) => &def.pos,
|
||||
&Expression::Grouped(ref expr) => expr.pos(),
|
||||
&Expression::Format(ref def) => &def.pos,
|
||||
&Expression::Call(ref def) => &def.pos,
|
||||
@ -689,6 +701,9 @@ impl fmt::Display for Expression {
|
||||
&Expression::Copy(_) => {
|
||||
write!(w, "<Copy>")?;
|
||||
}
|
||||
&Expression::Range(_) => {
|
||||
write!(w, "<Range>")?;
|
||||
}
|
||||
&Expression::Grouped(_) => {
|
||||
write!(w, "(<Expr>)")?;
|
||||
}
|
||||
|
@ -768,7 +768,24 @@ impl<'a> FileBuilder<'a> {
|
||||
Expression::Simple(Value::Int(ref i)) => {
|
||||
scope.lookup_idx(right.pos(), &Val::Int(i.val))
|
||||
}
|
||||
_ => self.eval_expr(right, scope),
|
||||
_ => {
|
||||
let val = self.eval_expr(right, scope)?;
|
||||
match val.as_ref() {
|
||||
Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)),
|
||||
Val::Str(ref s) => scope
|
||||
.lookup_sym(&PositionedItem::new(s.clone(), pos.clone()), false)
|
||||
.ok_or(Box::new(error::BuildError::new(
|
||||
format!("Unable to find binding {}", s,),
|
||||
error::ErrorType::NoSuchSymbol,
|
||||
pos,
|
||||
))),
|
||||
_ => Err(Box::new(error::BuildError::new(
|
||||
format!("Invalid selector lookup {}", val.type_name(),),
|
||||
error::ErrorType::NoSuchSymbol,
|
||||
pos,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1518,6 +1535,65 @@ impl<'a> FileBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_range(&self, def: &RangeDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
let start = self.eval_expr(&def.start, scope)?;
|
||||
let start = match start.as_ref() {
|
||||
&Val::Int(i) => i,
|
||||
_ => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Expected an integer for range start but got {}",
|
||||
start.type_name()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.start.pos().clone(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
// See if there was a step.
|
||||
let step = match &def.step {
|
||||
Some(step) => {
|
||||
let step = self.eval_expr(&step, scope)?;
|
||||
match step.as_ref() {
|
||||
&Val::Int(i) => i,
|
||||
_ => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Expected an integer for range step but got {}",
|
||||
step.type_name()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.start.pos().clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => 1,
|
||||
};
|
||||
|
||||
// Get the end.
|
||||
let end = self.eval_expr(&def.end, scope)?;
|
||||
let end = match end.as_ref() {
|
||||
&Val::Int(i) => i,
|
||||
_ => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Expected an integer for range start but got {}",
|
||||
end.type_name()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.start.pos().clone(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let vec = (start..end + 1)
|
||||
.step_by(step as usize)
|
||||
.map(|i| Rc::new(Val::Int(i)))
|
||||
.collect();
|
||||
Ok(Rc::new(Val::List(vec)))
|
||||
}
|
||||
|
||||
// Evals a single Expression in the context of a running Builder.
|
||||
// It does not mutate the builders collected state at all.
|
||||
pub fn eval_expr(&self, expr: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
@ -1525,6 +1601,7 @@ impl<'a> FileBuilder<'a> {
|
||||
&Expression::Simple(ref val) => self.eval_value(val, scope),
|
||||
&Expression::Binary(ref def) => self.eval_binary(def, scope),
|
||||
&Expression::Copy(ref def) => self.eval_copy(def, scope),
|
||||
&Expression::Range(ref def) => self.eval_range(def, scope),
|
||||
&Expression::Grouped(ref expr) => self.eval_expr(expr, scope),
|
||||
&Expression::Format(ref def) => self.eval_format(def, scope),
|
||||
&Expression::Call(ref def) => self.eval_call(def, scope),
|
||||
|
@ -631,11 +631,35 @@ make_fn!(
|
||||
either!(reduce_expression, map_expression, filter_expression)
|
||||
);
|
||||
|
||||
make_fn!(
|
||||
range_expression<SliceIter<Token>, Expression>,
|
||||
do_each!(
|
||||
pos => pos,
|
||||
start => either!(simple_expression, grouped_expression),
|
||||
_ => punct!(":"),
|
||||
maybe_step => optional!(
|
||||
do_each!(
|
||||
step => either!(simple_expression, grouped_expression),
|
||||
_ => punct!(":"),
|
||||
(Box::new(step))
|
||||
)
|
||||
),
|
||||
end => either!(simple_expression, grouped_expression),
|
||||
(Expression::Range(RangeDef{
|
||||
pos: pos,
|
||||
start: Box::new(start),
|
||||
step: maybe_step,
|
||||
end: Box::new(end),
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
|
||||
let _input = input.clone();
|
||||
either!(
|
||||
input,
|
||||
trace_parse!(format_expression),
|
||||
trace_parse!(range_expression),
|
||||
trace_parse!(simple_expression),
|
||||
trace_parse!(call_expression),
|
||||
trace_parse!(copy_expression)
|
||||
|
@ -242,6 +242,10 @@ make_fn!(semicolontok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::PUNCT, ";")
|
||||
);
|
||||
|
||||
make_fn!(colontok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::PUNCT, ":")
|
||||
);
|
||||
|
||||
make_fn!(leftsquarebracket<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::PUNCT, "[")
|
||||
);
|
||||
@ -389,6 +393,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
||||
fatcommatok, // Note fatcommatok must come before equaltok
|
||||
equaltok,
|
||||
semicolontok,
|
||||
colontok,
|
||||
leftsquarebracket,
|
||||
rightsquarebracket,
|
||||
booleantok,
|
||||
|
@ -49,4 +49,37 @@ let enumerate = module{
|
||||
|
||||
let enumerated = reduce reducer.result acc, (mod.list);
|
||||
let result = enumerated.list;
|
||||
};
|
||||
|
||||
let zip = module{
|
||||
list1 = NULL,
|
||||
list2 = NULL,
|
||||
} => {
|
||||
let counter = macro(acc, item) => {
|
||||
result = acc + 1,
|
||||
};
|
||||
|
||||
let len1 = reduce counter.result 0, (mod.list1);
|
||||
let len2 = reduce counter.result 0, (mod.list2);
|
||||
|
||||
let rng = select (len1 >= len2), NULL, {
|
||||
true = 0:(len1 - 1),
|
||||
false = 0:(len2 - 1),
|
||||
};
|
||||
|
||||
let reducer = macro(acc, item) => {
|
||||
result = acc{
|
||||
result = acc.result + [[acc.list1.(item), acc.list2.(item)]],
|
||||
idxs = acc.idxs + [item]
|
||||
},
|
||||
};
|
||||
|
||||
let acc = {
|
||||
list1 = mod.list1,
|
||||
list2 = mod.list2,
|
||||
result = [],
|
||||
idxs = [],
|
||||
};
|
||||
|
||||
let result = (reduce reducer.result acc, rng).result;
|
||||
};
|
@ -37,4 +37,9 @@ assert asserts.equal{
|
||||
list=["foo", "bar"]
|
||||
}.result,
|
||||
right=[[1, "foo"], [3, "bar"]],
|
||||
};
|
||||
|
||||
assert asserts.equal{
|
||||
left=list.zip{list1=[0, 1], list2=[3, 4]}.result,
|
||||
right=[[0, 3], [1, 4]],
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user