yrahul3910 / pysh

Embed bash in your Python
MIT License
0 stars 0 forks source link

Implement list and dict comprehensions #7

Closed yrahul3910 closed 11 months ago

yrahul3910 commented 1 year ago

There is one major known issue: list and dict comprehensions. Specifically, the following syntax is not yet supported:

numbers = [int`cat {file}` for file in files]

That is, while comprehensions themselves are fine, using template strings within them is not.

yrahul3910 commented 1 year ago

The use case is something of this form:

[`cat {file}` for file in files]
yrahul3910 commented 11 months ago

It might be better to implement something like zx's Promise.all, like so:

map(
    lambda file: `cat {file}`
    files
)

However, this might still be complicated to implement, so instead, an easier solution might be to use the formatter syntax, like so:

all_contents = files.foreach(file)`cat {file}`

This is slightly inspired by C++ lambdas. I'm not sure if this is expressive enough, for example. What if I'd like to mutate my list in-place? The above syntax would call for

files = files.foreach(file)`cat {file}`

A C++-like syntax would instead be closer to:

files.foreach(&file)`cat {file}`

Of course, the two can co-exist, and users have the freedom to stick with the former. This new syntax, while being less Pythonic, has an advantage, however: it allows us to expand the formatter syntax to involve attributes. For example, when we implement #9, we might do

files.foreach[a](&file)`long_process {file}`

where the a attribute means async.

yrahul3910 commented 11 months ago

To address the incoming question of multi-line commands to map, those should be abstracted to a function, as done in progres.

yrahul3910 commented 11 months ago

A C++-like syntax would instead be closer to:

files.foreach(&file)`cat {file}`

The obvious downside, of course, is we now need even uglier syntax to specify a formatter for each of those mapped commands. I suppose we could do something like this:

files.foreach[a](&file):list.str`long_process {file}`

This, sadly, is quite ugly.

yrahul3910 commented 11 months ago

I propose passing (idx, value) pairs to the callable (the user-defined formatter) instead. Moreover, the addition of & and = could complicate things (see below), so it should not be done.

Here is a regex that captures this syntax (regexr).

([a-zA-Z0-9()_.]+)(?:\.(foreach|other)(\[[a-z]+\])?(\([a-zA-Z0-9_]+(?:,\s*?[a-zA-Z0-9_]+)*\))(?::([^`]*))?)?`

where other is a placeholder for other potential future additions.

Internally, we would pass enumerate(<matched first group>). Therefore, the correct usage would be:

files.foreach[a](idx, file):list.str`cat {file}`

For dicts, we could do something similar:

mydict.items().foreach(idx, (key, val)):list.str`cat {key}: {val}`

As to the & and =, it is immediately obvious why this is troublesome: it is invalid to use & with the index, and we don't want the hassle of syntax checking. As such, that will not be implemented.

yrahul3910 commented 11 months ago

Implementing this now. Attributes will be implemented as a separate feature.