mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -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)
|
"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
|
Functional processing expressions
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
@ -141,6 +141,12 @@ reduce_expr: reduce_keyword, bareword, dot, bareword, expr, expr ;
|
|||||||
processing_expr: map_or_filter_expr | reduce_expr
|
processing_expr: map_or_filter_expr | reduce_expr
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Range Expression
|
||||||
|
|
||||||
|
```
|
||||||
|
range_expr: expr, ':', [int, ':'], expr ;
|
||||||
|
```
|
||||||
|
|
||||||
#### Include Expression
|
#### Include Expression
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -155,6 +161,7 @@ non_operator_expr: literal
|
|||||||
| macrodef
|
| macrodef
|
||||||
| module_def
|
| module_def
|
||||||
| format_expr
|
| format_expr
|
||||||
|
| range_expr
|
||||||
| include_expr
|
| include_expr
|
||||||
| copy_expr
|
| copy_expr
|
||||||
| processing_expr
|
| processing_expr
|
||||||
|
@ -4,10 +4,25 @@ let concatenated = ["foo"] + ["bar"];
|
|||||||
|
|
||||||
assert {
|
assert {
|
||||||
ok = concatenated == ["foo", "bar"],
|
ok = concatenated == ["foo", "bar"],
|
||||||
desc = "concatenated == [\"foo\", \"bar\"]",
|
desc = "Successfully concatenated",
|
||||||
};
|
};
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
ok = concatenated + empty_list == ["foo", "bar"],
|
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)),
|
||||||
};
|
};
|
@ -62,3 +62,9 @@ assert {
|
|||||||
ok = mymodule{}.foo == "bar",
|
ok = mymodule{}.foo == "bar",
|
||||||
desc = "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::Macro(_)
|
||||||
| &Expression::Copy(_)
|
| &Expression::Copy(_)
|
||||||
| &Expression::Module(_)
|
| &Expression::Module(_)
|
||||||
|
| &Expression::Range(_)
|
||||||
| &Expression::FuncOp(_)
|
| &Expression::FuncOp(_)
|
||||||
| &Expression::Include(_) => {
|
| &Expression::Include(_) => {
|
||||||
// noop
|
// 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.
|
/// Encodes a ucg expression. Expressions compute a value from.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
@ -644,6 +654,7 @@ pub enum Expression {
|
|||||||
|
|
||||||
// Complex Expressions
|
// Complex Expressions
|
||||||
Copy(CopyDef),
|
Copy(CopyDef),
|
||||||
|
Range(RangeDef),
|
||||||
// TODO(jwall): This should really store it's position :-(
|
// TODO(jwall): This should really store it's position :-(
|
||||||
Grouped(Box<Expression>),
|
Grouped(Box<Expression>),
|
||||||
Format(FormatDef),
|
Format(FormatDef),
|
||||||
@ -662,6 +673,7 @@ impl Expression {
|
|||||||
&Expression::Simple(ref v) => v.pos(),
|
&Expression::Simple(ref v) => v.pos(),
|
||||||
&Expression::Binary(ref def) => &def.pos,
|
&Expression::Binary(ref def) => &def.pos,
|
||||||
&Expression::Copy(ref def) => &def.pos,
|
&Expression::Copy(ref def) => &def.pos,
|
||||||
|
&Expression::Range(ref def) => &def.pos,
|
||||||
&Expression::Grouped(ref expr) => expr.pos(),
|
&Expression::Grouped(ref expr) => expr.pos(),
|
||||||
&Expression::Format(ref def) => &def.pos,
|
&Expression::Format(ref def) => &def.pos,
|
||||||
&Expression::Call(ref def) => &def.pos,
|
&Expression::Call(ref def) => &def.pos,
|
||||||
@ -689,6 +701,9 @@ impl fmt::Display for Expression {
|
|||||||
&Expression::Copy(_) => {
|
&Expression::Copy(_) => {
|
||||||
write!(w, "<Copy>")?;
|
write!(w, "<Copy>")?;
|
||||||
}
|
}
|
||||||
|
&Expression::Range(_) => {
|
||||||
|
write!(w, "<Range>")?;
|
||||||
|
}
|
||||||
&Expression::Grouped(_) => {
|
&Expression::Grouped(_) => {
|
||||||
write!(w, "(<Expr>)")?;
|
write!(w, "(<Expr>)")?;
|
||||||
}
|
}
|
||||||
|
@ -768,7 +768,24 @@ impl<'a> FileBuilder<'a> {
|
|||||||
Expression::Simple(Value::Int(ref i)) => {
|
Expression::Simple(Value::Int(ref i)) => {
|
||||||
scope.lookup_idx(right.pos(), &Val::Int(i.val))
|
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.
|
// Evals a single Expression in the context of a running Builder.
|
||||||
// It does not mutate the builders collected state at all.
|
// 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>> {
|
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::Simple(ref val) => self.eval_value(val, scope),
|
||||||
&Expression::Binary(ref def) => self.eval_binary(def, scope),
|
&Expression::Binary(ref def) => self.eval_binary(def, scope),
|
||||||
&Expression::Copy(ref def) => self.eval_copy(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::Grouped(ref expr) => self.eval_expr(expr, scope),
|
||||||
&Expression::Format(ref def) => self.eval_format(def, scope),
|
&Expression::Format(ref def) => self.eval_format(def, scope),
|
||||||
&Expression::Call(ref def) => self.eval_call(def, scope),
|
&Expression::Call(ref def) => self.eval_call(def, scope),
|
||||||
|
@ -631,11 +631,35 @@ make_fn!(
|
|||||||
either!(reduce_expression, map_expression, filter_expression)
|
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> {
|
fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
|
||||||
let _input = input.clone();
|
let _input = input.clone();
|
||||||
either!(
|
either!(
|
||||||
input,
|
input,
|
||||||
trace_parse!(format_expression),
|
trace_parse!(format_expression),
|
||||||
|
trace_parse!(range_expression),
|
||||||
trace_parse!(simple_expression),
|
trace_parse!(simple_expression),
|
||||||
trace_parse!(call_expression),
|
trace_parse!(call_expression),
|
||||||
trace_parse!(copy_expression)
|
trace_parse!(copy_expression)
|
||||||
|
@ -242,6 +242,10 @@ make_fn!(semicolontok<OffsetStrIter, Token>,
|
|||||||
do_text_token_tok!(TokenType::PUNCT, ";")
|
do_text_token_tok!(TokenType::PUNCT, ";")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
make_fn!(colontok<OffsetStrIter, Token>,
|
||||||
|
do_text_token_tok!(TokenType::PUNCT, ":")
|
||||||
|
);
|
||||||
|
|
||||||
make_fn!(leftsquarebracket<OffsetStrIter, Token>,
|
make_fn!(leftsquarebracket<OffsetStrIter, Token>,
|
||||||
do_text_token_tok!(TokenType::PUNCT, "[")
|
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
|
fatcommatok, // Note fatcommatok must come before equaltok
|
||||||
equaltok,
|
equaltok,
|
||||||
semicolontok,
|
semicolontok,
|
||||||
|
colontok,
|
||||||
leftsquarebracket,
|
leftsquarebracket,
|
||||||
rightsquarebracket,
|
rightsquarebracket,
|
||||||
booleantok,
|
booleantok,
|
||||||
|
@ -50,3 +50,36 @@ let enumerate = module{
|
|||||||
let enumerated = reduce reducer.result acc, (mod.list);
|
let enumerated = reduce reducer.result acc, (mod.list);
|
||||||
let result = enumerated.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;
|
||||||
|
};
|
@ -38,3 +38,8 @@ assert asserts.equal{
|
|||||||
}.result,
|
}.result,
|
||||||
right=[[1, "foo"], [3, "bar"]],
|
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