Skip to content

Commit

Permalink
Missing documented things (#50)
Browse files Browse the repository at this point in the history
* Define global arch/os constants as documented

* Define COLOR global variable

* Interpolation ops: dir, filename, ext

* Support piping into string expressions

* Add `dedup` function and interpolation op
  • Loading branch information
simonask authored Feb 7, 2025
1 parent be918b9 commit 4e50bdd
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 24 deletions.
8 changes: 0 additions & 8 deletions book/src/language/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ In build recipes:
scope and that pattern has a `%` in scope. When defining patterns in a scope
where another pattern is already present, the interpolated `{%}` may be used
to unambiguously refer to the stem of the "outer" pattern.
- `{+}` or `<+>`: Same as `in`, except that duplicate entries are preserved in
the order they were declared. This is primarily useful for use in linking
commands where it is meaningful to repeat library file names in a particular
order.
- `{@D}`: The directory part of the file name of the target. Same as
`{out:dir}`.
- `{@F}`: The file-within-directory part of the file name of the target. Same as
`{out:filename}`.

## Global constants

Expand Down
13 changes: 13 additions & 0 deletions book/src/language/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,19 @@ Example:
let filtered = ["a.c", "b.cpp"] | detain "%.cpp" # ["a.c"]
```

### `dedup`

Deduplicate strings in a list (recursively), preserving the original order. This
implies `flatten`.

When given a single string, returns the string unmodified.

Example:

```werk
let deduplicated = ["a", ["a"], "b", "a"] | dedup # ["a", "b"]
```

### `map`

Given a list expression, pass each element through a string expression where the
Expand Down
2 changes: 2 additions & 0 deletions book/src/language/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ they are applied in order.
- `{...:s/regex/replacement/}` replaces occurrences matching `regex` with
`replacement`. The regex is passed verbatim to the `regex` crate, and the
replacement string follows the normal conventions.
- `{...:dedup}`: When interpolating a list, deduplicate entries in the list
(removing duplicate entries recursively), preserving the original order.
- `{...:dir}`: When the stem refers to an [abstract path](../paths.md), produces
the directory part of the path.
- `{...:filename}`: When the stem refers to an [abstract path](../paths.md),
Expand Down
11 changes: 11 additions & 0 deletions tests/cases/dedup.werk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
let a = "a" | dedup | assert-eq "a"
let b = ["b"] | dedup | assert-eq ["b"]
let c = ["c", "c"] | dedup | assert-eq ["c"]
let d = ["d", ["d", ["d"]]] | dedup | assert-eq ["d"]
let abcd = ["a", ["b", "a"], ["c", "d"], "d"] | dedup | assert-eq ["a", "b", "c", "d"]

let a = "a" | "{:dedup}" | assert-eq "a"
let b = ["b"] | "{,*:dedup}" | assert-eq "b"
let c = ["c", "c"] | "{,*:dedup}" | assert-eq "c"
let d = ["d", ["d", ["d"]]] | "{,*:dedup}" | assert-eq "d"
let abcd = ["a", ["b", "a"], ["c", "d"], "d"] | "{,*:dedup}" | assert-eq "a,b,c,d"
9 changes: 9 additions & 0 deletions tests/cases/string_interp.werk
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let path = "/foo/bar/baz.c"
let filename = "{path:filename}" | assert-eq "baz.c"
let dirname = "{path:dir}" | assert-eq "/foo/bar"
let ext = "{path:ext}" | assert-eq "c"
let obj_path = "{path:.c=.o}" | assert-eq "/foo/bar/baz.o"
let obj_filename1 = "{path:.c=.o,filename}" | assert-eq "baz.o"
let obj_filename1 = "{path:filename,.c=.o}" | assert-eq "baz.o"
let path_regex = "{path:s/bar/qux/}" | assert-eq "/foo/qux/baz.c"
let path_regex_dir = "{path:s/bar/qux/,dir}" | assert-eq "/foo/qux"
2 changes: 2 additions & 0 deletions tests/test_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ success_case!(write);
success_case!(copy);
success_case!(read);
success_case!(env);
success_case!(string_interp);
success_case!(dedup);

error_case!(ambiguous_build_recipe);
error_case!(ambiguous_path_resolution);
Expand Down
19 changes: 19 additions & 0 deletions werk-fs/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,25 @@ impl Path {
Some(Self::new_unchecked(parent))
}

#[inline]
#[must_use]
pub fn file_name(&self) -> &Path {
let Some((_parent, tail)) = self.path.rsplit_once(Self::SEPARATOR) else {
return self;
};
if tail.is_empty() {
return self;
}
Self::new_unchecked(tail)
}

#[inline]
#[must_use]
pub fn extension(&self) -> Option<&str> {
let (_, ext) = self.path.rsplit_once('.')?;
Some(ext)
}

#[must_use]
pub fn is_parent_of(&self, other: &Path) -> bool {
if self.is_absolute() != other.is_absolute() {
Expand Down
10 changes: 8 additions & 2 deletions werk-parser/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub struct SubExpr<'a> {
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ExprOp<'a> {
SubExpr(SubExpr<'a>),
StringExpr(StringExpr<'a>),
Match(MatchExpr<'a>),
Map(MapExpr<'a>),
Flatten(FlattenExpr<'a>),
Expand All @@ -109,6 +110,7 @@ pub enum ExprOp<'a> {
Join(JoinExpr<'a>),
Split(SplitExpr<'a>),
Lines(LinesExpr<'a>),
Dedup(DedupExpr<'a>),
Info(InfoExpr<'a>),
Warn(WarnExpr<'a>),
Error(ErrorExpr<'a>),
Expand All @@ -121,6 +123,7 @@ impl Spanned for ExprOp<'_> {
fn span(&self) -> Span {
match self {
ExprOp::SubExpr(expr) => expr.span,
ExprOp::StringExpr(expr) => expr.span,
ExprOp::Match(expr) => expr.span,
ExprOp::Map(expr) => expr.span,
ExprOp::Flatten(expr) => expr.span(),
Expand All @@ -129,6 +132,7 @@ impl Spanned for ExprOp<'_> {
ExprOp::Discard(expr) => expr.span,
ExprOp::Join(expr) => expr.span,
ExprOp::Split(expr) => expr.span,
ExprOp::Dedup(expr) => expr.span(),
ExprOp::Lines(expr) => expr.span(),
ExprOp::Info(expr) => expr.span,
ExprOp::Warn(expr) => expr.span,
Expand All @@ -144,6 +148,7 @@ impl SemanticHash for ExprOp<'_> {
std::mem::discriminant(self).hash(state);
match self {
ExprOp::SubExpr(expr) => expr.expr.semantic_hash(state),
ExprOp::StringExpr(expr) => expr.semantic_hash(state),
ExprOp::Match(expr) => expr.semantic_hash(state),
ExprOp::Map(expr) => expr.semantic_hash(state),
ExprOp::Filter(expr) => expr.semantic_hash(state),
Expand All @@ -157,8 +162,8 @@ impl SemanticHash for ExprOp<'_> {
| ExprOp::Error(_)
| ExprOp::AssertEq(_)
| ExprOp::AssertMatch(_)
// `flatten` and `lines` are caught by the discriminant
| ExprOp::Flatten(_) | ExprOp::Lines(_)
// Covered by the discriminant:
| ExprOp::Dedup(_) | ExprOp::Flatten(_) | ExprOp::Lines(_)
=> (),
}
}
Expand Down Expand Up @@ -372,6 +377,7 @@ pub type AssertEqExpr<'a> = KwExpr<keyword::AssertEq, Box<Expr<'a>>>;
pub type AssertMatchExpr<'a> = KwExpr<keyword::AssertEq, Box<PatternExpr<'a>>>;
pub type FlattenExpr<'a> = keyword::Flatten;
pub type SplitExpr<'a> = KwExpr<keyword::Split, PatternExpr<'a>>;
pub type DedupExpr<'a> = keyword::Dedup;
pub type LinesExpr<'a> = keyword::Lines;
pub type FilterExpr<'a> = KwExpr<keyword::Filter, PatternExpr<'a>>;
pub type FilterMatchExpr<'a> = KwExpr<keyword::FilterMatch, MatchBody<'a>>;
Expand Down
1 change: 1 addition & 0 deletions werk-parser/ast/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def_keyword!(Filter, "filter");
def_keyword!(FilterMatch, "filter-match");
def_keyword!(Discard, "discard");
def_keyword!(Split, "split");
def_keyword!(Dedup, "dedup");
def_keyword!(Lines, "lines");

def_keyword!(AssertEq, "assert-eq");
Expand Down
21 changes: 20 additions & 1 deletion werk-parser/ast/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ impl std::fmt::Display for Interpolation<'_> {
regex_interpolation_op.replacer
)?,
InterpolationOp::ResolveOsPath => unreachable!(),
InterpolationOp::Dedup => f.write_str("dedup")?,
InterpolationOp::Filename => f.write_str("filename")?,
InterpolationOp::Dirname => f.write_str("dir")?,
InterpolationOp::Ext => f.write_str("ext")?,
InterpolationOp::ResolveOutDir => f.write_str("out-dir")?,
InterpolationOp::ResolveWorkspace => f.write_str("workspace")?,
}
Expand Down Expand Up @@ -389,6 +393,13 @@ impl SemanticHash for InterpolationStem {

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum InterpolationOp<'a> {
Dedup,
/// Get the filename part of a path.
Filename,
/// Get the directory part of a path (wihout a final path separator).
Dirname,
/// Get the file extension of a path (without the dot).
Ext,
/// Replace extension - input must be path.
ReplaceExtension {
from: Cow<'a, str>,
Expand Down Expand Up @@ -416,6 +427,10 @@ impl InterpolationOp<'_> {
InterpolationOp::PrependEach(s) => InterpolationOp::PrependEach(s.into_owned().into()),
InterpolationOp::AppendEach(s) => InterpolationOp::AppendEach(s.into_owned().into()),
InterpolationOp::RegexReplace(r) => InterpolationOp::RegexReplace(r.into_static()),
InterpolationOp::Dedup => InterpolationOp::Dedup,
InterpolationOp::Filename => InterpolationOp::Filename,
InterpolationOp::Dirname => InterpolationOp::Dirname,
InterpolationOp::Ext => InterpolationOp::Ext,
InterpolationOp::ResolveOsPath => InterpolationOp::ResolveOsPath,
InterpolationOp::ResolveOutDir => InterpolationOp::ResolveOutDir,
InterpolationOp::ResolveWorkspace => InterpolationOp::ResolveWorkspace,
Expand All @@ -434,7 +449,11 @@ impl SemanticHash for InterpolationOp<'_> {
InterpolationOp::PrependEach(s) | InterpolationOp::AppendEach(s) => s.hash(state),
InterpolationOp::RegexReplace(r) => r.hash(state),
// Covered by discriminant.
InterpolationOp::ResolveOsPath
InterpolationOp::Dedup
| InterpolationOp::Filename
| InterpolationOp::Dirname
| InterpolationOp::Ext
| InterpolationOp::ResolveOsPath
| InterpolationOp::ResolveOutDir
| InterpolationOp::ResolveWorkspace => (),
}
Expand Down
2 changes: 2 additions & 0 deletions werk-parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ impl<'a> Parse<'a> for ast::SubExpr<'a> {
fn expression_chain_op<'a>(input: &mut Input<'a>) -> PResult<ast::ExprOp<'a>> {
alt((
parse.map(ast::ExprOp::SubExpr),
parse.map(ast::ExprOp::StringExpr),
parse.map(ast::ExprOp::Match),
parse.map(ast::ExprOp::Map),
parse.map(ast::ExprOp::Flatten),
Expand All @@ -590,6 +591,7 @@ fn expression_chain_op<'a>(input: &mut Input<'a>) -> PResult<ast::ExprOp<'a>> {
parse.map(ast::ExprOp::Discard),
parse.map(ast::ExprOp::Join),
parse.map(ast::ExprOp::Split),
parse.map(ast::ExprOp::Dedup),
parse.map(ast::ExprOp::Lines),
parse.map(ast::ExprOp::Info),
parse.map(ast::ExprOp::Warn),
Expand Down
19 changes: 13 additions & 6 deletions werk-parser/parser/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,10 @@ fn interpolation_inner_with_stem<'a, const TERMINATE: char>(
// {stem*}, {stem:...}, or {stem*:...}
interpolation_options.map(|options| options.map(Box::new)),
// No options
preceded(space0, peek(TERMINATE)).value(None),
))
.expect(&"interpolation options or end of interpolation"),
preceded(space0, peek(TERMINATE))
.value(None)
.expect(&"interpolation options or end of interpolation"),
)),
)
.map(|(stem, options)| ast::Interpolation { stem, options })
.parse_next(input)
Expand Down Expand Up @@ -427,9 +428,11 @@ fn interpolation_join<'a>(input: &mut Input<'a>) -> PResult<Cow<'a, str>> {

// At least one interpolation option
fn interpolation_ops<'a>(input: &mut Input<'a>) -> PResult<Vec<ast::InterpolationOp<'a>>> {
preceded(':', separated(0.., interpolation_op, ','))
.expect(&"interpolation options")
.parse_next(input)
preceded(
':'.expect(&"interpolation options"),
separated(0.., interpolation_op, ','),
)
.parse_next(input)
}

fn interpolation_op<'a>(input: &mut Input<'a>) -> PResult<ast::InterpolationOp<'a>> {
Expand All @@ -448,6 +451,10 @@ fn interpolation_op_kw<'a>(input: &mut Input<'a>) -> PResult<ast::InterpolationO
let location = input.current_token_start();
let ident = ident_str.parse_next(input)?;
match ident {
"dedup" => Ok(ast::InterpolationOp::Dedup),
"filename" => Ok(ast::InterpolationOp::Filename),
"dir" => Ok(ast::InterpolationOp::Dirname),
"ext" => Ok(ast::InterpolationOp::Ext),
"out-dir" => Ok(ast::InterpolationOp::ResolveOutDir),
"workspace" => Ok(ast::InterpolationOp::ResolveWorkspace),
_ => Err(ModalErr::Error(Error::new(
Expand Down
Loading

0 comments on commit 4e50bdd

Please sign in to comment.