Skip to content

Commit

Permalink
Add multidim arr concat impl partials
Browse files Browse the repository at this point in the history
Resolves   #809.
  • Loading branch information
evhub committed Nov 29, 2023
1 parent e2befe6 commit 315b231
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 15 deletions.
33 changes: 21 additions & 12 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1853,25 +1853,34 @@ print(list(map(operator.add, range(0, 5), range(5, 10))))

Coconut supports a number of different syntactical aliases for common partial application use cases. These are:
```coconut
.attr => operator.attrgetter("attr")
.method(args) => operator.methodcaller("method", args)
func$ => ($)$(func)
seq[] => operator.getitem$(seq)
iter$[] => # the equivalent of seq[] for iterators
.[a:b:c] => operator.itemgetter(slice(a, b, c))
.$[a:b:c] => # the equivalent of .[a:b:c] for iterators
```
# attribute access and method calling
.attr1.attr2 => operator.attrgetter("attr1.attr2")
.method(args) => operator.methodcaller("method", args)
.attr.method(args) => .attr ..> .method(args)
# indexing
.[a:b:c] => operator.itemgetter(slice(a, b, c))
.[x][y] => .[x] ..> .[y]
.method[x] => .method ..> .[x]
seq[] => operator.getitem$(seq)
Additionally, `.attr.method(args)`, `.[x][y]`, `.$[x]$[y]`, and `.method[x]` are also supported.
# iterator indexing
.$[a:b:c] => # the equivalent of .[a:b:c] for iterators
.$[x]$[y] => .$[x] ..> .$[y]
iter$[] => # the equivalent of seq[] for iterators
# currying
func$ => ($)$(func)
```

In addition, for every Coconut [operator function](#operator-functions), Coconut supports syntax for implicitly partially applying that operator function as
```
(. <op> <arg>)
(<arg> <op> .)
```
where `<op>` is the operator function and `<arg>` is any expression. Note that, as with operator functions themselves, the parentheses are necessary for this type of implicit partial application.
where `<op>` is the operator function and `<arg>` is any expression. Note that, as with operator functions themselves, the parentheses are necessary for this type of implicit partial application. This syntax is slightly different for multidimensional array concatenation operator functions, which use brackets instead of parentheses.

Additionally, Coconut also supports implicit operator function partials for arbitrary functions as
Furthermore, Coconut also supports implicit operator function partials for arbitrary functions as
```
(. `<name>` <arg>)
(<arg> `<name>` .)
Expand Down Expand Up @@ -2071,7 +2080,7 @@ If multiple different concatenation operators are used, the operators with the l
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
```

_Note: the [operator functions](#operator-functions) for multidimensional array concatenation are spelled `[;]`, `[;;]`, etc. (for any number of parentheses)._
_Note: the [operator functions](#operator-functions) for multidimensional array concatenation are spelled `[;]`, `[;;]`, etc. (with any number of parentheses). The [implicit partials](#implicit-partial-application) are similarly spelled `[. ; x]`, `[x ; .]`, etc._

##### Comparison to Julia

Expand Down
22 changes: 22 additions & 0 deletions coconut/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
attrgetter_atom_handle,
itemgetter_handle,
partial_op_item_handle,
partial_arr_concat_handle,
)
from coconut.compiler.util import (
ExceptionNode,
Expand Down Expand Up @@ -2763,6 +2764,7 @@ def pipe_item_split(self, tokens, loc):
- (name, args) for attr/method
- (attr, [(op, args)]) for itemgetter
- (op, arg) for right op partial
- (op, arg) for right arr concat partial
"""
# list implies artificial tokens, which must be expr
if isinstance(tokens, list) or "expr" in tokens:
Expand Down Expand Up @@ -2792,6 +2794,18 @@ def pipe_item_split(self, tokens, loc):
return "right op partial", (op, arg)
else:
raise CoconutInternalException("invalid op partial tokens in pipe_item", inner_toks)
elif "arr concat partial" in tokens:
inner_toks, = tokens
if "left arr concat partial" in inner_toks:
arg, op = inner_toks
internal_assert(op.lstrip(";") == "", "invalid arr concat op", op)
return "partial", ("_coconut_arr_concat_op", str(len(op)) + ", " + arg, "")
elif "right arr concat partial" in inner_toks:
op, arg = inner_toks
internal_assert(op.lstrip(";") == "", "invalid arr concat op", op)
return "right arr concat partial", (op, arg)
else:
raise CoconutInternalException("invalid arr concat partial tokens in pipe_item", inner_toks)
elif "await" in tokens:
internal_assert(len(tokens) == 1 and tokens[0] == "await", "invalid await pipe item tokens", tokens)
return "await", []
Expand Down Expand Up @@ -2821,6 +2835,8 @@ def pipe_handle(self, original, loc, tokens, **kwargs):
return itemgetter_handle(item)
elif name == "right op partial":
return partial_op_item_handle(item)
elif name == "right arr concat partial":
return partial_arr_concat_handle(item)
elif name == "await":
raise CoconutDeferredSyntaxError("await in pipe must have something piped into it", loc)
else:
Expand Down Expand Up @@ -2889,6 +2905,12 @@ def pipe_handle(self, original, loc, tokens, **kwargs):
raise CoconutDeferredSyntaxError("cannot star pipe into operator partial", loc)
op, arg = split_item
return "({op})({x}, {arg})".format(op=op, x=subexpr, arg=arg)
elif name == "right arr concat partial":
if stars:
raise CoconutDeferredSyntaxError("cannot star pipe into array concatenation operator partial", loc)
op, arg = split_item
internal_assert(op.lstrip(";") == "", "invalid arr concat op", op)
return "_coconut_arr_concat_op({dim}, {x}, {arg})".format(dim=len(op), x=subexpr, arg=arg)
elif name == "await":
internal_assert(not split_item, "invalid split await pipe item tokens", split_item)
if stars:
Expand Down
29 changes: 27 additions & 2 deletions coconut/compiler/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ def partial_op_item_handle(tokens):
raise CoconutInternalException("invalid operator function implicit partial token group", tok_grp)


def partial_arr_concat_handle(tokens):
"""Handle array concatenation operator function implicit partials."""
tok_grp, = tokens
if "left arr concat partial" in tok_grp:
arg, op = tok_grp
internal_assert(op.lstrip(";") == "", "invalid arr concat op", op)
return "_coconut_partial(_coconut_arr_concat_op, " + str(len(op)) + ", " + arg + ")"
elif "right arr concat partial" in tok_grp:
op, arg = tok_grp
internal_assert(op.lstrip(";") == "", "invalid arr concat op", op)
return "_coconut_complex_partial(_coconut_arr_concat_op, {{0: {dim}, 2: {arg}}}, 3, ())".format(dim=len(op), arg=arg)
else:
raise CoconutInternalException("invalid array concatenation operator function implicit partial token group", tok_grp)


def array_literal_handle(loc, tokens):
"""Handle multidimensional array literals."""
internal_assert(len(tokens) >= 1, "invalid array literal tokens", tokens)
Expand Down Expand Up @@ -1071,14 +1086,20 @@ class Grammar(object):
)
partial_op_item = attach(partial_op_item_tokens, partial_op_item_handle)
op_item = (
# partial_op_item must come first, then typedef_op_item must come after base_op_item
# must stay in exactly this order
partial_op_item
| typedef_op_item
| base_op_item
)

partial_op_atom_tokens = lparen.suppress() + partial_op_item_tokens + rparen.suppress()

partial_arr_concat_tokens = lbrack.suppress() + (
labeled_group(dot.suppress() + multisemicolon + test_no_infix + rbrack.suppress(), "right arr concat partial")
| labeled_group(test_no_infix + multisemicolon + dot.suppress() + rbrack.suppress(), "left arr concat partial")
)
partial_arr_concat = attach(partial_arr_concat_tokens, partial_arr_concat_handle)

# we include (var)arg_comma to ensure the pattern matches the whole arg
arg_comma = comma | fixto(FollowedBy(rparen), "")
setarg_comma = arg_comma | fixto(FollowedBy(colon), "")
Expand Down Expand Up @@ -1234,7 +1255,8 @@ class Grammar(object):
list_item = (
lbrack.suppress() + list_expr + rbrack.suppress()
| condense(lbrack + Optional(comprehension_expr) + rbrack)
# array_literal must come last
# partial_arr_concat and array_literal must come last
| partial_arr_concat
| array_literal
)

Expand Down Expand Up @@ -1544,6 +1566,7 @@ class Grammar(object):
| labeled_group(itemgetter_atom_tokens, "itemgetter") + pipe_op
| labeled_group(attrgetter_atom_tokens, "attrgetter") + pipe_op
| labeled_group(partial_op_atom_tokens, "op partial") + pipe_op
| labeled_group(partial_arr_concat_tokens, "arr concat partial") + pipe_op
# expr must come at end
| labeled_group(comp_pipe_expr, "expr") + pipe_op
)
Expand All @@ -1554,6 +1577,7 @@ class Grammar(object):
| labeled_group(itemgetter_atom_tokens, "itemgetter") + end_simple_stmt_item
| labeled_group(attrgetter_atom_tokens, "attrgetter") + end_simple_stmt_item
| labeled_group(partial_op_atom_tokens, "op partial") + end_simple_stmt_item
| labeled_group(partial_arr_concat_tokens, "arr concat partial") + end_simple_stmt_item
)
last_pipe_item = Group(
lambdef("expr")
Expand All @@ -1564,6 +1588,7 @@ class Grammar(object):
attrgetter_atom_tokens("attrgetter"),
partial_atom_tokens("partial"),
partial_op_atom_tokens("op partial"),
partial_arr_concat_tokens("arr concat partial"),
comp_pipe_expr("expr"),
)
)
Expand Down
2 changes: 1 addition & 1 deletion coconut/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
VERSION = "3.0.4"
VERSION_NAME = None
# False for release, int >= 1 for develop
DEVELOP = 3
DEVELOP = 4
ALPHA = False # for pre releases rather than post releases

assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1"
Expand Down
9 changes: 9 additions & 0 deletions coconut/tests/src/cocotest/agnostic/primary_2.coco
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,15 @@ def primary_test_2() -> bool:
assert 0xff == 255 == 0x100-1
assert 11259375 == 0xabcdef
assert [[] ;; [] ;;;] == [[[], []]]
assert (
1
|> [. ; 2]
|> [[3; 4] ;; .]
) == [3; 4;; 1; 2] == [[3; 4] ;; .]([. ; 2](1))
arr: Any = 1
arr |>= [. ; 2]
arr |>= [[3; 4] ;; .]
assert arr == [3; 4;; 1; 2] == [[3; 4] ;; .] |> call$(?, [. ; 2] |> call$(?, 1))

with process_map.multiple_sequential_calls(): # type: ignore
assert map((+), range(3), range(4)$[:-1], strict=True) |> list == [0, 2, 4] == process_map((+), range(3), range(4)$[:-1], strict=True) |> list # type: ignore
Expand Down

0 comments on commit 315b231

Please sign in to comment.