Compare commits

...

10 Commits

Author SHA1 Message Date
Axel Liljencrantz
e177a02fd5 AST/parsing cleanup 2022-09-06 01:18:00 +02:00
Axel Liljencrantz
8a94efe6b6 Naming 2022-09-05 15:59:20 +02:00
Axel Liljencrantz
60fd099edc Minor refactoring 2022-09-05 15:32:31 +02:00
Axel Liljencrantz
cd2710c8da Fix pup invocation bug 2022-09-03 11:23:06 +02:00
Axel Liljencrantz
14383e2d5d Support streaming globs 2022-09-02 22:10:27 +02:00
Axel Liljencrantz
8830644a66 Drop weird file fallback feature 2022-09-02 21:20:35 +02:00
Axel Liljencrantz
8d96142ac9 Change syntax for the whole language... :-/ 2022-09-02 21:03:01 +02:00
Axel Liljencrantz
ecba86aaac Simplify command invocation code 2022-09-01 19:11:56 +02:00
Axel Liljencrantz
2997cfb062 Simplify command invocation code 2022-08-31 18:36:54 +02:00
Axel Liljencrantz
11e09d09c5 Fix various context bugs 2022-08-31 18:06:19 +02:00
105 changed files with 2221 additions and 1734 deletions

1210
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -56,6 +56,7 @@ psutil = "3.2.2"
num-format = { version = "0.4", features = ["with-system-locale"] } num-format = { version = "0.4", features = ["with-system-locale"] }
unicode-width = "0.1.5" unicode-width = "0.1.5"
os_pipe = "0.9.2" os_pipe = "0.9.2"
uptime_lib = "0.2.2"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
dbus = "0.8.4" dbus = "0.8.4"

View File

@ -39,14 +39,14 @@ Crush code in the interactive Crush prompt. Assign ANSI color codes
to the various token types of Crush to make your terminal more closely to the various token types of Crush to make your terminal more closely
resemble a Christmas tree: resemble a Christmas tree:
| Name | Description | | Name | Description |
| --- | --- | | --- |----------------------------------------------------------|
| `operator` | All the different Crush operators, such as `neg` and `+` | | `operator` | All the different Crush operators, such as `neg` and `+` |
| `numeric_literal` | Integer and floating point literals, such as `6` | | `numeric_literal` | Integer and floating point literals, such as `6` |
| `string_literal` | String literals, like `"Burrow"` | | `string_literal` | String literals, like `"Burrow"` |
| `file_literal` | File literals, like `'Cargo.toml'` | | `file_literal` | File literals, like `'Cargo.toml'` |
| `label` | Variables and members, like `global` | | `label` | Variables and members, like `$global` |
| `field` | Field definitions, such as `^name` | | `field` | Field definitions, such as `name` |
The `term` namespace contains useful constants containing ANSI color codes. The `term` namespace contains useful constants containing ANSI color codes.
A configuration example: A configuration example:

View File

@ -13,8 +13,8 @@ what I felt was a more suitable type system.
## Similarity to Nushell ## Similarity to Nushell
On the surface, Crush looks identical to nushell, but less polished. Crush lacks On the surface, Crush looks identical to nushell, but less polished. Crush has
syntax highlighting, tab completion and has a worse screen rendering. But that less polished syntax highlighting, tab completion and screen rendering. But that
is because the focus of Crush right now is to create a well defined, powerful is because the focus of Crush right now is to create a well defined, powerful
and convenient language that supports things like arithmetic operations, and convenient language that supports things like arithmetic operations,
closures, loops and flow control while remaining useful for interactive use. closures, loops and flow control while remaining useful for interactive use.

View File

@ -36,7 +36,7 @@ a Rush channel. It is not understood by the command as a series of bytes, but as
a table of rows, and Crush provides you with SQL-like commands to sort, filter, a table of rows, and Crush provides you with SQL-like commands to sort, filter,
aggregate and group rows of data. aggregate and group rows of data.
crush# ll | sort ^size crush# ll | sort size
user size modified type file user size modified type file
fox 31 2019-10-03 13:43:12 +0200 file .gitignore fox 31 2019-10-03 13:43:12 +0200 file .gitignore
fox 75 2020-03-07 17:09:15 +0100 file build.rs fox 75 2020-03-07 17:09:15 +0100 file build.rs
@ -44,7 +44,7 @@ aggregate and group rows of data.
fox 711 2019-10-03 14:19:46 +0200 file crush.iml fox 711 2019-10-03 14:19:46 +0200 file crush.iml
... ...
crush# ll | where {type == "directory"} crush# ll | where {$type == "directory"}
user size modified type file user size modified type file
fox 4_096 2019-11-22 21:56:30 +0100 directory target fox 4_096 2019-11-22 21:56:30 +0100 directory target
fox 4_096 2020-02-22 11:50:12 +0100 directory tests fox 4_096 2020-02-22 11:50:12 +0100 directory tests
@ -84,10 +84,10 @@ commands all work like you'd expect:
crush# ls | json:to ./listing.json crush# ls | json:to ./listing.json
# Read the file Cargo.toml as a toml file, and extract the dependencies-field # Read the file Cargo.toml as a toml file, and extract the dependencies-field
crush# toml:from Cargo.toml | member ^dependencies crush# toml:from Cargo.toml | member dependencies
# Fetch a web page and write it to a file # Fetch a web page and write it to a file
http "https://isitchristmas.com/" | member ^body | bin:to ./isitchristmas.html http "https://isitchristmas.com/" | member body | bin:to ./isitchristmas.html
``` ```
If you don't supply an input file to any of the deserializer commands, If you don't supply an input file to any of the deserializer commands,
@ -109,7 +109,7 @@ crush# list:of "carrot" "carrot" "acorn" | json:to
One of the Crush serializers, Pup, is a native file format for Crush. The One of the Crush serializers, Pup, is a native file format for Crush. The
Pup-format is protobuf-based, and its schema is available Pup-format is protobuf-based, and its schema is available
[here](src/crush.proto). The advantage of Pup is that all crush types, [here](../src/crush.proto). The advantage of Pup is that all crush types,
including classes and closures, can be losslessly serialized into this format. including classes and closures, can be losslessly serialized into this format.
But because Pup is Crush-specific, it's useless for data sharing to But because Pup is Crush-specific, it's useless for data sharing to
other languages. other languages.
@ -142,7 +142,7 @@ are false.
The `and` and `or` operators are used to combine logical expressions: The `and` and `or` operators are used to combine logical expressions:
crush# false or true crush# $false or $true
true true
crush# if (./tree:exists) and ((./tree:stat):is_file) {echo "yay"} crush# if (./tree:exists) and ((./tree:stat):is_file) {echo "yay"}
@ -183,16 +183,17 @@ current working directory as a single element of the `file` type.
### Variables of any type ### Variables of any type
Variables must be declared (using the `:=` operator) before use. Use the $ sigil to refer to variables. Variables must be declared
(using the `:=` operator) before use.
crush# some_number := 4 # The := operator declares a new variable crush# $some_number := 4 # The := operator declares a new variable
crush# some_number * 5 crush# $some_number * 5
20 20
Once declared, a variable can be reassigned to using the `=` operator. Once declared, a variable can be reassigned to using the `=` operator.
crush# some_number = 6 crush# $some_number = 6
crush# some_number * 5 crush# $some_number * 5
30 30
Like in any sane programming language, variables can be of any type supported by Like in any sane programming language, variables can be of any type supported by
@ -218,13 +219,14 @@ the other or a combination of both. The following three invocations are equivale
It is quite common to want to pass boolean arguments to commands, which is why It is quite common to want to pass boolean arguments to commands, which is why
Crush has a special shorthand syntax for it. Passing in `--foo` is equivalent Crush has a special shorthand syntax for it. Passing in `--foo` is equivalent
to passing in `foo=true`. to passing in `foo=$true`.
### Subshells ### Subshells
Sometimes you want to use the output of one command as an *argument* to another Sometimes you want to use the output of one command as an *argument* to another
command, just like a subshell in e.g. bash. This is different from using the command, just like a subshell in e.g. bash. This is different from what a pipe does,
output as the *input*, and is done by putting the command within parenthesis (`()`), like so: which is using the output as the *input*, and is done by putting the command within
parenthesis (`()`), like so:
crush# echo (pwd) crush# echo (pwd)
@ -233,14 +235,14 @@ output as the *input*, and is done by putting the command within parenthesis (`(
In Crush, braces (`{}`) are used to create a closure. Assigning a closure to a In Crush, braces (`{}`) are used to create a closure. Assigning a closure to a
variable is how you create a function. variable is how you create a function.
crush# print_greeting := {echo "Hello"} crush# $print_greeting := {echo "Hello"}
crush# print_greeting crush# print_greeting
Hello Hello
Any named arguments passed when calling a closure and added to the local scope Any named arguments passed when calling a closure and added to the local scope
of the invocation: of the invocation:
crush# print_a := {echo a} crush# $print_a := {echo $a}
crush# print_a a="Greetings" crush# print_a a="Greetings"
Greetings Greetings
@ -251,13 +253,13 @@ The following closure requires the caller to supply the argument `a`, and allows
the caller to specify the argument `b`, which must by of type integer. If the the caller to specify the argument `b`, which must by of type integer. If the
caller does not specify it, it falls back to a default value of 7. caller does not specify it, it falls back to a default value of 7.
crush# print_things := {|a b: integer = 7|} crush# print_things := {|a b: $integer = 7|}
Additionally, the `@` operator can be used to create a list of all unnamed Additionally, the `@` operator can be used to create a list of all unnamed
arguments, and the `@@` operator can be used to create a list of all named arguments, and the `@@` operator can be used to create a list of all named
arguments not mentioned elsewhere in the parameter list. arguments not mentioned elsewhere in the parameter list.
crush# print_everything := {|@unnamed @@named| echo "Named" named "Unnamed" unnamed} crush# print_everything := {|@unnamed @@named| echo "Named" $named "Unnamed" $unnamed}
The `@` and `@@` operators are also used during command invocation to perform The `@` and `@@` operators are also used during command invocation to perform
the mirrored operation. The following code creates an `lss` function that calls the mirrored operation. The following code creates an `lss` function that calls
@ -271,7 +273,7 @@ the `select` command to only show one column from the output.
Crush comes with a variety of types: Crush comes with a variety of types:
* lists of any type, * lists of any type,
* dicts of any pair of types type, (Some types can not be used as keys!) * dicts of a pair of types, (Some types can not be used as keys!)
* strings, * strings,
* regular expressions, * regular expressions,
* globs, * globs,
@ -295,15 +297,15 @@ commands.
When playing around with Crush, the `help` and `dir`commands are useful. The When playing around with Crush, the `help` and `dir`commands are useful. The
former displays a help messages, the latter lists the content of a value. former displays a help messages, the latter lists the content of a value.
crush# help crush# help $sort
sort column:field sort column:field
Sort input based on column Sort input based on column
Example: Example:
ps | sort ^cpu ps | sort cpu
crush# dir list crush# dir $list
[type, truncate, remove, clone, of, __call__, __setitem__, pop, push, empty, len, peek, new, clear] [type, truncate, remove, clone, of, __call__, __setitem__, pop, push, empty, len, peek, new, clear]
### The content of your current working directory lives in your namespace ### The content of your current working directory lives in your namespace
@ -347,21 +349,21 @@ what you'd expect.
If you assign the output of the find command to a variable like so: If you assign the output of the find command to a variable like so:
crush# all_the_files := (find /) crush# $all_the_files := (find /)
What will really be stored in the `all_the_files` variable is simply a stream. A What will really be stored in the `all_the_files` variable is simply a stream. A
small number of lines of output will be eagerly evaluated, before the thread small number of lines of output will be eagerly evaluated, before the thread
executing the find command will start blocking. If the stream is consumed, for executing the find command will start blocking. If the stream is consumed, for
example by writing example by writing
crush# all_the_files crush# $all_the_files
then all hell will break loose on your screen as tens of thousands of lines are then all hell will break loose on your screen as tens of thousands of lines are
printed to your screen. printed to your screen.
Another option would be to pipe the output via the head command Another option would be to pipe the output via the head command
crush# all_the_files | head 1 crush# $all_the_files | head 1
Which will consume one line of output from the stream. This command can be Which will consume one line of output from the stream. This command can be
re-executed until the stream is empty. re-executed until the stream is empty.
@ -369,10 +371,10 @@ re-executed until the stream is empty.
### More SQL-like data stream operations ### More SQL-like data stream operations
Crush features many commands to operate om arbitrary streams of data using a Crush features many commands to operate om arbitrary streams of data using a
SQL-like syntax. These commands use field-specifiers like `^foo` to specify SQL-like syntax. These commands use field-specifiers like `foo` to specify
columns in the data stream that they operate on: columns in the data stream that they operate on:
ps | where {user == "root"} | group ^status proc_per_status={count} | sort ^proc_per_status ps | where {$user == "root"} | group status proc_per_status={count} | sort proc_per_status
status proc_per_status status proc_per_status
Idle 108 Idle 108
Sleeping 170 Sleeping 170
@ -395,7 +397,7 @@ operator instead. `?` is still used for single character wildcards.
The operator `%%` is used for performing globbing recursively into subdirectories. The operator `%%` is used for performing globbing recursively into subdirectories.
Another way of looking ath the same syntax is to say that `%` and `?` match any Another way of looking ath the same syntax is to say that `%` and `?` match any
character except `/`, whereas `%%` also matches `/`. character except `/`, whereas `%%` matches any character including `/`.
# Count the number of lines of rust code in the crush source code # Count the number of lines of rust code in the crush source code
crush# lines src/%%.rs|count crush# lines src/%%.rs|count
@ -506,8 +508,8 @@ computers memory, and even infinite data sets.
But sometimes, streaming data sets are inconvenient, especially if one wants to But sometimes, streaming data sets are inconvenient, especially if one wants to
use the same dataset twice. use the same dataset twice.
crush# files := ls crush# $files := ls
crush# files crush# $files
user size modified type file user size modified type file
fox 1_307 2020-03-26 01:08:45 +0100 file ideas fox 1_307 2020-03-26 01:08:45 +0100 file ideas
fox 4_096 2019-11-22 21:56:30 +0100 directory target fox 4_096 2019-11-22 21:56:30 +0100 directory target
@ -522,17 +524,17 @@ use the same dataset twice.
fox 8_382 2020-03-29 00:54:13 +0100 file todo fox 8_382 2020-03-29 00:54:13 +0100 file todo
fox 75 2020-03-07 17:09:15 +0100 file build.rs fox 75 2020-03-07 17:09:15 +0100 file build.rs
fox 711 2019-10-03 14:19:46 +0200 file crush.iml fox 711 2019-10-03 14:19:46 +0200 file crush.iml
crush# files crush# $files
Notice how there is no output the second time the content of the `files` variable is Notice how there is no output the second time the content of the `files` variable is
displayed, because the table_input_stream has already been consumed. displayed, because the table_input_stream has already been consumed.
Enter the materialize command, which takes any value and recursively converts Enter the materialize command, which takes any value and recursively converts
all transient (table_input_stream and binary_stream) components into an equivalent all transient (table_input_stream and binary_stream) components into an equivalent
in-memory form (stable, and binary, respectively). in-memory form (table, and binary, respectively).
crush# materialized_files := (ls|materialize) crush# $materialized_files := (ls|materialize)
crush# materialized_files crush# $materialized_files
user size modified type file user size modified type file
fox 1307 2020-03-26 01:08:45 +0100 file ideas fox 1307 2020-03-26 01:08:45 +0100 file ideas
fox 4096 2019-11-22 21:56:30 +0100 directory target fox 4096 2019-11-22 21:56:30 +0100 directory target
@ -547,7 +549,7 @@ in-memory form (stable, and binary, respectively).
fox 8_382 2020-03-29 00:54:13 +0100 file todo fox 8_382 2020-03-29 00:54:13 +0100 file todo
fox 75 2020-03-07 17:09:15 +0100 file build.rs fox 75 2020-03-07 17:09:15 +0100 file build.rs
fox 711 2019-10-03 14:19:46 +0200 file crush.iml fox 711 2019-10-03 14:19:46 +0200 file crush.iml
crush# materialized_files crush# $materialized_files
user size modified type file user size modified type file
fox 1307 2020-03-26 01:08:45 +0100 file ideas fox 1307 2020-03-26 01:08:45 +0100 file ideas
fox 4096 2019-11-22 21:56:30 +0100 directory target fox 4096 2019-11-22 21:56:30 +0100 directory target
@ -563,7 +565,7 @@ in-memory form (stable, and binary, respectively).
fox 75 2020-03-07 17:09:15 +0100 file build.rs fox 75 2020-03-07 17:09:15 +0100 file build.rs
fox 711 2019-10-03 14:19:46 +0200 file crush.iml fox 711 2019-10-03 14:19:46 +0200 file crush.iml
When the `table_input_stream` is materialized into a `table`, it can be displayed When the `table_input_stream` is materialized into a `table`, it can be accessed
multiple times. multiple times.
### Flow control ### Flow control
@ -612,13 +614,10 @@ non-interactive commands work as expected:
Crush features several shortcuts to make working with external commands easier. Crush features several shortcuts to make working with external commands easier.
* Firstly, subcommands like `git status` are mapped into method calls like * Named arguments are transparently translated into options. Single
`git:status`. That way you do not have to quote the subcommand name, e.g.
`git "status"`.
* Secondly, named arguments are transparently translated into options. Single
character argument names are turned into options with a single hyphen, and character argument names are turned into options with a single hyphen, and
multi-character argument names are turned into GNU style long options with multi-character argument names are turned into GNU style long options with
two hyphens, e.g. `git:commit m="hello"` is converted into two hyphens, e.g. `git commit m="hello"` is converted into
`git commit -m "hello"` and `git:commit message="hello"` is converted into `git commit -m "hello"` and `git:commit message="hello"` is converted into
`git commit --message "hello"`. `git commit --message "hello"`.
* Thirdly, named arguments with a value of boolean true are simply turned into * Thirdly, named arguments with a value of boolean true are simply turned into
@ -661,26 +660,26 @@ To run a closure on multiple remote hosts, use `remote:pexec` instead.
You can create custom types in Crush, by using the class command: You can create custom types in Crush, by using the class command:
Point := (class) $Point := (class)
Point:__init__ = { $Point:__init__ = {
|x:float y:float| |$x:$float $y:$float|
this:x = x $this:x = $x
this:y = y $this:y = $y
} }
Point:len = { $Point:len = {
|| ||
math:sqrt this:x*this:x + this:y*this:y math:sqrt $this:x*$this:x + $this:y*$this:y
} }
Point:__add__ = { $Point:__add__ = {
|other| |$other|
Point:new x=(this:x + other:x) y=(this:y + other:y) Point:new x=($this:x + $other:x) y=($this:y + $other:y)
} }
p := (Point:new x=1.0 y=2.0) $p := (Point:new x=1.0 y=2.0)
p:len $p:len
Crush supports single inheritance (by passing in the parent to the class Crush supports single inheritance (by passing in the parent to the class
command). The class command will create a new struct, that contains a method command). The class command will create a new struct, that contains a method

View File

@ -37,7 +37,7 @@ input and produce a single value as output. The input and output of a command
is passed down via a so called pipeline: is passed down via a so called pipeline:
```shell script ```shell script
ps | sort ^cpu ps | sort cpu
``` ```
Many commands consume and produce table streams as input and output. These commands Many commands consume and produce table streams as input and output. These commands
@ -54,18 +54,25 @@ A character sequence enclosed within double quotes become a string literal value
e.g. `"hello"`. e.g. `"hello"`.
A character sequence enclosed within single quotes become a file literal, e.g. A character sequence enclosed within single quotes become a file literal, e.g.
`'Cargo.yaml'`, i.e. a value of the type `file`. A character sequence that begins `'Cargo.yaml'`, i.e. a value of the type `file`. An unquoted character sequence
with a dot (`.`), a tilde (`~`) or slash (`/`) is also interpreted as a file literal, e.g. that contains a dot (`.`) or a slash ('/'), or begins with a tilde (`~`) is also
`./Cargo.yam` or `/etc`. interpreted as a file literal, e.g. `Cargo.yaml` or `~/.ssh`.
A character sequence starting with a caret (`^`) becomes a field literal, e.g. A character sequence starting with a caret (`$`) is intepreted as a variable
`^user`. Field literals are used by some Crush commands to specify specific columns lookup. The first sequewnce in a command (i.e. the command name) is interpreted
in a stream. For example, to tell the sort command to sort the input stream by the as a variable lookup even without the leading `$`. Commands live in
column `user`, one would write `sort ^user`.
Other character sequences are interpreted as a variable lookup. Commands live in
the same namespace as all other variables. the same namespace as all other variables.
All other unquoted character sequences are field literals, e.g.
`user` or `hat`. Field literals are basically short strings. They
are used by some Crush commands to specify specific columns
in a stream. For example, to tell the sort command to sort the
input stream by the column `user`, one would write `sort user`.
Commands that expect files as argument, like `cd` or `find`,
generally accept fields as files. They also accept globs and
regular expression literals (see below), and expand them into
file names.
## Operators ## Operators
Crush features a number of operators to enable users to write mathematical Crush features a number of operators to enable users to write mathematical
@ -95,8 +102,9 @@ command. To do this, simply put the command within parenthesis:
if the command that you run in a substitution returns a stream, the outer command will if the command that you run in a substitution returns a stream, the outer command will
be run in parallel with the substitution and may in fact finish running first. be run in parallel with the substitution and may in fact finish running first.
This example will create a command that locates all the files in your computer, and assign This example will create a command that locates all the files in your computer, and assigns
the output stream to a variable. the output stream to a variable. The `find` command in this example will block because there
is nothing reading its output.
```shell script ```shell script
all_the_files := (find /) all_the_files := (find /)
@ -113,7 +121,8 @@ A few of the namespaces in Crush are:
* the `crush` namespace, which contains runtime information about the current shell * the `crush` namespace, which contains runtime information about the current shell
as well as methods and variables that allow you to reconfigure it, as well as methods and variables that allow you to reconfigure it,
* the `fd` namespace, which contains information about file descriptors, e.g. all * the `user` namespace which contains information about all the users of this system,
open network sockets, unix sockets and open files. * the `fd` namespace, which contains information about open file descriptors, e.g. all
open network sockets, unix sockets and open files, and
* the `host` namespace, which contains information about the current host, including * the `host` namespace, which contains information about the current host, including
host name, CPU status, memory usage and operating system. host name, CPU status, memory usage and operating system.

View File

@ -79,7 +79,7 @@ fn extract_type(ty: &Type) -> SignatureResult<(&'static str, Vec<&'static str>)>
"Command" => "Command", "Command" => "Command",
"Duration" => "Duration", "Duration" => "Duration",
"Struct" => "Struct", "Struct" => "Struct",
"Field" => "Field", "Symbol" => "Symbol",
"Value" => "Value", "Value" => "Value",
"Stream" => "Stream", "Stream" => "Stream",
"Number" => "Number", "Number" => "Number",
@ -169,7 +169,7 @@ fn simple_type_to_value(simple_type: &str) -> TokenStream {
"char" => quote! {crate::lang::value::Value::String(_value)}, "char" => quote! {crate::lang::value::Value::String(_value)},
"Command" => quote! {crate::lang::value::Value::Command(_value)}, "Command" => quote! {crate::lang::value::Value::Command(_value)},
"Duration" => quote! {crate::lang::value::Value::Duration(_value)}, "Duration" => quote! {crate::lang::value::Value::Duration(_value)},
"Field" => quote! {crate::lang::value::Value::Field(_value)}, "Symbol" => quote! {crate::lang::value::Value::Symbol(_value)},
"Struct" => quote! {crate::lang::value::Value::Struct(_value)}, "Struct" => quote! {crate::lang::value::Value::Struct(_value)},
"Stream" => quote! {_value}, "Stream" => quote! {_value},
"Value" => quote! {_value}, "Value" => quote! {_value},
@ -192,7 +192,7 @@ fn simple_type_to_value_type(simple_type: &str) -> TokenStream {
"char" => quote! {crate::lang::value::ValueType::String}, "char" => quote! {crate::lang::value::ValueType::String},
"Command" => quote! {crate::lang::value::ValueType::Command}, "Command" => quote! {crate::lang::value::ValueType::Command},
"Duration" => quote! {crate::lang::value::ValueType::Duration}, "Duration" => quote! {crate::lang::value::ValueType::Duration},
"Field" => quote! {crate::lang::value::ValueType::Field}, "Symbol" => quote! {crate::lang::value::ValueType::Symbol},
"Struct" => quote! {crate::lang::value::ValueType::Struct}, "Struct" => quote! {crate::lang::value::ValueType::Struct},
_ => quote! {crate::lang::value::ValueType::Any}, _ => quote! {crate::lang::value::ValueType::Any},
} }
@ -213,7 +213,7 @@ fn simple_type_to_value_description(simple_type: &str) -> &str {
"char" => "string", "char" => "string",
"Command" => "command", "Command" => "command",
"Duration" => "duration", "Duration" => "duration",
"Field" => "field", "Symbol" => "symbol",
"Value" => "any value", "Value" => "any value",
"Stream" => "stream", "Stream" => "stream",
"Struct" => "Struct", "Struct" => "Struct",
@ -241,7 +241,7 @@ fn simple_type_to_mutator(simple_type: &str, allowed_values: &Option<Ident>) ->
"Stream" => { "Stream" => {
quote! { quote! {
crate::lang::errors::mandate_argument( crate::lang::errors::mandate_argument(
_value.stream(), _value.stream()?,
"Expected a type that can be streamed", "Expected a type that can be streamed",
_location)?, _location)?,
} }
@ -299,7 +299,7 @@ fn simple_type_dump_list(simple_type: &str) -> &str {
"ValueType" => "dump_type", "ValueType" => "dump_type",
"f64" => "dump_float", "f64" => "dump_float",
"Value" => "dump_value", "Value" => "dump_value",
"Field" => "dump_field", "Symbol" => "dump_symbol",
_ => panic!("Unknown type"), _ => panic!("Unknown type"),
} }
} }
@ -320,7 +320,7 @@ fn parse_type_data(
let (type_name, args) = extract_type(ty)?; let (type_name, args) = extract_type(ty)?;
match type_name { match type_name {
"i128" | "bool" | "String" | "char" | "ValueType" | "f64" | "Command" | "Duration" "i128" | "bool" | "String" | "char" | "ValueType" | "f64" | "Command" | "Duration"
| "Field" | "Value" | "usize" | "i64" | "u64" | "i32" | "u32" | "Stream" | "Struct" => { | "Symbol" | "Value" | "usize" | "i64" | "u64" | "i32" | "u32" | "Stream" | "Struct" => {
if !args.is_empty() { if !args.is_empty() {
fail!(ty.span(), "This type can't be paramterizised") fail!(ty.span(), "This type can't be paramterizised")
} else { } else {

View File

@ -24,7 +24,7 @@ message Element {
Closure closure = 9; // A Value::Command that is a closure Closure closure = 9; // A Value::Command that is a closure
bytes binary = 10; // A Value::binary bytes binary = 10; // A Value::binary
Duration duration = 11; // A Value::Duration Duration duration = 11; // A Value::Duration
uint64 field = 12; // A Value::Field string symbol = 12; // A Value::Symbol
string glob = 13; // A Value::Glob string glob = 13; // A Value::Glob
string regex = 14; // A Value::RegEx string regex = 14; // A Value::RegEx
Scope user_scope = 15; // A Value::Scope Scope user_scope = 15; // A Value::Scope
@ -204,7 +204,7 @@ message Type {
COMMAND = 4; COMMAND = 4;
BINARY = 5; BINARY = 5;
DURATION = 6; DURATION = 6;
FIELD = 7; SYMBOL = 7;
GLOB = 8; GLOB = 8;
REGEX = 9; REGEX = 9;
SCOPE = 10; SCOPE = 10;

View File

@ -1,23 +1,21 @@
ls := { $ls := {
|sort_by:field=^file @args| |@ $args |
"List names of files non-recursively" "List names of files non-recursively"
" Unlike find and ll, ls only shows you the names of files. " Unlike find and ll, ls only shows you the names of files.
sort_by can be one of ^user, ^size, ^modified, ^type or ^file.
Example: Example:
ls / sort_by=^size" ls /"
find recursive=false @args | sort sort_by| select ^file find recursive=$false @ $args | sort file| select file
} }
ll := { $ll := {
|sort_by:field=^file @args| |@ $args|
"List files non-recursively" "List files non-recursively"
" sort_by can be one of ^user, ^size, ^modified, ^type or ^file. "
Example: Example:
ll .. sort_by=^modified" ll .."
find recursive=false @args | sort sort_by find recursive=$false @ $args | sort file
} }

View File

@ -1,9 +1,14 @@
use crate::lang::errors::{argument_error_legacy, error, CrushResult, argument_error}; /**
Code for managing arguments passed in to commands
*/
use crate::lang::errors::{argument_error, argument_error_legacy, CrushResult, error};
use crate::lang::execution_context::CompileContext; use crate::lang::execution_context::CompileContext;
use crate::lang::value::Value; use crate::lang::value::Value;
use crate::lang::value::ValueDefinition; use crate::lang::value::ValueDefinition;
use std::collections::HashSet; use std::collections::HashSet;
use crate::lang::ast::{TrackedString, Location}; use std::fmt::{Display, Formatter};
use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::ast::location::Location;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ArgumentType { pub enum ArgumentType {
@ -112,35 +117,35 @@ impl Argument {
} }
} }
pub trait ArgumentVecCompiler { pub trait ArgumentEvaluator {
fn compile(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)>; fn eval(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)>;
} }
impl ArgumentVecCompiler for Vec<ArgumentDefinition> { impl ArgumentEvaluator for Vec<ArgumentDefinition> {
fn compile(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)> { fn eval(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)> {
let mut this = None; let mut this = None;
let mut res = Vec::new(); let mut res = Vec::new();
for a in self { for a in self {
if a.argument_type.is_this() { if a.argument_type.is_this() {
this = Some(a.value.compile_bound(context)?); this = Some(a.value.eval_and_bind(context)?);
} else { } else {
match &a.argument_type { match &a.argument_type {
ArgumentType::Some(name) => { ArgumentType::Some(name) => {
res.push(Argument::named( res.push(Argument::named(
&name.string, &name.string,
a.value.compile_bound(context)?, a.value.eval_and_bind(context)?,
a.location, a.location,
)) ))
} }
ArgumentType::None => { ArgumentType::None => {
res.push(Argument::unnamed( res.push(Argument::unnamed(
a.value.compile_bound(context)?, a.value.eval_and_bind(context)?,
a.location, a.location,
)) ))
} }
ArgumentType::ArgumentList => match a.value.compile_bound(context)? { ArgumentType::ArgumentList => match a.value.eval_and_bind(context)? {
Value::List(l) => { Value::List(l) => {
let mut copy = l.dump(); let mut copy = l.dump();
for v in copy.drain(..) { for v in copy.drain(..) {
@ -153,7 +158,7 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
_ => return argument_error_legacy("Argument list must be of type list"), _ => return argument_error_legacy("Argument list must be of type list"),
}, },
ArgumentType::ArgumentDict => match a.value.compile_bound(context)? { ArgumentType::ArgumentDict => match a.value.eval_and_bind(context)? {
Value::Dict(d) => { Value::Dict(d) => {
let mut copy = d.elements(); let mut copy = d.elements();
for (key, value) in copy.drain(..) { for (key, value) in copy.drain(..) {
@ -177,6 +182,13 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
} }
} }
impl Display for ArgumentDefinition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
pub fn column_names(arguments: &Vec<Argument>) -> Vec<String> { pub fn column_names(arguments: &Vec<Argument>) -> Vec<String> {
let mut taken = HashSet::new(); let mut taken = HashSet::new();
taken.insert("_".to_string()); taken.insert("_".to_string());

28
src/lang/ast/location.rs Normal file
View File

@ -0,0 +1,28 @@
use std::cmp::{max, min};
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub struct Location {
pub start: usize,
pub end: usize,
}
impl Location {
pub fn new(start: usize, end: usize) -> Location {
Location { start, end }
}
pub fn union(&self, other: Location) -> Location {
Location {
start: min(self.start, other.start),
end: max(self.end, other.end),
}
}
pub fn contains(&self, cursor: usize) -> bool {
cursor >= self.start && cursor <= self.end
}
pub fn len(&self) -> usize {
self.end - self.start
}
}

View File

@ -1,7 +1,7 @@
use crate::lang::argument::ArgumentDefinition; use crate::lang::argument::ArgumentDefinition;
use crate::lang::command::{Command, Parameter}; use crate::lang::command::{Command, Parameter};
use crate::lang::command_invocation::CommandInvocation; use crate::lang::command_invocation::CommandInvocation;
use crate::lang::errors::{error, to_crush_error, CrushResult}; use crate::lang::errors::{CrushResult, error, to_crush_error};
use crate::lang::job::Job; use crate::lang::job::Job;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::value::{Value, ValueDefinition, ValueType}; use crate::lang::value::{Value, ValueDefinition, ValueType};
@ -10,9 +10,36 @@ use regex::Regex;
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::cmp::{min, max}; use std::cmp::{max, min};
use location::Location;
use tracked_string::TrackedString;
use crate::util::escape::unescape; use crate::util::escape::unescape;
pub mod location;
pub mod tracked_string;
/**
A type representing a node in the abstract syntax tree that is the output of parsing a Crush script.
*/
#[derive(Clone, Debug)]
pub enum Node {
Assignment(Box<Node>, String, Box<Node>),
Unary(TrackedString, Box<Node>),
Glob(TrackedString),
Identifier(TrackedString),
Regex(TrackedString),
Field(TrackedString),
String(TrackedString),
File(TrackedString, bool), // true if filename is quoted
Integer(TrackedString),
Float(TrackedString),
GetItem(Box<Node>, Box<Node>),
GetAttr(Box<Node>, TrackedString),
Path(Box<Node>, TrackedString),
Substitution(JobNode),
Closure(Option<Vec<ParameterNode>>, JobListNode),
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct JobListNode { pub struct JobListNode {
pub jobs: Vec<JobNode>, pub jobs: Vec<JobNode>,
@ -166,7 +193,7 @@ impl CommandNode {
error("Stray arguments") error("Stray arguments")
} }
} else { } else {
let cmd = self.expressions[0].generate_argument(env)?; let cmd = self.expressions[0].generate_command(env)?;
let arguments = self.expressions[1..] let arguments = self.expressions[1..]
.iter() .iter()
.map(|e| e.generate_argument(env)) .map(|e| e.generate_argument(env))
@ -176,99 +203,6 @@ impl CommandNode {
} }
} }
#[derive(Clone, Debug)]
pub struct TrackedString {
pub string: String,
pub location: Location,
}
impl TrackedString {
pub fn from(string: &str, location: Location) -> TrackedString {
TrackedString {
string: string.to_string(),
location,
}
}
pub fn literal(start: usize, string: &str, end: usize) -> TrackedString {
TrackedString {
string: string.to_string(),
location: Location::new(start, end),
}
}
pub fn prefix(&self, pos: usize) -> TrackedString {
if !self.location.contains(pos) {
if self.location.start > pos {
TrackedString {
string: "".to_string(),
location: Location::new(self.location.start, self.location.start),
}
} else {
self.clone()
}
} else {
let len = pos - self.location.start;
TrackedString {
string: self.string[0..len].to_string(),
location: Location::new(self.location.start, self.location.start + len),
}
}
}
}
impl Display for TrackedString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.string)
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub struct Location {
pub start: usize,
pub end: usize,
}
impl Location {
pub fn new(start: usize, end: usize) -> Location {
Location { start, end }
}
pub fn union(&self, other: Location) -> Location {
Location {
start: min(self.start, other.start),
end: max(self.end, other.end),
}
}
pub fn contains(&self, cursor: usize) -> bool {
cursor >= self.start && cursor <= self.end
}
pub fn len(&self) -> usize {
self.end - self.start
}
}
#[derive(Clone, Debug)]
pub enum Node {
Assignment(Box<Node>, String, Box<Node>),
Unary(TrackedString, Box<Node>),
Glob(TrackedString),
Label(TrackedString),
Regex(TrackedString),
Field(TrackedString),
String(TrackedString),
File(TrackedString, bool),
Integer(TrackedString),
Float(TrackedString),
GetItem(Box<Node>, Box<Node>),
GetAttr(Box<Node>, TrackedString),
Path(Box<Node>, TrackedString),
Substitution(JobNode),
Closure(Option<Vec<ParameterNode>>, JobListNode),
}
fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition { fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
match v { match v {
ValueDefinition::ClosureDefinition(_, p, j, l) => ValueDefinition::ClosureDefinition(_, p, j, l) =>
@ -280,7 +214,7 @@ fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
impl Node { impl Node {
pub fn prefix(&self, pos: usize) -> CrushResult<Node> { pub fn prefix(&self, pos: usize) -> CrushResult<Node> {
match self { match self {
Node::Label(s) => Ok(Node::Label(s.prefix(pos))), Node::Identifier(s) => Ok(Node::Identifier(s.prefix(pos))),
_ => Ok(self.clone()), _ => Ok(self.clone()),
} }
} }
@ -289,7 +223,7 @@ impl Node {
use Node::*; use Node::*;
match self { match self {
Glob(s) | Label(s) | Field(s) | Glob(s) | Identifier(s) | Field(s) |
String(s) | Integer(s) | Float(s) | String(s) | Integer(s) | Float(s) |
Regex(s) | File(s, _) => Regex(s) | File(s, _) =>
s.location, s.location,
@ -311,16 +245,44 @@ impl Node {
} }
} }
pub fn generate_command(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
self.generate(env, true)
}
pub fn generate_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> { pub fn generate_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
self.generate(env, false)
}
pub fn type_name(&self) -> &str {
match self {
Node::Assignment(_, _, _) => "assignment",
Node::Unary(_, _) => "unary operator",
Node::Glob(_) => "glob",
Node::Identifier(_) => "identifier",
Node::Regex(_) => "regular expression literal",
Node::Field(_) => "field",
Node::String(_) => "quoted string literal",
Node::File(_, _) => "file literal",
Node::Integer(_) => "integer literal",
Node::Float(_) => "floating point number literal",
Node::GetItem(_, _) => "subscript",
Node::GetAttr(_, _) => "member access",
Node::Path(_, _) => "path segment",
Node::Substitution(_) => "command substitution",
Node::Closure(_, _) => "closure",
}
}
pub fn generate(&self, env: &Scope, is_command: bool) -> CrushResult<ArgumentDefinition> {
Ok(ArgumentDefinition::unnamed(match self { Ok(ArgumentDefinition::unnamed(match self {
Node::Assignment(target, op, value) => match op.deref() { Node::Assignment(target, op, value) => match op.deref() {
"=" => { "=" => {
return match target.as_ref() { return match target.as_ref() {
Node::Label(t) => Ok(ArgumentDefinition::named( Node::Field(t) => Ok(ArgumentDefinition::named(
t.deref(), t.deref(),
propose_name(&t, value.generate_argument(env)?.unnamed_value()?), propose_name(&t, value.generate_argument(env)?.unnamed_value()?),
)), )),
_ => error("Invalid left side in named argument"), _ => error(format!("Invalid left side in named argument. Expected a field, got a {}", target.type_name())),
}; };
} }
_ => return error("Invalid assignment operator"), _ => return error("Invalid assignment operator"),
@ -346,7 +308,7 @@ impl Node {
} }
_ => return error("Unknown operator"), _ => return error("Unknown operator"),
}, },
Node::Label(l) => ValueDefinition::Label(l.clone()), Node::Identifier(l) => ValueDefinition::Identifier(l.clone()),
Node::Regex(l) => ValueDefinition::Value( Node::Regex(l) => ValueDefinition::Value(
Value::Regex( Value::Regex(
l.string.clone(), l.string.clone(),
@ -366,21 +328,19 @@ impl Node {
s.string.replace("_", "").parse::<f64>() s.string.replace("_", "").parse::<f64>()
)?), )?),
s.location), s.location),
Node::GetAttr(node, label) => { Node::GetAttr(node, identifier) =>
let parent = node.generate_argument(env)?; ValueDefinition::GetAttr(Box::new(node.generate(env, is_command)?.unnamed_value()?), identifier.clone()),
match parent.unnamed_value()? {
ValueDefinition::Value(Value::Field(mut f), location) => { Node::Path(node, identifier) => ValueDefinition::Path(
f.push(label.string.clone());
ValueDefinition::Value(Value::Field(f), location)
}
value => ValueDefinition::GetAttr(Box::new(value), label.clone()),
}
}
Node::Path(node, label) => ValueDefinition::Path(
Box::new(node.generate_argument(env)?.unnamed_value()?), Box::new(node.generate_argument(env)?.unnamed_value()?),
label.clone(), identifier.clone(),
), ),
Node::Field(f) => ValueDefinition::Value(Value::Field(vec![f.string[1..].to_string()]), f.location), Node::Field(f) =>
if is_command {
ValueDefinition::Identifier(f.clone())
} else {
ValueDefinition::Value(Value::Symbol(f.string.to_string()), f.location)
},
Node::Substitution(s) => ValueDefinition::JobDefinition(s.generate(env)?), Node::Substitution(s) => ValueDefinition::JobDefinition(s.generate(env)?),
Node::Closure(s, c) => { Node::Closure(s, c) => {
let param = s.as_ref().map(|v| { let param = s.as_ref().map(|v| {
@ -413,7 +373,7 @@ impl Node {
) -> CrushResult<Option<CommandInvocation>> { ) -> CrushResult<Option<CommandInvocation>> {
match op.deref() { match op.deref() {
"=" => match target.as_ref() { "=" => match target.as_ref() {
Node::Label(t) => Node::function_invocation( Node::Identifier(t) => Node::function_invocation(
env.global_static_cmd(vec!["global", "var", "set"])?, env.global_static_cmd(vec!["global", "var", "set"])?,
t.location, t.location,
vec![ArgumentDefinition::named( vec![ArgumentDefinition::named(
@ -429,6 +389,7 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?), ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
], ],
env, env,
true,
), ),
Node::GetAttr(container, attr) => container.method_invocation( Node::GetAttr(container, attr) => container.method_invocation(
@ -441,12 +402,13 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?), ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
], ],
env, env,
true,
), ),
_ => error("Invalid left side in assignment"), _ => error("Invalid left side in assignment"),
}, },
":=" => match target.as_ref() { ":=" => match target.as_ref() {
Node::Label(t) => Node::function_invocation( Node::Identifier(t) => Node::function_invocation(
env.global_static_cmd(vec!["global", "var", "let"])?, env.global_static_cmd(vec!["global", "var", "let"])?,
t.location, t.location,
vec![ArgumentDefinition::named( vec![ArgumentDefinition::named(
@ -467,7 +429,7 @@ impl Node {
} }
Node::GetItem(val, key) => { Node::GetItem(val, key) => {
val.method_invocation(&TrackedString::from("__getitem__", key.location()), vec![key.generate_argument(env)?], env) val.method_invocation(&TrackedString::from("__getitem__", key.location()), vec![key.generate_argument(env)?], env, true)
} }
Node::Unary(op, _) => match op.string.as_ref() { Node::Unary(op, _) => match op.string.as_ref() {
@ -476,7 +438,7 @@ impl Node {
}, },
Node::Glob(_) Node::Glob(_)
| Node::Label(_) | Node::Identifier(_)
| Node::Regex(_) | Node::Regex(_)
| Node::Field(_) | Node::Field(_)
| Node::String(_) | Node::String(_)
@ -506,24 +468,31 @@ impl Node {
name: &TrackedString, name: &TrackedString,
arguments: Vec<ArgumentDefinition>, arguments: Vec<ArgumentDefinition>,
env: &Scope, env: &Scope,
as_command: bool,
) -> CrushResult<Option<CommandInvocation>> { ) -> CrushResult<Option<CommandInvocation>> {
Ok(Some(CommandInvocation::new( Ok(Some(CommandInvocation::new(
ValueDefinition::GetAttr( ValueDefinition::GetAttr(
Box::from(self.generate_argument(env)?.unnamed_value()?), Box::from(self.generate(env, as_command)?.unnamed_value()?),
name.clone(), name.clone(),
), ),
arguments, arguments,
))) )))
} }
pub fn parse_label_or_wildcard(s: &TrackedString) -> Box<Node> { pub fn parse_string_or_wildcard(s: &TrackedString) -> Box<Node> {
if s.string.contains('%') || s.string.contains('?') { if s.string.contains('%') || s.string.contains('?') {
Box::from(Node::Glob(s.clone())) Box::from(Node::Glob(s.clone()))
} else if s.string.contains('/') || s.string.contains('.') {
Box::from(Node::File(s.clone(), false))
} else { } else {
Box::from(Node::Label(s.clone())) Box::from(Node::Field(s.clone()))
} }
} }
pub fn parse_identifier(s: &TrackedString) -> Box<Node> {
Box::from(Node::Identifier(TrackedString::from(&s.string[1..], s.location)))
}
pub fn parse_file_or_wildcard(s: &TrackedString) -> Box<Node> { pub fn parse_file_or_wildcard(s: &TrackedString) -> Box<Node> {
if s.string.contains('%') || s.string.contains('?') { if s.string.contains('%') || s.string.contains('?') {
Box::from(Node::Glob(s.clone())) Box::from(Node::Glob(s.clone()))
@ -534,7 +503,7 @@ impl Node {
} }
fn path(parts: &[&str], location: Location) -> Node { fn path(parts: &[&str], location: Location) -> Node {
let mut res = Node::Label(TrackedString::from(parts[0], location)); let mut res = Node::Identifier(TrackedString::from(parts[0], location));
for part in &parts[1..] { for part in &parts[1..] {
res = Node::Path(Box::from(res), TrackedString::from(part, location)); res = Node::Path(Box::from(res), TrackedString::from(part, location));
} }
@ -542,7 +511,7 @@ fn path(parts: &[&str], location: Location) -> Node {
} }
fn attr(parts: &[&str], location: Location) -> Node { fn attr(parts: &[&str], location: Location) -> Node {
let mut res = Node::Label(TrackedString::from(parts[0], location)); let mut res = Node::Identifier(TrackedString::from(parts[0], location));
for part in &parts[1..] { for part in &parts[1..] {
res = Node::GetAttr(Box::from(res), TrackedString::from(part, location)); res = Node::GetAttr(Box::from(res), TrackedString::from(part, location));
} }
@ -584,7 +553,7 @@ fn expand_user(s: &str, location: Location) -> Box<Node> {
simple_substitution( simple_substitution(
vec![ vec![
attr(&vec!["global", "user", "find"], location), attr(&vec!["global", "user", "find"], location),
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)) Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)),
], ],
location, location,
), ),
@ -660,7 +629,8 @@ pub enum TokenType {
FactorOperator, FactorOperator,
TermOperator, TermOperator,
QuotedString, QuotedString,
LabelOrWildcard, StringOrWildcard,
Identifier,
Flag, Flag,
Field, Field,
QuotedFile, QuotedFile,

View File

@ -0,0 +1,49 @@
use std::fmt::{Display, Formatter};
use crate::lang::ast::location::Location;
#[derive(Clone, Debug)]
pub struct TrackedString {
pub string: String,
pub location: Location,
}
impl TrackedString {
pub fn from(string: &str, location: Location) -> TrackedString {
TrackedString {
string: string.to_string(),
location,
}
}
pub fn literal(start: usize, string: &str, end: usize) -> TrackedString {
TrackedString {
string: string.to_string(),
location: Location::new(start, end),
}
}
pub fn prefix(&self, pos: usize) -> TrackedString {
if !self.location.contains(pos) {
if self.location.start > pos {
TrackedString {
string: "".to_string(),
location: Location::new(self.location.start, self.location.start),
}
} else {
self.clone()
}
} else {
let len = pos - self.location.start;
TrackedString {
string: self.string[0..len].to_string(),
location: Location::new(self.location.start, self.location.start + len),
}
}
}
}
impl Display for TrackedString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.string)
}
}

View File

@ -1,9 +1,9 @@
use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType}; use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType};
use crate::lang::command::{BoundCommand, Command, CrushCommand, OutputType, Parameter, ArgumentDescription}; use crate::lang::command::{ArgumentDescription, BoundCommand, Command, CrushCommand, OutputType, Parameter};
use crate::lang::command_invocation::CommandInvocation; use crate::lang::command_invocation::CommandInvocation;
use crate::lang::data::dict::Dict; use crate::lang::data::dict::Dict;
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult}; use crate::lang::errors::{argument_error, argument_error_legacy, CrushResult, error, mandate};
use crate::lang::execution_context::{CompileContext, CommandContext, JobContext}; use crate::lang::execution_context::{CommandContext, CompileContext, JobContext};
use crate::lang::help::Help; use crate::lang::help::Help;
use crate::lang::job::Job; use crate::lang::job::Job;
use crate::lang::data::list::List; use crate::lang::data::list::List;
@ -16,7 +16,8 @@ use crate::lang::pipe::{black_hole, empty_channel};
use crate::lang::value::{Value, ValueDefinition, ValueType}; use crate::lang::value::{Value, ValueDefinition, ValueType};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use crate::lang::ast::{TrackedString, Location}; use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::ast::location::Location;
pub struct Closure { pub struct Closure {
name: Option<TrackedString>, name: Option<TrackedString>,
@ -29,7 +30,7 @@ pub struct Closure {
} }
impl CrushCommand for Closure { impl CrushCommand for Closure {
fn invoke(&self, context: CommandContext) -> CrushResult<()> { fn eval(&self, context: CommandContext) -> CrushResult<()> {
let job_definitions = self.job_definitions.clone(); let job_definitions = self.job_definitions.clone();
let parent_env = self.env.clone(); let parent_env = self.env.clone();
let env = parent_env.create_child(&context.scope, false); let env = parent_env.create_child(&context.scope, false);
@ -58,7 +59,7 @@ impl CrushCommand for Closure {
black_hole() black_hole()
}; };
let job = job_definition.invoke(JobContext::new( let job = job_definition.eval(JobContext::new(
input, input,
output, output,
env.clone(), env.clone(),
@ -75,7 +76,7 @@ impl CrushCommand for Closure {
Ok(()) Ok(())
} }
fn can_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool { fn might_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
true true
} }
@ -114,7 +115,7 @@ impl CrushCommand for Closure {
}) })
} }
fn output(&self, _input: &OutputType) -> Option<&ValueType> { fn output_type(&self, _input: &OutputType) -> Option<&ValueType> {
None None
} }
@ -201,7 +202,6 @@ impl<'a> ClosureSerializer<'a> {
model::parameter::Parameter::Named(n.serialize(self.elements, self.state)? as u64), model::parameter::Parameter::Named(n.serialize(self.elements, self.state)? as u64),
Parameter::Parameter(n, t, d) => { Parameter::Parameter(n, t, d) => {
model::parameter::Parameter::Normal(model::NormalParameter { model::parameter::Parameter::Normal(model::NormalParameter {
name: n.serialize(self.elements, self.state)? as u64, name: n.serialize(self.elements, self.state)? as u64,
r#type: Some(self.value_definition(t)?), r#type: Some(self.value_definition(t)?),
@ -303,7 +303,7 @@ impl<'a> ClosureSerializer<'a> {
model::value_definition::ValueDefinition::Job(self.job(j)?) model::value_definition::ValueDefinition::Job(self.job(j)?)
} }
ValueDefinition::Label(l) => { ValueDefinition::Identifier(l) => {
model::value_definition::ValueDefinition::Label( model::value_definition::ValueDefinition::Label(
l.serialize(self.elements, self.state)? as u64) l.serialize(self.elements, self.state)? as u64)
} }
@ -485,7 +485,7 @@ impl<'a> ClosureDeserializer<'a> {
)) ))
} }
model::value_definition::ValueDefinition::Label(s) => { model::value_definition::ValueDefinition::Label(s) => {
ValueDefinition::Label(TrackedString::deserialize(*s as usize, self.elements, self.state)?) ValueDefinition::Identifier(TrackedString::deserialize(*s as usize, self.elements, self.state)?)
} }
model::value_definition::ValueDefinition::GetAttr(a) => ValueDefinition::GetAttr( model::value_definition::ValueDefinition::GetAttr(a) => ValueDefinition::GetAttr(
Box::from(self.value_definition(mandate( Box::from(self.value_definition(mandate(
@ -531,13 +531,14 @@ impl Help for Closure {
} }
} }
/** Extracts the help message from a closure definition */
fn extract_help(jobs: &mut Vec<Job>) -> String { fn extract_help(jobs: &mut Vec<Job>) -> String {
if jobs.is_empty() { if jobs.is_empty() {
return "".to_string(); return "".to_string();
} }
let j = &jobs[0]; let j = &jobs[0];
match j.as_string() { match j.extract_help_message() {
Some(help) => { Some(help) => {
if jobs.len() > 1 { if jobs.len() > 1 {
jobs.remove(0); jobs.remove(0);
@ -570,80 +571,98 @@ impl Closure {
} }
} }
fn push_arguments_to_env_with_signature(
signature: &Vec<Parameter>,
mut arguments: Vec<Argument>,
context: &mut CompileContext,
) -> CrushResult<()> {
let mut named = HashMap::new();
let mut unnamed = Vec::new();
for arg in arguments.drain(..) {
match arg.argument_type {
Some(name) => {
named.insert(name.clone(), arg.value);
}
None => unnamed.push(arg.value),
};
}
let mut unnamed_name = None;
let mut named_name = None;
for param in signature {
match param {
Parameter::Parameter(name, value_type, default) => {
if let Value::Type(value_type) = value_type.eval_and_bind(context)? {
if named.contains_key(&name.string) {
let value = named.remove(&name.string).unwrap();
if !value_type.is(&value) {
return argument_error(
format!(
"Wrong parameter type {}, expected {}",
value.value_type(), value_type),
name.location);
}
context.env.redeclare(&name.string, value)?;
} else if !unnamed.is_empty() {
context.env.redeclare(&name.string, unnamed.remove(0))?;
} else if let Some(default) = default {
let env = context.env.clone();
env.redeclare(&name.string, default.eval_and_bind(context)?)?;
} else {
return argument_error(
format!(
"Missing variable {}. Options are {}!!!",
name.string,
named.keys().map(|a|{a.to_string()}).collect::<Vec<String>>().join(", ")),
name.location);
}
} else {
return argument_error_legacy("Not a type");
}
}
Parameter::Named(name) => {
if named_name.is_some() {
return argument_error_legacy("Multiple named argument maps specified");
}
named_name = Some(name);
}
Parameter::Unnamed(name) => {
if unnamed_name.is_some() {
return argument_error_legacy("Multiple named argument maps specified");
}
unnamed_name = Some(name);
}
}
}
if let Some(unnamed_name) = unnamed_name {
context.env.redeclare(
unnamed_name.string.as_ref(),
Value::List(List::new(ValueType::Any, unnamed)),
)?;
} else if !unnamed.is_empty() {
return argument_error_legacy("No target for unnamed arguments");
}
if let Some(named_name) = named_name {
let d = Dict::new(ValueType::String, ValueType::Any);
for (k, v) in named {
d.insert(Value::string(&k), v)?;
}
context.env.redeclare(named_name.string.as_ref(), Value::Dict(d))?;
} else if !named.is_empty() {
return argument_error_legacy("No target for extra named arguments");
}
Ok(())
}
fn push_arguments_to_env( fn push_arguments_to_env(
signature: &Option<Vec<Parameter>>, signature: &Option<Vec<Parameter>>,
mut arguments: Vec<Argument>, mut arguments: Vec<Argument>,
context: &mut CompileContext, context: &mut CompileContext,
) -> CrushResult<()> { ) -> CrushResult<()> {
if let Some(signature) = signature { if let Some(signature) = signature {
let mut named = HashMap::new(); Self::push_arguments_to_env_with_signature(signature, arguments, context)
let mut unnamed = Vec::new();
for arg in arguments.drain(..) {
match arg.argument_type {
Some(name) => {
named.insert(name.clone(), arg.value);
}
None => unnamed.push(arg.value),
};
}
let mut unnamed_name = None;
let mut named_name = None;
for param in signature {
match param {
Parameter::Parameter(name, value_type, default) => {
if let Value::Type(value_type) = value_type.compile_bound(context)? {
if named.contains_key(&name.string) {
let value = named.remove(&name.string).unwrap();
if !value_type.is(&value) {
return argument_error_legacy("Wrong parameter type");
}
context.env.redeclare(&name.string, value)?;
} else if !unnamed.is_empty() {
context.env.redeclare(&name.string, unnamed.remove(0))?;
} else if let Some(default) = default {
let env = context.env.clone();
env.redeclare(&name.string, default.compile_bound(context)?)?;
} else {
return argument_error_legacy("Missing variable!!!");
}
} else {
return argument_error_legacy("Not a type");
}
}
Parameter::Named(name) => {
if named_name.is_some() {
return argument_error_legacy("Multiple named argument maps specified");
}
named_name = Some(name);
}
Parameter::Unnamed(name) => {
if unnamed_name.is_some() {
return argument_error_legacy("Multiple named argument maps specified");
}
unnamed_name = Some(name);
}
}
}
if let Some(unnamed_name) = unnamed_name {
context.env.redeclare(
unnamed_name.string.as_ref(),
Value::List(List::new(ValueType::Any, unnamed)),
)?;
} else if !unnamed.is_empty() {
return argument_error_legacy("No target for unnamed arguments");
}
if let Some(named_name) = named_name {
let d = Dict::new(ValueType::String, ValueType::Any);
for (k, v) in named {
d.insert(Value::string(&k), v)?;
}
context.env.redeclare(named_name.string.as_ref(), Value::Dict(d))?;
} else if !named.is_empty() {
return argument_error_legacy("No target for extra named arguments");
}
} else { } else {
for arg in arguments.drain(..) { for arg in arguments.drain(..) {
match arg.argument_type { match arg.argument_type {
@ -655,8 +674,8 @@ impl Closure {
} }
} }
} }
Ok(())
} }
Ok(())
} }
pub fn deserialize( pub fn deserialize(

View File

@ -13,7 +13,7 @@ use crate::lang::value::{Value, ValueDefinition, ValueType};
use closure::Closure; use closure::Closure;
use ordered_map::OrderedMap; use ordered_map::OrderedMap;
use std::fmt::{Formatter, Display}; use std::fmt::{Formatter, Display};
use crate::lang::ast::TrackedString; use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::completion::Completion; use crate::lang::completion::Completion;
use crate::lang::completion::parse::PartialCommandResult; use crate::lang::completion::parse::PartialCommandResult;
@ -62,8 +62,8 @@ pub struct ArgumentDescription {
} }
pub trait CrushCommand: Help { pub trait CrushCommand: Help {
fn invoke(&self, context: CommandContext) -> CrushResult<()>; fn eval(&self, context: CommandContext) -> CrushResult<()>;
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool; fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool;
fn name(&self) -> &str; fn name(&self) -> &str;
fn copy(&self) -> Command; fn copy(&self) -> Command;
fn help(&self) -> &dyn Help; fn help(&self) -> &dyn Help;
@ -73,7 +73,7 @@ pub trait CrushCommand: Help {
state: &mut SerializationState, state: &mut SerializationState,
) -> CrushResult<usize>; ) -> CrushResult<usize>;
fn bind(&self, this: Value) -> Command; fn bind(&self, this: Value) -> Command;
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType>; fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType>;
fn arguments(&self) -> &Vec<ArgumentDescription>; fn arguments(&self) -> &Vec<ArgumentDescription>;
} }
@ -220,12 +220,12 @@ impl dyn CrushCommand {
} }
impl CrushCommand for SimpleCommand { impl CrushCommand for SimpleCommand {
fn invoke(&self, context: CommandContext) -> CrushResult<()> { fn eval(&self, context: CommandContext) -> CrushResult<()> {
let c = self.call; let c = self.call;
c(context) c(context)
} }
fn can_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool { fn might_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
self.can_block self.can_block
} }
@ -270,7 +270,7 @@ impl CrushCommand for SimpleCommand {
}) })
} }
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> { fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
self.output.calculate(input) self.output.calculate(input)
} }
@ -315,7 +315,7 @@ impl std::fmt::Debug for SimpleCommand {
} }
impl CrushCommand for ConditionCommand { impl CrushCommand for ConditionCommand {
fn invoke(&self, context: CommandContext) -> CrushResult<()> { fn eval(&self, context: CommandContext) -> CrushResult<()> {
let c = self.call; let c = self.call;
c(context)?; c(context)?;
Ok(()) Ok(())
@ -325,10 +325,10 @@ impl CrushCommand for ConditionCommand {
"conditional command" "conditional command"
} }
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool { fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
arguments arguments
.iter() .iter()
.any(|arg| arg.value.can_block(arguments, context)) .any(|arg| arg.value.can_block(context))
} }
fn copy(&self) -> Command { fn copy(&self) -> Command {
@ -365,7 +365,7 @@ impl CrushCommand for ConditionCommand {
}) })
} }
fn output(&self, _input: &OutputType) -> Option<&ValueType> { fn output_type(&self, _input: &OutputType) -> Option<&ValueType> {
None None
} }
@ -388,13 +388,13 @@ impl Help for ConditionCommand {
} }
} }
impl std::cmp::PartialEq for ConditionCommand { impl PartialEq for ConditionCommand {
fn eq(&self, _other: &ConditionCommand) -> bool { fn eq(&self, _other: &ConditionCommand) -> bool {
false false
} }
} }
impl std::cmp::Eq for ConditionCommand {} impl Eq for ConditionCommand {}
#[derive(Clone)] #[derive(Clone)]
pub enum Parameter { pub enum Parameter {
@ -434,13 +434,13 @@ pub struct BoundCommand {
} }
impl CrushCommand for BoundCommand { impl CrushCommand for BoundCommand {
fn invoke(&self, mut context: CommandContext) -> CrushResult<()> { fn eval(&self, mut context: CommandContext) -> CrushResult<()> {
context.this = Some(self.this.clone()); context.this = Some(self.this.clone());
self.command.invoke(context) self.command.eval(context)
} }
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool { fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
self.command.can_block(arguments, context) self.command.might_block(arguments, context)
} }
fn name(&self) -> &str { fn name(&self) -> &str {
@ -482,8 +482,8 @@ impl CrushCommand for BoundCommand {
}) })
} }
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> { fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
self.command.output(input) self.command.output_type(input)
} }
fn arguments(&self) -> &Vec<ArgumentDescription> { fn arguments(&self) -> &Vec<ArgumentDescription> {

View File

@ -1,15 +1,16 @@
use crate::lang::errors::{error, CrushResult, CrushErrorType}; use crate::lang::errors::{CrushResult, error};
use crate::lang::execution_context::{CompileContext, JobContext}; use crate::lang::execution_context::{CompileContext, JobContext};
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::{argument::ArgumentDefinition, argument::ArgumentVecCompiler, value::Value}; use crate::lang::{argument::ArgumentDefinition, argument::ArgumentEvaluator, value::Value};
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::value::ValueDefinition; use crate::lang::value::{ValueDefinition, ValueType};
use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::thread::ThreadId; use std::thread::ThreadId;
use crate::lang::ast::Location; use crate::data::r#struct::Struct;
use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::ast::location::Location;
#[derive(Clone)] #[derive(Clone)]
pub struct CommandInvocation { pub struct CommandInvocation {
@ -17,6 +18,239 @@ pub struct CommandInvocation {
arguments: Vec<ArgumentDefinition>, arguments: Vec<ArgumentDefinition>,
} }
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
for arg in local_arguments {
if arg.value.can_block(context) {
return true;
}
}
false
}
impl CommandInvocation {
pub fn new(command: ValueDefinition, arguments: Vec<ArgumentDefinition>) -> CommandInvocation {
CommandInvocation { command, arguments }
}
/** Extracts the help message from a closure definition */
pub fn extract_help_message(&self) -> Option<String> {
if self.arguments.len() != 0 {
return None;
}
match &self.command {
ValueDefinition::Value(Value::String(s), _) => Some(s.to_string()),
_ => None,
}
}
pub fn arguments(&self) -> &[ArgumentDefinition] {
&self.arguments
}
pub fn command(&self) -> &ValueDefinition {
&self.command
}
/**
Evaluates all the arguments into values, and puts them into a CommandContext,
ready to be exacuted by the main command.
*/
fn execution_context(
local_arguments: Vec<ArgumentDefinition>,
mut this: Option<Value>,
job_context: JobContext,
) -> CrushResult<CommandContext> {
let (arguments, arg_this) = local_arguments.eval(&mut CompileContext::from(&job_context))?;
if arg_this.is_some() {
this = arg_this;
}
Ok(job_context.command_context(arguments, this))
}
pub fn can_block(&self, context: &mut CompileContext) -> bool {
if self.command.can_block(context) {
return true;
}
match self.command.eval(context) {
Ok((_, Value::Command(command))) =>
command.might_block(&self.arguments, context) || arg_can_block(&self.arguments, context),
_ => true,
}
}
pub fn eval(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
eval(&self.command, &self.arguments, context)
}
}
pub fn eval_non_blocking(command: &ValueDefinition, arguments: &Vec<ArgumentDefinition>, context: JobContext) -> CrushResult<Option<ThreadId>> {
match command.eval(&mut CompileContext::from(&context))
{
// Try to find the command in this thread. This may fail if the command is found via a subshell, in which case we need to spawn a thread
Ok((this, value)) => {
eval_internal(this, value, arguments.clone(), context, command.location())
}
Err(err) =>
if let ValueDefinition::Identifier(str) = command {
try_external_command(str, arguments.clone(), context)
} else {
return Err(err);
}
}
}
pub fn eval(command: &ValueDefinition, arguments: &Vec<ArgumentDefinition>, context: JobContext) -> CrushResult<Option<ThreadId>> {
if command.can_block(&mut CompileContext::from(&context)) {
eval_non_blocking(command, arguments, context)
} else {
let command = command.clone();
let arguments = arguments.clone();
let t = context.global_state.threads().clone();
Ok(Some(t.spawn(
&command.to_string(),
move || {
match eval_non_blocking(&command, &arguments, context.clone()) {
Ok(Some(id)) => context.global_state.threads().join_one(
id,
&context.global_state.printer(),
),
Err(e) => context.global_state.printer().crush_error(e),
_ => {}
}
Ok(())
},
)?))
}
}
fn eval_internal(
this: Option<Value>,
value: Value,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match value {
Value::Command(command) => eval_command(command, this, local_arguments, context),
Value::Type(t) => eval_type(t, local_arguments, context, location),
Value::Struct(s) => eval_struct(s, local_arguments, context, location),
v => eval_other(v, local_arguments, context, location),
}
}
fn eval_other(
value: Value,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
if local_arguments.len() == 0 {
eval_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(value, location))],
context,
)
} else {
error(&format!("{} is not a command.", value))
}
}
fn eval_type(
value_type: ValueType,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match value_type.fields().get("__call__") {
None => eval_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::Type(value_type),
location,
))],
context,
),
Some(call) => eval_command(
call.as_ref().copy(),
Some(Value::Type(value_type)),
local_arguments,
context,
),
}
}
fn eval_struct(
struct_value: Struct,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match struct_value.get("__call__") {
Some(Value::Command(call)) => {
eval_command(call, Some(Value::Struct(struct_value)), local_arguments, context)
}
Some(v) => error(
format!(
"__call__ should be a command, was of type {}",
v.value_type().to_string()
)
.as_str(),
),
_ => {
if local_arguments.len() == 0 {
eval_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::Struct(struct_value),
location,
))],
context,
)
} else {
error(
format!(
"Struct must have a member __call__ to be used as a command {}",
struct_value.to_string()
)
.as_str(),
)
}
}
}
}
fn eval_command(
command: Command,
this: Option<Value>,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
) -> CrushResult<Option<ThreadId>> {
if !command.might_block(&local_arguments, &mut CompileContext::from(&context))
&& !arg_can_block(&local_arguments, &mut CompileContext::from(&context))
{
let new_context =
CommandInvocation::execution_context(local_arguments, this, context.clone())?;
context.global_state.printer().handle_error(command.eval(new_context));
Ok(None)
} else {
let t = context.global_state.threads().clone();
let name = command.name().to_string();
Ok(Some(t.spawn(
&name,
move || {
let res = CommandInvocation::execution_context(local_arguments, this, context.clone())?;
command.eval(res)
},
)?))
}
}
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> { fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
if let Some(Value::List(path)) = env.get("cmd_path")? { if let Some(Value::List(path)) = env.get("cmd_path")? {
let path_vec = path.dump(); let path_vec = path.dump();
@ -35,284 +269,11 @@ fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathB
Ok(None) Ok(None)
} }
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
for arg in local_arguments {
if arg.value.can_block(local_arguments, context) {
return true;
}
}
false
}
impl CommandInvocation {
pub fn new(command: ValueDefinition, arguments: Vec<ArgumentDefinition>) -> CommandInvocation {
CommandInvocation { command, arguments }
}
pub fn as_string(&self) -> Option<String> {
if self.arguments.len() != 0 {
return None;
}
match &self.command {
ValueDefinition::Value(Value::String(s), _) => Some(s.to_string()),
_ => None,
}
}
pub fn arguments(&self) -> &[ArgumentDefinition] {
&self.arguments
}
pub fn command(&self) -> &ValueDefinition {
&self.command
}
/*
pub fn spawn_stream(
&self,
env: &Scope,
mut argument_stream: InputStream,
output: ValueSender,
) -> CrushResult<JobJoinHandle> {
let cmd = env.get(&self.name);
match cmd {
Some(Value::Command(command)) => {
let c = command.call;
Ok(handle(build(format_name(&self.name)).spawn(
move || {
loop {
match argument_stream.recv() {
Ok(mut row) => {}
Err(_) => break,
}
}
Ok(())
})))
}
_ => {
error("Can't stream call")
}
}
}
*/
fn execution_context(
local_arguments: Vec<ArgumentDefinition>,
mut this: Option<Value>,
job_context: JobContext,
) -> CrushResult<CommandContext> {
let (arguments, arg_this) = local_arguments.compile(&mut CompileContext::from(&job_context))?;
if arg_this.is_some() {
this = arg_this;
}
Ok(job_context.command_context(arguments, this))
}
pub fn can_block(&self, arg: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
let cmd = self.command.compile_internal(context, false);
match cmd {
Ok((_, Value::Command(command))) => {
command.can_block(arg, context) || arg_can_block(&self.arguments, context)
}
_ => true,
}
}
pub fn invoke(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
match self
.command
.compile_internal(&mut CompileContext::from(&context), false)
{
Ok((this, value)) => invoke_value(this, value, self.arguments.clone(), context, self.command.location()),
Err(err) => {
if err.is(CrushErrorType::BlockError) {
let cmd = self.command.clone();
let arguments = self.arguments.clone();
let t = context.global_state.threads().clone();
let location = self.command.location();
Ok(Some(t.spawn(
&self.command.to_string(),
move || {
match cmd.clone().compile_unbound(&mut CompileContext::from(&context)) {
Ok((this, value)) => context.global_state.printer().handle_error(invoke_value(
this,
value,
arguments,
context.clone(),
location,
)),
_ => context.global_state.printer().handle_error(try_external_command(
cmd,
arguments,
context.clone(),
)),
}
Ok(())
},
)?))
} else {
try_external_command(self.command.clone(), self.arguments.clone(), context)
}
}
}
}
}
fn invoke_value(
this: Option<Value>,
value: Value,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match value {
Value::Command(command) => invoke_command(command, this, local_arguments, context),
Value::File(f) => {
if local_arguments.len() == 0 {
let meta = f.metadata();
if meta.is_ok() && meta.unwrap().is_dir() {
invoke_command(
context
.scope
.global_static_cmd(vec!["global", "fs", "cd"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::File(f),
location,
))],
context,
)
} else {
invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::File(f),
location,
))],
context,
)
}
} else {
error(
format!(
"Not a command {}",
f.to_str().unwrap_or("<invalid filename>")
)
.as_str(),
)
}
}
Value::Type(t) => match t.fields().get("__call__") {
None => invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::Type(t),
location,
))],
context,
),
Some(call) => invoke_command(
call.as_ref().copy(),
Some(Value::Type(t)),
local_arguments,
context,
),
},
Value::Struct(s) => match s.get("__call__") {
Some(Value::Command(call)) => {
invoke_command(call, Some(Value::Struct(s)), local_arguments, context)
}
Some(v) => error(
format!(
"__call__ should be a command, was of type {}",
v.value_type().to_string()
)
.as_str(),
),
_ => {
if local_arguments.len() == 0 {
invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::Struct(s),
location,
))],
context,
)
} else {
error(
format!(
"Struct must have a member __call__ to be used as a command {}",
s.to_string()
)
.as_str(),
)
}
}
},
_ => {
if local_arguments.len() == 0 {
invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(value, location))],
context,
)
} else {
error(&format!("Not a command {}", value))
}
}
}
}
fn invoke_command(
action: Command,
this: Option<Value>,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
) -> CrushResult<Option<ThreadId>> {
if !action.can_block(&local_arguments, &mut CompileContext::from(&context))
&& !arg_can_block(&local_arguments, &mut CompileContext::from(&context))
{
let new_context =
CommandInvocation::execution_context(local_arguments, this, context.clone())?;
context.global_state.printer().handle_error(action.invoke(new_context));
Ok(None)
} else {
let t = context.global_state.threads().clone();
let name = action.name().to_string();
Ok(Some(t.spawn(
&name,
move || {
let res = CommandInvocation::execution_context(local_arguments, this, context.clone())?;
action.invoke(res)
},
)?))
}
}
fn try_external_command( fn try_external_command(
def: ValueDefinition, cmd: &TrackedString,
mut arguments: Vec<ArgumentDefinition>, mut arguments: Vec<ArgumentDefinition>,
context: JobContext, context: JobContext,
) -> CrushResult<Option<ThreadId>> { ) -> CrushResult<Option<ThreadId>> {
let def_location = def.location();
let (cmd, sub) = match def {
ValueDefinition::Label(str) => (str, None),
ValueDefinition::GetAttr(parent, sub) => match parent.deref() {
ValueDefinition::Label(str) => (str.clone(), Some(sub)),
_ => return error("Not a command"),
},
_ => return error("Not a command"),
};
match resolve_external_command(&cmd.string, &context.scope)? { match resolve_external_command(&cmd.string, &context.scope)? {
None => error(format!("Unknown command name {}", cmd).as_str()), None => error(format!("Unknown command name {}", cmd).as_str()),
Some(path) => { Some(path) => {
@ -320,16 +281,6 @@ fn try_external_command(
0, 0,
ArgumentDefinition::unnamed(ValueDefinition::Value(Value::File(path), cmd.location)), ArgumentDefinition::unnamed(ValueDefinition::Value(Value::File(path), cmd.location)),
); );
if let Some(subcmd) = sub {
arguments.insert(
1,
ArgumentDefinition::unnamed(
ValueDefinition::Value(
Value::string(subcmd.string),
subcmd.location,
)),
);
}
let call = CommandInvocation { let call = CommandInvocation {
command: ValueDefinition::Value( command: ValueDefinition::Value(
Value::Command( Value::Command(
@ -337,17 +288,22 @@ fn try_external_command(
.scope .scope
.global_static_cmd(vec!["global", "control", "cmd"])?, .global_static_cmd(vec!["global", "control", "cmd"])?,
), ),
def_location, cmd.location,
), ),
arguments, arguments,
}; };
call.invoke(context) call.eval(context)
} }
} }
} }
impl Display for CommandInvocation { impl Display for CommandInvocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.command.fmt(f) self.command.fmt(f)?;
for a in &self.arguments {
f.write_str(" ")?;
a.fmt(f)?;
}
Ok(())
} }
} }

View File

@ -81,7 +81,7 @@ fn completion_suffix(maybe_scope: CrushResult<Option<Value>>, t: &ValueType) ->
Ok(Some(Value::Scope(_))) => ":", Ok(Some(Value::Scope(_))) => ":",
Ok(Some(Value::Empty())) | Ok(Some(Value::Empty())) |
Ok(Some(Value::Bool(_))) | Ok(Some(Value::Bool(_))) |
Ok(Some(Value::Field(_))) | Ok(Some(Value::Symbol(_))) |
Ok(Some(Value::Command(_))) => " ", Ok(Some(Value::Command(_))) => " ",
_ => "", _ => "",
} }
@ -281,6 +281,9 @@ fn complete_partial_argument(
LastArgument::Label(label) => { LastArgument::Label(label) => {
complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?; complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?;
}
LastArgument::Field(label) => {
if parse_result.last_argument_name.is_none() { if parse_result.last_argument_name.is_none() {
if let CompletionCommand::Known(cmd) = parse_result.command { if let CompletionCommand::Known(cmd) = parse_result.command {
complete_argument_name(cmd.arguments(), &label, cursor, res, false)?; complete_argument_name(cmd.arguments(), &label, cursor, res, false)?;
@ -288,7 +291,7 @@ fn complete_partial_argument(
} }
} }
LastArgument::Field(parent, field) => { LastArgument::Member(parent, field) => {
complete_label(parent, &field, &argument_type, cursor, res)?; complete_label(parent, &field, &argument_type, cursor, res)?;
} }
@ -320,6 +323,10 @@ pub fn complete(
complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?; complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?;
} }
ParseResult::PartialField(label) => {
complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?;
}
ParseResult::PartialMember(parent, label) => { ParseResult::PartialMember(parent, label) => {
complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?; complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?;
} }
@ -642,8 +649,8 @@ mod tests {
#[test] #[test]
fn complete_namespaced_argument() { fn complete_namespaced_argument() {
let line = "xxx abcd:bc"; let line = "xxx $abcd:bc";
let cursor = 11; let cursor = line.len();
let s = Scope::create_root(); let s = Scope::create_root();
s.create_namespace("abcd", "bla", Box::new(|env| { s.create_namespace("abcd", "bla", Box::new(|env| {
@ -653,19 +660,19 @@ mod tests {
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "xxx abcd:bcde "); assert_eq!(&completions[0].complete(line), "xxx $abcd:bcde ");
} }
#[test] #[test]
fn complete_simple_argument() { fn complete_simple_argument() {
let line = "abcd ab"; let line = "abcd $ab";
let cursor = 7; let cursor = line.len();
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("abcd", Value::Empty()).unwrap(); s.declare("abcd", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "abcd abcd "); assert_eq!(&completions[0].complete(line), "abcd $abcd ");
} }
#[test] #[test]
@ -682,26 +689,26 @@ mod tests {
#[test] #[test]
fn check_multiple_token() { fn check_multiple_token() {
let line = "ab cd ef"; let line = "ab $cd ef";
let cursor = 5; let cursor = 6;
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap(); s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab cdef ef"); assert_eq!(&completions[0].complete(line), "ab $cdef ef");
} }
#[test] #[test]
fn check_named_argument() { fn check_named_argument() {
let line = "ab foo=cd"; let line = "ab foo=$cd";
let cursor = 9; let cursor = 10;
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap(); s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab foo=cdef "); assert_eq!(&completions[0].complete(line), "ab foo=$cdef ");
} }
#[test] #[test]
@ -718,7 +725,7 @@ mod tests {
#[test] #[test]
fn check_completion_type_filtering() { fn check_completion_type_filtering() {
let line = "my_cmd super_fancy_argument=t"; let line = "my_cmd super_fancy_argument=$t";
let cursor = line.len(); let cursor = line.len();
let s = scope_with_function(); let s = scope_with_function();
@ -726,7 +733,7 @@ mod tests {
s.declare("type", Value::Type(ValueType::Empty)).unwrap(); s.declare("type", Value::Type(ValueType::Empty)).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "my_cmd super_fancy_argument=type "); assert_eq!(&completions[0].complete(line), "my_cmd super_fancy_argument=$type ");
} }
#[test] #[test]

View File

@ -1,3 +1,4 @@
use std::cmp::min;
use crate::lang::ast::{Node, CommandNode, JobListNode, JobNode}; use crate::lang::ast::{Node, CommandNode, JobListNode, JobNode};
use crate::lang::errors::{error, CrushResult, mandate, argument_error_legacy, to_crush_error}; use crate::lang::errors::{error, CrushResult, mandate, argument_error_legacy, to_crush_error};
use crate::lang::value::{ValueType, Value}; use crate::lang::value::{ValueType, Value};
@ -9,6 +10,7 @@ use crate::util::glob::Glob;
use crate::lang::parser::Parser; use crate::lang::parser::Parser;
use crate::util::escape::unescape; use crate::util::escape::unescape;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::{Display, Formatter};
pub enum CompletionCommand { pub enum CompletionCommand {
Unknown, Unknown,
@ -28,7 +30,8 @@ impl Clone for CompletionCommand {
pub enum LastArgument { pub enum LastArgument {
Unknown, Unknown,
Label(String), Label(String),
Field(Value, String), Field(String),
Member(Value, String),
File(String, bool), File(String, bool),
QuotedString(String), QuotedString(String),
Switch(String), Switch(String),
@ -68,9 +71,9 @@ impl PartialCommandResult {
if false && cmd.arguments().len() == 1 { if false && cmd.arguments().len() == 1 {
Some(&cmd.arguments()[0]) Some(&cmd.arguments()[0])
} else { } else {
let mut previous_named = HashSet::new(); let mut previous_named = HashSet::new();
let mut previous_unnamed = 0usize; let mut previous_unnamed = 0usize;
for arg in &self.previous_arguments { for arg in &self.previous_arguments {
match &arg.name { match &arg.name {
Some(name) => { Some(name) => {
@ -83,9 +86,9 @@ impl PartialCommandResult {
let mut unnamed_used = 0usize; let mut unnamed_used = 0usize;
for arg in cmd.arguments() { for arg in cmd.arguments() {
if arg.unnamed { if arg.unnamed {
return Some(arg) return Some(arg);
} }
if previous_named.contains(&arg.name ) { if previous_named.contains(&arg.name) {
continue; continue;
} else { } else {
unnamed_used += 1; unnamed_used += 1;
@ -115,15 +118,47 @@ impl PartialCommandResult {
pub enum ParseResult { pub enum ParseResult {
Nothing, Nothing,
PartialLabel(String), PartialLabel(String),
PartialField(String),
PartialMember(Value, String), PartialMember(Value, String),
PartialFile(String, bool), PartialFile(String, bool),
PartialQuotedString(String), PartialQuotedString(String),
PartialArgument(PartialCommandResult), PartialArgument(PartialCommandResult),
} }
impl Display for ParseResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ParseResult::Nothing => f.write_str("nothing"),
ParseResult::PartialLabel(l) => {
f.write_str("label ")?;
f.write_str(l)
}
ParseResult::PartialField(l) => {
f.write_str("field ")?;
f.write_str(l)
}
ParseResult::PartialMember(_p, m) => {
f.write_str("member ")?;
f.write_str(m)
}
ParseResult::PartialFile(p, _q) => {
f.write_str("file ")?;
f.write_str(p)
}
ParseResult::PartialQuotedString(s) => {
f.write_str("string ")?;
f.write_str(s)
}
ParseResult::PartialArgument(_a) => {
f.write_str("command")
}
}
}
}
fn simple_path(node: &Node, cursor: usize) -> CrushResult<String> { fn simple_path(node: &Node, cursor: usize) -> CrushResult<String> {
match node { match node {
Node::Label(label) => Ok(label.string.clone()), Node::Identifier(label) => Ok(label.string.clone()),
Node::Path(p, a) => { Node::Path(p, a) => {
let res = simple_path(p.as_ref(), cursor)?; let res = simple_path(p.as_ref(), cursor)?;
Ok(format!("{}/{}", res, &a.string)) Ok(format!("{}/{}", res, &a.string))
@ -192,12 +227,19 @@ fn find_command_in_job_list(ast: JobListNode, cursor: usize) -> CrushResult<Comm
.map(|c| c.clone())), "Nothing to complete") .map(|c| c.clone())), "Nothing to complete")
} }
fn fetch_value(node: &Node, scope: &Scope) -> CrushResult<Option<Value>> { fn fetch_value(node: &Node, scope: &Scope, is_command: bool) -> CrushResult<Option<Value>> {
match node { match node {
Node::Label(l) => scope.get(&l.string), Node::Identifier(l) => scope.get(&l.string),
Node::Field(l) =>
if is_command {
scope.get(&l.string)
} else {
Ok(None)
},
Node::GetAttr(n, l) => Node::GetAttr(n, l) =>
match fetch_value(n, scope)? { match fetch_value(n, scope, is_command)? {
Some(parent) => parent.field(&l.string), Some(parent) => parent.field(&l.string),
None => Ok(None), None => Ok(None),
}, },
@ -225,7 +267,7 @@ fn fetch_value(node: &Node, scope: &Scope) -> CrushResult<Option<Value>> {
} }
fn parse_command_node(node: &Node, scope: &Scope) -> CrushResult<CompletionCommand> { fn parse_command_node(node: &Node, scope: &Scope) -> CrushResult<CompletionCommand> {
match fetch_value(node, scope)? { match fetch_value(node, scope, true)? {
Some(Value::Command(command)) => Ok(CompletionCommand::Known(command)), Some(Value::Command(command)) => Ok(CompletionCommand::Known(command)),
_ => Ok(CompletionCommand::Unknown), _ => Ok(CompletionCommand::Unknown),
} }
@ -235,18 +277,18 @@ fn parse_previous_argument(arg: &Node) -> PreviousArgument {
match arg { match arg {
Node::Assignment(key, op, value) => { Node::Assignment(key, op, value) => {
match (key.as_ref(), op.as_str()) { match (key.as_ref(), op.as_str()) {
(Node::Label(name), "=") => { (Node::Field(name), "=") => {
let inner = parse_previous_argument(value.as_ref()); let inner = parse_previous_argument(value.as_ref());
return PreviousArgument { return PreviousArgument {
name: Some(name.string.clone()), name: Some(name.string.clone()),
value: inner.value value: inner.value,
} };
} }
_ => {} _ => {}
} }
} }
_ => {}, _ => {}
} }
PreviousArgument { PreviousArgument {
name: None, name: None,
@ -274,13 +316,17 @@ pub fn parse(
let cmd = &cmd.expressions[0]; let cmd = &cmd.expressions[0];
if cmd.location().contains(cursor) { if cmd.location().contains(cursor) {
match cmd { match cmd {
Node::Label(label) => Node::Identifier(label) =>
Ok(ParseResult::PartialLabel( Ok(ParseResult::PartialLabel(
label.prefix(cursor).string)), label.prefix(cursor).string)),
Node::Field(label) =>
Ok(ParseResult::PartialField(
label.prefix(cursor).string)),
Node::GetAttr(parent, field) => Node::GetAttr(parent, field) =>
Ok(ParseResult::PartialMember( Ok(ParseResult::PartialMember(
mandate(fetch_value(parent, scope)?, "Unknown value")?, mandate(fetch_value(parent, scope, true)?, "Unknown value")?,
field.prefix(cursor).string)), field.prefix(cursor).string)),
Node::Path(_, _) => Node::Path(_, _) =>
@ -296,7 +342,7 @@ pub fn parse(
Node::GetItem(_, _) => { panic!("AAA"); } Node::GetItem(_, _) => { panic!("AAA"); }
_ => error("Can't extract command to complete"), _ => error(format!("Can't extract command to complete. Unknown node type {}", cmd.type_name())),
} }
} else { } else {
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
@ -321,7 +367,7 @@ pub fn parse(
if name.location().contains(cursor) { if name.location().contains(cursor) {
(Box::from(name.prefix(cursor)?), None, true) (Box::from(name.prefix(cursor)?), None, true)
} else { } else {
if let Node::Label(name) = name.as_ref() { if let Node::Identifier(name) = name.as_ref() {
(value.clone(), Some(name.string.clone()), false) (value.clone(), Some(name.string.clone()), false)
} else { } else {
(value.clone(), None, false) (value.clone(), None, false)
@ -333,22 +379,24 @@ pub fn parse(
if argument_complete { if argument_complete {
match arg.deref() { match arg.deref() {
Node::Label(l) => Node::Field(l) => {
let substring = &l.string[0..min(l.string.len(), cursor - l.location.start)];
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
PartialCommandResult { PartialCommandResult {
command: c, command: c,
previous_arguments, previous_arguments,
last_argument: LastArgument::Switch(l.string.clone()), last_argument: LastArgument::Switch(substring.to_string()),
last_argument_name, last_argument_name,
} }
)), ))
}
_ => argument_error_legacy("Invalid argument name"), _ => argument_error_legacy(format!("Invalid argument name {}", arg.type_name()))
} }
} else { } else {
if arg.location().contains(cursor) { if arg.location().contains(cursor) {
match arg.as_ref() { match arg.as_ref() {
Node::Label(l) => Node::Identifier(l) =>
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
PartialCommandResult { PartialCommandResult {
command: c, command: c,
@ -358,13 +406,23 @@ pub fn parse(
} }
)), )),
Node::Field(l) =>
Ok(ParseResult::PartialArgument(
PartialCommandResult {
command: c,
previous_arguments,
last_argument: LastArgument::Field(l.string.clone()),
last_argument_name,
}
)),
Node::GetAttr(parent, field) => Node::GetAttr(parent, field) =>
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
PartialCommandResult { PartialCommandResult {
command: c, command: c,
previous_arguments, previous_arguments,
last_argument: LastArgument::Field( last_argument: LastArgument::Member(
mandate(fetch_value(parent, scope)?, "unknown value")?, mandate(fetch_value(parent, scope, false)?, "unknown value")?,
field.prefix(cursor).string), field.prefix(cursor).string),
last_argument_name, last_argument_name,
})), })),
@ -421,7 +479,7 @@ pub fn parse(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
use crate::lang::parser::lalrparser; use crate::lang::parser::lalrparser;
fn ast(s: &str) -> CrushResult<JobListNode> { fn ast(s: &str) -> CrushResult<JobListNode> {
@ -451,8 +509,8 @@ mod tests {
#[test] #[test]
fn find_command_in_operator() { fn find_command_in_operator() {
let ast = ast("ps | where {^cpu == (max_)}").unwrap(); let ast = ast("ps | where {cpu == (max_)}").unwrap();
let cmd = find_command_in_job_list(ast, 25).unwrap(); let cmd = find_command_in_job_list(ast, 24).unwrap();
assert_eq!(cmd.location, Location::new(21, 25)) assert_eq!(cmd.location, Location::new(20, 24))
} }
} }

View File

@ -127,9 +127,9 @@ impl dyn BinaryReader {
} }
} }
pub fn vec(vec: &Vec<u8>) -> Box<dyn BinaryReader + Send + Sync> { pub fn vec(bytes: &[u8]) -> Box<dyn BinaryReader + Send + Sync> {
Box::from(VecReader { Box::from(VecReader {
vec: vec.clone(), vec: Vec::from(bytes),
offset: 0, offset: 0,
}) })
} }

View File

@ -1,6 +1,6 @@
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult}; use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult};
use crate::lang::pipe::CrushStream; use crate::lang::pipe::{CrushStream, Stream};
use crate::lang::{data::table::ColumnType, data::table::Row, value::Field, value::Value, value::ValueType}; use crate::lang::{data::table::ColumnType, data::table::Row, value::Value, value::ValueType};
use crate::util::identity_arc::Identity; use crate::util::identity_arc::Identity;
use chrono::Duration; use chrono::Duration;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -8,6 +8,7 @@ use std::collections::HashSet;
use std::hash::Hasher; use std::hash::Hasher;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::lang::value::VecReader;
#[derive(Clone)] #[derive(Clone)]
pub struct List { pub struct List {
@ -179,12 +180,18 @@ impl List {
Ok(()) Ok(())
} }
pub fn stream(&self) -> Stream {
let mut vec = Vec::new();
self.dump_value(&mut vec);
Box::new(VecReader::new(vec, self.cell_type.clone()))
}
dump_to!(dump_string, String, String, |e: &String| e.to_string()); dump_to!(dump_string, String, String, |e: &String| e.to_string());
dump_to!(dump_integer, i128, Integer, |v: &i128| *v); dump_to!(dump_integer, i128, Integer, |v: &i128| *v);
dump_to!(dump_bool, bool, Bool, |v: &bool| *v); dump_to!(dump_bool, bool, Bool, |v: &bool| *v);
dump_to!(dump_type, ValueType, Type, |v: &ValueType| v.clone()); dump_to!(dump_type, ValueType, Type, |v: &ValueType| v.clone());
dump_to!(dump_float, f64, Float, |v: &f64| *v); dump_to!(dump_float, f64, Float, |v: &f64| *v);
dump_to!(dump_field, Field, Field, |e: &Field| e.clone()); dump_to!(dump_symbol, String, Symbol, |e: &String| e.clone());
} }
impl Display for List { impl Display for List {
@ -213,7 +220,7 @@ impl std::hash::Hash for List {
} }
} }
impl std::cmp::PartialEq for List { impl PartialEq for List {
fn eq(&self, other: &List) -> bool { fn eq(&self, other: &List) -> bool {
let us = self.cells.lock().unwrap().clone(); let us = self.cells.lock().unwrap().clone();
let them = other.cells.lock().unwrap().clone(); let them = other.cells.lock().unwrap().clone();
@ -229,7 +236,7 @@ impl std::cmp::PartialEq for List {
} }
} }
impl std::cmp::PartialOrd for List { impl PartialOrd for List {
fn partial_cmp(&self, other: &List) -> Option<Ordering> { fn partial_cmp(&self, other: &List) -> Option<Ordering> {
let us = self.cells.lock().unwrap().clone(); let us = self.cells.lock().unwrap().clone();
let them = other.cells.lock().unwrap().clone(); let them = other.cells.lock().unwrap().clone();
@ -246,40 +253,3 @@ impl std::cmp::PartialOrd for List {
Some(Ordering::Equal) Some(Ordering::Equal)
} }
} }
pub struct ListReader {
list: List,
idx: usize,
types: Vec<ColumnType>,
}
impl ListReader {
pub fn new(list: List, name: &str) -> ListReader {
ListReader {
types: vec![ColumnType::new(name, list.element_type())],
list,
idx: 0usize,
}
}
}
impl CrushStream for ListReader {
fn read(&mut self) -> CrushResult<Row> {
self.idx += 1;
Ok(Row::new(vec![self.list.get(self.idx - 1)?]))
}
fn read_timeout(
&mut self,
_timeout: Duration,
) -> Result<Row, crate::lang::pipe::RecvTimeoutError> {
match self.read() {
Ok(r) => Ok(r),
Err(_) => Err(crate::lang::pipe::RecvTimeoutError::Disconnected),
}
}
fn types(&self) -> &[ColumnType] {
&self.types
}
}

View File

@ -1,5 +1,5 @@
use crate::lang::command::{Command, CrushCommand, OutputType, ArgumentDescription}; use crate::lang::command::{Command, CrushCommand, OutputType, ArgumentDescription};
use crate::lang::errors::{error, mandate, CrushResult, argument_error_legacy}; use crate::lang::errors::{error, mandate, CrushResult, argument_error_legacy, CrushError};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::help::Help; use crate::lang::help::Help;
use crate::lang::data::r#struct::Struct; use crate::lang::data::r#struct::Struct;
@ -9,6 +9,7 @@ use ordered_map::OrderedMap;
use std::cmp::max; use std::cmp::max;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::lang::errors::CrushErrorType::GenericError;
/** /**
This is where we store variables, including functions. This is where we store variables, including functions.
@ -154,20 +155,20 @@ impl ScopeLoader {
pub struct ScopeData { pub struct ScopeData {
/** This is the parent scope used to perform variable name resolution. If a variable lookup /** This is the parent scope used to perform variable name resolution. If a variable lookup
fails in the current scope, it proceeds to this scope. This is usually the scope in which this fails in the current scope, it proceeds to this scope. This is usually the scope in which this
scope was *created*. scope was *created*.
Not that when scopes are used as namespaces, they do not use this scope. Not that when scopes are used as namespaces, they do not use this scope.
*/ */
pub parent_scope: Option<Scope>, pub parent_scope: Option<Scope>,
/** This is the scope in which the current scope was called. Since a closure can be called /** This is the scope in which the current scope was called. Since a closure can be called
from inside any scope, it need not be the same as the parent scope. This scope is the one used from inside any scope, it need not be the same as the parent scope. This scope is the one used
for break/continue loop control, and it is also the scope that builds up the namespace hierarchy. */ for break/continue loop control, and it is also the scope that builds up the namespace hierarchy. */
pub calling_scope: Option<Scope>, pub calling_scope: Option<Scope>,
/** This is a list of scopes that are imported into the current scope. Anything directly inside /** This is a list of scopes that are imported into the current scope. Anything directly inside
one of these scopes is also considered part of this scope. */ one of these scopes is also considered part of this scope. */
pub uses: Vec<Scope>, pub uses: Vec<Scope>,
/** The actual data of this scope. */ /** The actual data of this scope. */
@ -177,11 +178,11 @@ pub struct ScopeData {
pub is_loop: bool, pub is_loop: bool,
/** True if this scope should stop execution, i.e. if the continue or break commands have been /** True if this scope should stop execution, i.e. if the continue or break commands have been
called. */ called. */
pub is_stopped: bool, pub is_stopped: bool,
/** True if this scope can not be further modified. Note that mutable variables in it, e.g. /** True if this scope can not be further modified. Note that mutable variables in it, e.g.
lists can still be modified. */ lists can still be modified. */
pub is_readonly: bool, pub is_readonly: bool,
pub name: Option<String>, pub name: Option<String>,
@ -351,6 +352,15 @@ impl Scope {
} }
} }
pub fn get_calling_scope(&self) -> CrushResult<Scope> {
let data = self.lock()?;
if let Some(scope) = &data.calling_scope {
Ok(scope.clone())
} else {
error("Scope not found")
}
}
pub fn do_break(&self) -> CrushResult<bool> { pub fn do_break(&self) -> CrushResult<bool> {
let mut data = self.lock()?; let mut data = self.lock()?;
if data.is_readonly { if data.is_readonly {

View File

@ -58,7 +58,7 @@ impl CrushStream for TableReader {
Ok(self Ok(self
.rows .rows
.rows .rows
.replace(self.idx - 1, Row::new(vec![Value::Integer(0)]))) .replace(self.idx - 1, Row::new(vec![Value::Empty()])))
} }
fn read_timeout( fn read_timeout(
@ -147,19 +147,18 @@ impl ColumnType {
impl Display for ColumnType { impl Display for ColumnType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.name.fmt(f)?; self.name.fmt(f)?;
f.write_str("=(")?; f.write_str("=($")?;
self.cell_type.fmt(f)?; self.cell_type.fmt(f)?;
f.write_str(")") f.write_str(")")
} }
} }
pub trait ColumnVec { pub trait ColumnVec {
fn find_str(&self, needle: &str) -> CrushResult<usize>; fn find(&self, needle: &str) -> CrushResult<usize>;
fn find(&self, needle: &[String]) -> CrushResult<usize>;
} }
impl ColumnVec for &[ColumnType] { impl ColumnVec for &[ColumnType] {
fn find_str(&self, needle: &str) -> CrushResult<usize> { fn find(&self, needle: &str) -> CrushResult<usize> {
for (idx, field) in self.iter().enumerate() { for (idx, field) in self.iter().enumerate() {
if field.name == needle { if field.name == needle {
return Ok(idx); return Ok(idx);
@ -177,29 +176,4 @@ impl ColumnVec for &[ColumnType] {
.as_str(), .as_str(),
) )
} }
fn find(&self, needle_vec: &[String]) -> CrushResult<usize> {
if needle_vec.len() != 1 {
argument_error_legacy("Expected direct field")
} else {
let needle = &needle_vec[0];
for (idx, field) in self.iter().enumerate() {
if &field.name == needle {
return Ok(idx);
}
}
error(
format!(
"Unknown column {}, available columns are {}",
needle,
self.iter()
.map(|t| t.name.to_string())
.collect::<Vec<String>>()
.join(", "),
)
.as_str(),
)
}
}
} }

View File

@ -1,6 +1,6 @@
use std::error::Error; use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
use CrushErrorType::*; use CrushErrorType::*;
use std::cmp::{min, max}; use std::cmp::{min, max};

View File

@ -40,7 +40,7 @@ pub fn pup(
}, },
)?; )?;
cmd.invoke(CommandContext::new(&env, global_state)); cmd.eval(CommandContext::new(&env, global_state).with_output(snd));
global_state.threads().join(global_state.printer()); global_state.threads().join(global_state.printer());
Ok(()) Ok(())
@ -58,7 +58,7 @@ pub fn string(
) -> CrushResult<()> { ) -> CrushResult<()> {
let jobs = global_state.parser().parse(command, &global_env)?; let jobs = global_state.parser().parse(command, &global_env)?;
for job_definition in jobs { for job_definition in jobs {
let handle = job_definition.invoke(JobContext::new( let handle = job_definition.eval(JobContext::new(
empty_channel(), empty_channel(),
output.clone(), output.clone(),
global_env.clone(), global_env.clone(),

View File

@ -11,7 +11,7 @@ use crate::lang::pipe::{
black_hole, empty_channel, InputStream, OutputStream, ValueReceiver, ValueSender, black_hole, empty_channel, InputStream, OutputStream, ValueReceiver, ValueSender,
}; };
use crate::lang::printer::Printer; use crate::lang::printer::Printer;
use crate::lang::value::{Value, ValueType}; use crate::lang::value::{Symbol, Value, ValueType};
use crate::util::glob::Glob; use crate::util::glob::Glob;
use crate::util::replace::Replace; use crate::util::replace::Replace;
use chrono::{DateTime, Duration, Local}; use chrono::{DateTime, Duration, Local};
@ -26,7 +26,7 @@ pub trait ArgumentVector {
fn string(&mut self, idx: usize) -> CrushResult<String>; fn string(&mut self, idx: usize) -> CrushResult<String>;
fn integer(&mut self, idx: usize) -> CrushResult<i128>; fn integer(&mut self, idx: usize) -> CrushResult<i128>;
fn float(&mut self, idx: usize) -> CrushResult<f64>; fn float(&mut self, idx: usize) -> CrushResult<f64>;
fn field(&mut self, idx: usize) -> CrushResult<Vec<String>>; fn symbol(&mut self, idx: usize) -> CrushResult<Symbol>;
fn file(&mut self, idx: usize) -> CrushResult<PathBuf>; fn file(&mut self, idx: usize) -> CrushResult<PathBuf>;
fn command(&mut self, idx: usize) -> CrushResult<Command>; fn command(&mut self, idx: usize) -> CrushResult<Command>;
fn r#type(&mut self, idx: usize) -> CrushResult<ValueType>; fn r#type(&mut self, idx: usize) -> CrushResult<ValueType>;
@ -39,7 +39,7 @@ pub trait ArgumentVector {
fn optional_integer(&mut self, idx: usize) -> CrushResult<Option<i128>>; fn optional_integer(&mut self, idx: usize) -> CrushResult<Option<i128>>;
fn optional_string(&mut self, idx: usize) -> CrushResult<Option<String>>; fn optional_string(&mut self, idx: usize) -> CrushResult<Option<String>>;
fn optional_command(&mut self, idx: usize) -> CrushResult<Option<Command>>; fn optional_command(&mut self, idx: usize) -> CrushResult<Option<Command>>;
fn optional_field(&mut self, idx: usize) -> CrushResult<Option<Vec<String>>>; fn optional_symbol(&mut self, idx: usize) -> CrushResult<Option<Symbol>>;
fn optional_value(&mut self, idx: usize) -> CrushResult<Option<Value>>; fn optional_value(&mut self, idx: usize) -> CrushResult<Option<Value>>;
} }
@ -132,7 +132,7 @@ impl ArgumentVector for Vec<Argument> {
argument_getter!(string, String, String, "string"); argument_getter!(string, String, String, "string");
argument_getter!(integer, i128, Integer, "integer"); argument_getter!(integer, i128, Integer, "integer");
argument_getter!(float, f64, Float, "float"); argument_getter!(float, f64, Float, "float");
argument_getter!(field, Vec<String>, Field, "field"); argument_getter!(symbol, Symbol, Symbol, "field");
argument_getter!(command, Command, Command, "command"); argument_getter!(command, Command, Command, "command");
argument_getter!(r#type, ValueType, Type, "type"); argument_getter!(r#type, ValueType, Type, "type");
argument_getter!(glob, Glob, Glob, "glob"); argument_getter!(glob, Glob, Glob, "glob");
@ -162,7 +162,7 @@ impl ArgumentVector for Vec<Argument> {
optional_argument_getter!(optional_bool, bool, bool); optional_argument_getter!(optional_bool, bool, bool);
optional_argument_getter!(optional_integer, i128, integer); optional_argument_getter!(optional_integer, i128, integer);
optional_argument_getter!(optional_string, String, string); optional_argument_getter!(optional_string, String, string);
optional_argument_getter!(optional_field, Vec<String>, field); optional_argument_getter!(optional_symbol, Symbol, symbol);
optional_argument_getter!(optional_command, Command, command); optional_argument_getter!(optional_command, Command, command);
optional_argument_getter!(optional_value, Value, value); optional_argument_getter!(optional_value, Value, value);
} }
@ -332,7 +332,7 @@ impl CommandContext {
arguments, arguments,
this, this,
global_state: self.global_state, global_state: self.global_state,
handle: self.handle.clone(), handle: self.handle,
} }
} }
@ -347,7 +347,22 @@ impl CommandContext {
arguments: self.arguments, arguments: self.arguments,
this: self.this, this: self.this,
global_state: self.global_state, global_state: self.global_state,
handle: self.handle.clone(), handle: self.handle,
}
}
/**
Return a new Command context that is identical to this one but with a different output sender.
*/
pub fn with_scope(self, scope : Scope) -> CommandContext {
CommandContext {
input: self.input,
output: self.output,
scope,
arguments: self.arguments,
this: self.this,
global_state: self.global_state,
handle: self.handle,
} }
} }

View File

@ -10,6 +10,10 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::convert::{TryFrom}; use std::convert::{TryFrom};
/**
A type representing a set of files. It is used in the signature of builtin commands that
accept files, including globs, regexes, etc.
*/
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Files { pub struct Files {
had_entries: bool, had_entries: bool,
@ -51,6 +55,7 @@ impl Files {
match input.recv()? { match input.recv()? {
Value::BinaryInputStream(b) => Ok(b), Value::BinaryInputStream(b) => Ok(b),
Value::Binary(b) => Ok(<dyn BinaryReader>::vec(&b)), Value::Binary(b) => Ok(<dyn BinaryReader>::vec(&b)),
Value::String(s) => Ok(<dyn BinaryReader>::vec(s.as_bytes())),
_ => argument_error_legacy("Expected either a file to read or binary pipe io"), _ => argument_error_legacy("Expected either a file to read or binary pipe io"),
} }
} else { } else {
@ -69,7 +74,7 @@ impl Files {
self.files[0].clone(), self.files[0].clone(),
))?)) ))?))
} else { } else {
argument_error_legacy("Expected exactly one desitnation file") argument_error_legacy("Expected at most one destination file")
} }
} }
@ -78,7 +83,8 @@ impl Files {
Value::File(p) => self.files.push(p), Value::File(p) => self.files.push(p),
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), &mut self.files)?, Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), &mut self.files)?,
Value::Regex(_, re) => re.match_files(&cwd()?, &mut self.files, printer), Value::Regex(_, re) => re.match_files(&cwd()?, &mut self.files, printer),
value => match value.stream() { Value::Symbol(f) => self.files.push(PathBuf::from(f)),
value => match value.stream()? {
None => return argument_error_legacy("Expected a file name"), None => return argument_error_legacy("Expected a file name"),
Some(mut s) => { Some(mut s) => {
let t = s.types(); let t = s.types();

View File

@ -6,9 +6,10 @@ use crate::lang::threads::ThreadStore;
use num_format::{Grouping, SystemLocale}; use num_format::{Grouping, SystemLocale};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
struct StateData { /**
locale: SystemLocale, A type representing the shared crush state, such as the printer, the running jobs, the running
} threads, etc.
*/
#[derive(Clone)] #[derive(Clone)]
pub struct GlobalState { pub struct GlobalState {
@ -21,6 +22,10 @@ pub struct GlobalState {
parser: Parser, parser: Parser,
} }
struct StateData {
locale: SystemLocale,
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct JobId(usize); pub struct JobId(usize);
@ -47,7 +52,7 @@ pub struct LiveJob {
} }
/** /**
A resource counter. Once it reaches zero, the job is done, and it is removed from the global job A resource tracker. Once it reaches zero, the job is done, and it is removed from the global job
table. table.
*/ */
#[derive(Clone)] #[derive(Clone)]

View File

@ -16,7 +16,7 @@ use crate::lang::global_state::GlobalState;
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::command_invocation::CommandInvocation; use crate::lang::command_invocation::CommandInvocation;
use crate::lang::value::{ValueDefinition, Value}; use crate::lang::value::{ValueDefinition, Value};
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
use crate::lang::execution_context::JobContext; use crate::lang::execution_context::JobContext;
const DEFAULT_PROMPT: &'static str = "crush# "; const DEFAULT_PROMPT: &'static str = "crush# ";
@ -46,7 +46,7 @@ pub fn execute_prompt(
ValueDefinition::Value(Value::Command(prompt), Location::new(0, 0)), ValueDefinition::Value(Value::Command(prompt), Location::new(0, 0)),
vec![]); vec![]);
let (snd, recv) = pipe(); let (snd, recv) = pipe();
cmd.invoke(JobContext::new( cmd.eval(JobContext::new(
empty_channel(), empty_channel(),
snd, snd,
env.clone(), env.clone(),

View File

@ -57,7 +57,7 @@ impl RustylineHelper {
QuotedString => highlight.get(&Value::string("string_literal")), QuotedString => highlight.get(&Value::string("string_literal")),
Regex => highlight.get(&Value::string("string_literal")), Regex => highlight.get(&Value::string("string_literal")),
QuotedFile => highlight.get(&Value::string("file_literal")), QuotedFile => highlight.get(&Value::string("file_literal")),
LabelOrWildcard => highlight.get(&Value::string("label")), StringOrWildcard => highlight.get(&Value::string("label")),
Integer => highlight.get(&Value::string("numeric_literal")), Integer => highlight.get(&Value::string("numeric_literal")),
Float => highlight.get(&Value::string("numeric_literal")), Float => highlight.get(&Value::string("numeric_literal")),
Field => highlight.get(&Value::string("field")), Field => highlight.get(&Value::string("field")),

View File

@ -4,7 +4,7 @@ use crate::lang::execution_context::{CompileContext, JobContext};
use crate::lang::pipe::pipe; use crate::lang::pipe::pipe;
use std::thread::ThreadId; use std::thread::ThreadId;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
#[derive(Clone)] #[derive(Clone)]
pub struct Job { pub struct Job {
@ -23,7 +23,7 @@ impl Job {
pub fn can_block(&self, context: &mut CompileContext) -> bool { pub fn can_block(&self, context: &mut CompileContext) -> bool {
if self.commands.len() == 1 { if self.commands.len() == 1 {
self.commands[0].can_block(self.commands[0].arguments(), context) self.commands[0].can_block(context)
} else { } else {
true true
} }
@ -33,13 +33,13 @@ impl Job {
&self.commands &self.commands
} }
pub fn invoke(&self, context: JobContext) -> CrushResult<Option<ThreadId>> { pub fn eval(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
let context = context.running(self.to_string()); let context = context.running(self.to_string());
let mut input = context.input.clone(); let mut input = context.input.clone();
let last_job_idx = self.commands.len() - 1; let last_job_idx = self.commands.len() - 1;
for call_def in &self.commands[..last_job_idx] { for call_def in &self.commands[..last_job_idx] {
let (output, next_input) = pipe(); let (output, next_input) = pipe();
call_def.invoke(context.with_io(input, output))?; call_def.eval(context.with_io(input, output))?;
input = next_input; input = next_input;
if context.scope.is_stopped() { if context.scope.is_stopped() {
@ -52,15 +52,16 @@ impl Job {
} }
let last_call_def = &self.commands[last_job_idx]; let last_call_def = &self.commands[last_job_idx];
last_call_def.invoke(context.with_io(input, context.output.clone())).map_err(|e| e.with_location(self.location)) last_call_def.eval(context.with_io(input, context.output.clone())).map_err(|e| e.with_location(self.location))
} }
pub fn as_string(&self) -> Option<String> { /** Extracts the help message from a closure definition */
pub fn extract_help_message(&self) -> Option<String> {
if self.commands.len() != 1 { if self.commands.len() != 1 {
return None; return None;
} }
self.commands[0].as_string() self.commands[0].extract_help_message()
} }
} }

View File

@ -1,4 +1,6 @@
use crate::lang::ast::*; use crate::lang::ast::*;
use crate::lang::ast::location::Location;
use crate::lang::ast::tracked_string::TrackedString;
grammar; grammar;
@ -102,15 +104,15 @@ ParameterList: Vec<ParameterNode> = {
} }
Parameter: ParameterNode = { Parameter: ParameterNode = {
<start: @L> <l: LabelOrWildcard> <end: @R> <d: Default> => <start: @L> <l: Identifier> <end: @R> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), None, d), ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), None, d),
<start: @L> <l: LabelOrWildcard> <end: @R> Colon <t: Item> <d: Default> => <start: @L> <l: Identifier> <end: @R> Colon <t: Item> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), Some(t), d), ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), Some(t), d),
"@" <start: @L> <l: LabelOrWildcard> <end: @R> => ParameterNode::Unnamed(TrackedString::from(l, Location::new(start, end))), "@" <start: @L> <l: Identifier> <end: @R> => ParameterNode::Unnamed(TrackedString::from(&l[1..], Location::new(start, end))),
"@@" <start: @L> <l: LabelOrWildcard> <end: @R> => ParameterNode::Named(TrackedString::from(l, Location::new(start, end))), "@@" <start: @L> <l: Identifier> <end: @R> => ParameterNode::Named(TrackedString::from(&l[1..], Location::new(start, end))),
} }
Default: Option<Node> = { Default: Option<Node> = {
@ -119,9 +121,11 @@ Default: Option<Node> = {
} }
Item: Box<Node> = { Item: Box<Node> = {
<start: @L> <l: LabelOrWildcard> <end: @R> => <start: @L> <l: StringOrWildcard> <end: @R> =>
Node::parse_label_or_wildcard(&TrackedString::from(l, Location::new(start, end))), Node::parse_string_or_wildcard(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Identifier> <end: @R> =>
Node::parse_identifier(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Regex> <end: @R> => <start: @L> <l: Regex> <end: @R> =>
Box::from(Node::Regex(TrackedString::from(&l[3..l.len()-1], Location::new(start, end)))), Box::from(Node::Regex(TrackedString::from(&l[3..l.len()-1], Location::new(start, end)))),
@ -145,12 +149,12 @@ Item: Box<Node> = {
<start: @L> <l:Flag> <end: @R> => <start: @L> <l:Flag> <end: @R> =>
Box::from( Box::from(
Node::Assignment(Box::from(Node::Label(TrackedString::from(&l[2..], Location::new(start+2, end)))), Node::Assignment(Box::from(Node::Field(TrackedString::from(&l[2..], Location::new(start+2, end)))),
"=".to_string(), "=".to_string(),
Box::from(Node::Label(TrackedString::from("true", Location::new(start, start+2)))))), Box::from(Node::Identifier(TrackedString::from("true", Location::new(start, start+2)))))),
<i: Item> "[" <e: Assignment> "]" => Box::from(Node::GetItem(i, e)), <i: Item> "[" <e: Assignment> "]" => Box::from(Node::GetItem(i, e)),
<i: Item> Colon <start: @L> <l: LabelOrWildcard> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(&l, Location::new(start, end)))), <i: Item> Colon <start: @L> <l: StringOrWildcard> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(&l, Location::new(start, end)))),
"{" Separator? <s: Signature> <l: JobListWithoutSeparator> "}" => Box::from(Node::Closure(s, l)), "{" Separator? <s: Signature> <l: JobListWithoutSeparator> "}" => Box::from(Node::Closure(s, l)),
"(" <j:Job> ")" => Box::from(Node::Substitution(j)), "(" <j:Job> ")" => Box::from(Node::Substitution(j)),
} }
@ -169,7 +173,8 @@ Token: TokenNode = {
@L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>), @L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>),
@L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>), @L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>),
@L QuotedString @R => TokenNode::new(TokenType::QuotedString, <>), @L QuotedString @R => TokenNode::new(TokenType::QuotedString, <>),
@L LabelOrWildcard @R => TokenNode::new(TokenType::LabelOrWildcard, <>), @L Identifier @R => TokenNode::new(TokenType::Identifier, <>),
@L StringOrWildcard @R => TokenNode::new(TokenType::StringOrWildcard, <>),
@L QuotedFile @R => TokenNode::new(TokenType::QuotedFile, <>), @L QuotedFile @R => TokenNode::new(TokenType::QuotedFile, <>),
@L FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>), @L FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>),
@L Flag @R => TokenNode::new(TokenType::Flag, <>), @L Flag @R => TokenNode::new(TokenType::Flag, <>),
@ -202,9 +207,10 @@ match {
r"(\*|//)" => FactorOperator, r"(\*|//)" => FactorOperator,
r"(\+|-)" => TermOperator, r"(\+|-)" => TermOperator,
r#""([^\\"]|\\.)*""# => QuotedString, r#""([^\\"]|\\.)*""# => QuotedString,
r"[_a-zA-Z%\?][\._0-9a-zA-Z%\?/]*" => LabelOrWildcard, r"[_a-zA-Z%\?][\._0-9a-zA-Z%\?/]*" => StringOrWildcard,
r"(\.[\./_0-9a-zA-Z%\?]*|/([\._0-9a-zA-Z%\?][\./_0-9a-zA-Z%\?]*)?)" => FileOrWildcard, r"\$[_0-9a-zA-Z][_0-9a-zA-Z]*" => Identifier,
r"--[_0-9a-zA-Z]+" => Flag, r"([\.~][\./_0-9a-zA-Z%\?]*|/([\._0-9a-zA-Z%\?][\./_0-9a-zA-Z%\?]*)?)" => FileOrWildcard,
r"--[_0-9a-zA-Z]*" => Flag,
r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field, r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field,
r#"'([^\\']|\\.)*'"# => QuotedFile, r#"'([^\\']|\\.)*'"# => QuotedFile,
r#"re"([^"]|\\.)*""# => Regex, r#"re"([^"]|\\.)*""# => Regex,

View File

@ -1,3 +1,6 @@
/**
Core language implementation lives in this crate
*/
pub mod argument; pub mod argument;
pub mod ast; pub mod ast;
pub mod command; pub mod command;

View File

@ -1,3 +1,7 @@
/**
A type representing a f64 or an i128. It is used in the signature of builtin commands that
accept any type of nomeric value as arguments, e.g. the math library.
*/
pub enum Number { pub enum Number {
Float(f64), Float(f64),
Integer(i128), Integer(i128),

View File

@ -3,6 +3,11 @@ use crate::util::glob::Glob;
use regex::Regex; use regex::Regex;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
/**
A type representing a set of text patterns. The test method can be used to check if the patterns
match.
*/
pub struct Patterns { pub struct Patterns {
patterns: Vec<Value>, patterns: Vec<Value>,
} }

View File

@ -174,6 +174,17 @@ pub fn pipe() -> (ValueSender, ValueReceiver) {
) )
} }
/**
A Sender/Receiver pair that is bounded to only one Value on the wire before blocking.
*/
pub fn printer_pipe() -> (ValueSender, ValueReceiver) {
let (send, recv) = bounded(1);
(
ValueSender { sender: send, is_pipeline: false },
ValueReceiver { receiver: recv, is_pipeline: false },
)
}
pub fn streams(signature: Vec<ColumnType>) -> (OutputStream, InputStream) { pub fn streams(signature: Vec<ColumnType>) -> (OutputStream, InputStream) {
let (output, input) = bounded(128); let (output, input) = bounded(128);
( (

View File

@ -4,9 +4,8 @@
use crate::lang::data::binary::BinaryReader; use crate::lang::data::binary::BinaryReader;
use crate::lang::errors::to_crush_error; use crate::lang::errors::to_crush_error;
use crate::lang::data::list::ListReader;
use crate::lang::printer::Printer; use crate::lang::printer::Printer;
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, pipe}; use crate::lang::pipe::{CrushStream, InputStream, ValueSender, printer_pipe, Stream};
use crate::lang::data::table::ColumnType; use crate::lang::data::table::ColumnType;
use crate::lang::data::table::Row; use crate::lang::data::table::Row;
use crate::lang::data::table::Table; use crate::lang::data::table::Table;
@ -17,12 +16,34 @@ use crate::lang::value::ValueType;
use crate::lang::data::r#struct::Struct; use crate::lang::data::r#struct::Struct;
use std::cmp::max; use std::cmp::max;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use std::ops::Deref;
use std::thread; use std::thread;
use chrono::Duration; use chrono::Duration;
use crate::util::hex::to_hex; use crate::util::hex::to_hex;
use crate::lang::global_state::GlobalState; use crate::lang::global_state::GlobalState;
use num_format::Grouping; use num_format::Grouping;
pub fn create_pretty_printer(
printer: Printer,
global_state: &GlobalState,
) -> ValueSender {
let global_state = global_state.clone();
let (o, i) = printer_pipe();
let printer_clone = printer.clone();
printer_clone.handle_error(to_crush_error(
thread::Builder::new()
.name("output-formater".to_string())
.spawn(move || {
let mut pp = PrettyPrinter { printer, grouping: global_state.grouping() };
while let Ok(val) = i.recv() {
pp.grouping = global_state.grouping();
pp.print_value(val);
}
}),
));
o
}
trait Width { trait Width {
fn width(&self) -> usize; fn width(&self) -> usize;
} }
@ -39,26 +60,6 @@ impl Width for &str {
} }
} }
pub fn create_pretty_printer(
printer: Printer,
global_state: &GlobalState,
) -> ValueSender {
let global_state = global_state.clone();
let (o, i) = pipe();
let printer_clone = printer.clone();
printer_clone.handle_error(to_crush_error(
thread::Builder::new()
.name("output-formater".to_string())
.spawn(move || {
let mut pp = PrettyPrinter { printer, grouping: global_state.grouping() };
while let Ok(val) = i.recv() {
pp.grouping = global_state.grouping();
pp.print_value(val);
}
}),
));
o
}
pub struct PrettyPrinter { pub struct PrettyPrinter {
printer: Printer, printer: Printer,
@ -136,14 +137,14 @@ impl PrettyPrinter {
if list.len() < 8 { if list.len() < 8 {
self.printer.line(list.to_string().as_str()) self.printer.line(list.to_string().as_str())
} else { } else {
self.print_stream(&mut ListReader::new(list, "value"), 0) self.print_stream(list.stream().as_mut(), 0)
} }
} }
_ => self.printer.line(cell.to_pretty_string(self.grouping).as_str()), _ => self.printer.line(cell.to_pretty_string(self.grouping).as_str()),
}; };
} }
fn print_stream(&self, stream: &mut impl CrushStream, indent: usize) { fn print_stream(&self, stream: &mut dyn CrushStream, indent: usize) {
let mut data: Vec<Row> = Vec::new(); let mut data: Vec<Row> = Vec::new();
let mut has_table = false; let mut has_table = false;
@ -388,7 +389,7 @@ impl PrettyPrinter {
Value::TableInputStream(mut output) => self.print_stream(&mut output, indent), Value::TableInputStream(mut output) => self.print_stream(&mut output, indent),
Value::Table(rows) => self.print_stream(&mut TableReader::new(rows), indent), Value::Table(rows) => self.print_stream(&mut TableReader::new(rows), indent),
Value::BinaryInputStream(mut b) => self.print_binary(b.as_mut(), indent), Value::BinaryInputStream(mut b) => self.print_binary(b.as_mut(), indent),
Value::List(list) => self.print_stream(&mut ListReader::new(list, "value"), indent), Value::List(list) => self.print_stream(list.stream().as_mut(), indent),
_ => { _ => {
let mut line = " ".repeat(4 * indent); let mut line = " ".repeat(4 * indent);
line.push_str(&ss); line.push_str(&ss);

View File

@ -16,7 +16,7 @@ use crate::lang::printer::PrinterMessage::*;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use termion::terminal_size; use termion::terminal_size;
use std::cmp::max; use std::cmp::max;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
#[derive(Clone)] #[derive(Clone)]
pub struct Printer { pub struct Printer {

View File

@ -1,9 +1,10 @@
use crate::lang::errors::{error, CrushResult}; use crate::lang::errors::{CrushResult, error};
use crate::lang::serialization::model::{element, Element}; use crate::lang::serialization::model::{element, Element};
use crate::lang::serialization::model; use crate::lang::serialization::model;
use crate::lang::serialization::{DeserializationState, Serializable, SerializationState}; use crate::lang::serialization::{DeserializationState, Serializable, SerializationState};
use crate::lang::ast::{TrackedString, Location}; use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::ast::location::Location;
impl Serializable<TrackedString> for TrackedString { impl Serializable<TrackedString> for TrackedString {
fn deserialize( fn deserialize(

View File

@ -36,7 +36,7 @@ fn serialize_simple(
Value::Bool(b) => element::Element::Bool(*b), Value::Bool(b) => element::Element::Bool(*b),
Value::Empty() => element::Element::Empty(false), Value::Empty() => element::Element::Empty(false),
Value::Time(d) => element::Element::Time(d.timestamp_nanos()), Value::Time(d) => element::Element::Time(d.timestamp_nanos()),
Value::Field(f) => element::Element::Field(f.serialize(elements, state)? as u64), Value::Symbol(f) => element::Element::Symbol(f.to_string()),
_ => return error("Expected simple value"), _ => return error("Expected simple value"),
}), }),
}; };
@ -88,7 +88,7 @@ impl Serializable<Value> for Value {
id, elements, state, id, elements, state,
)?)), )?)),
element::Element::Field(f) => Ok(Value::Field(Vec::deserialize(*f as usize, elements, state)?)), element::Element::Symbol(s) => Ok(Value::Symbol(s.to_string())),
element::Element::UserScope(_) | element::Element::InternalScope(_) => { element::Element::UserScope(_) | element::Element::InternalScope(_) => {
Ok(Value::Scope(Scope::deserialize(id, elements, state)?)) Ok(Value::Scope(Scope::deserialize(id, elements, state)?))
} }
@ -121,7 +121,7 @@ impl Serializable<Value> for Value {
| Value::Bool(_) | Value::Bool(_)
| Value::Empty() | Value::Empty()
| Value::Time(_) | Value::Time(_)
| Value::Field(_) => serialize_simple(self, elements, state), | Value::Symbol(_) => serialize_simple(self, elements, state),
Value::Integer(s) => s.serialize(elements, state), Value::Integer(s) => s.serialize(elements, state),

View File

@ -23,7 +23,7 @@ impl Serializable<ValueType> for ValueType {
4 => ValueType::Command, 4 => ValueType::Command,
5 => ValueType::Binary, 5 => ValueType::Binary,
6 => ValueType::Duration, 6 => ValueType::Duration,
7 => ValueType::Field, 7 => ValueType::Symbol,
8 => ValueType::Glob, 8 => ValueType::Glob,
9 => ValueType::Regex, 9 => ValueType::Regex,
10 => ValueType::Scope, 10 => ValueType::Scope,
@ -84,7 +84,7 @@ impl Serializable<ValueType> for ValueType {
ValueType::Integer => SimpleTypeKind::Integer, ValueType::Integer => SimpleTypeKind::Integer,
ValueType::Time => SimpleTypeKind::Time, ValueType::Time => SimpleTypeKind::Time,
ValueType::Duration => SimpleTypeKind::Duration, ValueType::Duration => SimpleTypeKind::Duration,
ValueType::Field => SimpleTypeKind::Field, ValueType::Symbol => SimpleTypeKind::Symbol,
ValueType::Glob => SimpleTypeKind::Glob, ValueType::Glob => SimpleTypeKind::Glob,
ValueType::Regex => SimpleTypeKind::Regex, ValueType::Regex => SimpleTypeKind::Regex,
ValueType::Command => SimpleTypeKind::Command, ValueType::Command => SimpleTypeKind::Command,

View File

@ -8,7 +8,9 @@ use crossbeam::channel::Receiver;
use crossbeam::channel::unbounded; use crossbeam::channel::unbounded;
use std::time::Duration; use std::time::Duration;
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
/**
A thread management utility. Spawn, track and join on threads.
*/
struct ThreadData { struct ThreadData {
handle: JoinHandle<CrushResult<()>>, handle: JoinHandle<CrushResult<()>>,
creation_time: DateTime<Local>, creation_time: DateTime<Local>,
@ -73,10 +75,10 @@ impl ThreadStore {
}))?; }))?;
let id = handle.thread().id(); let id = handle.thread().id();
let mut data = self.data.lock().unwrap(); let mut data = self.data.lock().unwrap();
data.threads.push(ThreadData { data.threads.push(ThreadData {
handle, handle,
creation_time: Local::now(), creation_time: Local::now(),
}); });
Ok(id) Ok(id)
} }

View File

@ -1,6 +1,6 @@
/** /**
The type representing all values in crush. The type representing all values in crush.
*/ */
mod value_definition; mod value_definition;
mod value_type; mod value_type;
@ -12,13 +12,13 @@ use std::str::FromStr;
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use regex::Regex; use regex::Regex;
use crate::lang::errors::{argument_error_legacy, mandate, CrushResult}; use crate::lang::errors::{argument_error_legacy, mandate, CrushResult, data_error, eof_error};
use crate::lang::data::r#struct::Struct; use crate::lang::data::r#struct::Struct;
use crate::lang::data::r#struct::StructReader; use crate::lang::data::r#struct::StructReader;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::pipe::{streams, InputStream, Stream, OutputStream}; use crate::lang::pipe::{streams, InputStream, Stream, OutputStream, CrushStream};
use crate::lang::data::{ use crate::lang::data::{
binary::BinaryReader, dict::Dict, dict::DictReader, list::List, list::ListReader, binary::BinaryReader, dict::Dict, dict::DictReader, list::List,
table::ColumnType, table::TableReader, table::ColumnType, table::TableReader,
}; };
use crate::util::time::duration_format; use crate::util::time::duration_format;
@ -38,18 +38,20 @@ use crate::util::regex::RegexFileMatcher;
use ordered_map::OrderedMap; use ordered_map::OrderedMap;
pub use value_definition::ValueDefinition; pub use value_definition::ValueDefinition;
pub use value_type::ValueType; pub use value_type::ValueType;
use std::fmt::{Display, Formatter, Debug}; use std::fmt::{Display, Formatter};
use num_format::Grouping; use num_format::Grouping;
use crate::data::table::Row;
use crate::util::escape::escape; use crate::util::escape::escape;
use crate::util::replace::Replace;
pub type Field = Vec<String>; pub type Symbol = String;
pub enum Value { pub enum Value {
String(String), String(String),
Integer(i128), Integer(i128),
Time(DateTime<Local>), Time(DateTime<Local>),
Duration(Duration), Duration(Duration),
Field(Field), Symbol(Symbol),
Glob(Glob), Glob(Glob),
Regex(String, Regex), Regex(String, Regex),
Command(Command), Command(Command),
@ -75,10 +77,7 @@ impl Display for Value {
Value::String(val) => std::fmt::Display::fmt(val, f), Value::String(val) => std::fmt::Display::fmt(val, f),
Value::Integer(val) => std::fmt::Display::fmt(val, f), Value::Integer(val) => std::fmt::Display::fmt(val, f),
Value::Time(val) => f.write_str(&val.format("%Y-%m-%d %H:%M:%S %z").to_string()), Value::Time(val) => f.write_str(&val.format("%Y-%m-%d %H:%M:%S %z").to_string()),
Value::Field(val) => { Value::Symbol(val) => std::fmt::Display::fmt(val, f),
f.write_str("^")?;
f.write_str(&val.join(":"))
}
Value::Glob(val) => std::fmt::Display::fmt(val, f), Value::Glob(val) => std::fmt::Display::fmt(val, f),
Value::Regex(val, _) => { Value::Regex(val, _) => {
f.write_str("re\"")?; f.write_str("re\"")?;
@ -144,6 +143,49 @@ impl From<bool> for Value {
} }
} }
pub struct VecReader {
vec: Vec<Value>,
types: Vec<ColumnType>,
idx: usize,
}
impl VecReader {
pub fn new(
vec: Vec<Value>,
column_type: ValueType,
) -> VecReader {
VecReader {
vec,
types: vec![ColumnType::new("value", column_type)],
idx: 0,
}
}
}
impl CrushStream for VecReader {
fn read(&mut self) -> CrushResult<Row> {
self.idx += 1;
if self.idx > self.vec.len() {
return eof_error()
}
Ok(Row::new(vec![self.vec.replace(self.idx - 1, Value::Empty())]))
}
fn read_timeout(
&mut self,
_timeout: Duration,
) -> Result<Row, crate::lang::pipe::RecvTimeoutError> {
match self.read() {
Ok(r) => Ok(r),
Err(_) => Err(crate::lang::pipe::RecvTimeoutError::Disconnected),
}
}
fn types(&self) -> &[ColumnType] {
&self.types
}
}
impl Value { impl Value {
pub fn bind(self, this: Value) -> Value { pub fn bind(self, this: Value) -> Value {
match self { match self {
@ -218,15 +260,22 @@ impl Value {
Value::String(s.into()) Value::String(s.into())
} }
pub fn stream(&self) -> Option<Stream> { pub fn stream(&self) -> CrushResult<Option<Stream>> {
match self { Ok(match self {
Value::TableInputStream(s) => Some(Box::from(s.clone())), Value::TableInputStream(s) => Some(Box::from(s.clone())),
Value::Table(r) => Some(Box::from(TableReader::new(r.clone()))), Value::Table(r) => Some(Box::from(TableReader::new(r.clone()))),
Value::List(l) => Some(Box::from(ListReader::new(l.clone(), "value"))), Value::List(l) => Some(l.stream()),
Value::Dict(d) => Some(Box::from(DictReader::new(d.clone()))), Value::Dict(d) => Some(Box::from(DictReader::new(d.clone()))),
Value::Struct(s) => Some(Box::from(StructReader::new(s.clone()))), Value::Struct(s) => Some(Box::from(StructReader::new(s.clone()))),
Value::Glob(l) => {
let mut paths = Vec::<PathBuf>::new();
l.glob_files(&cwd()?, &mut paths)?;
Some(Box::from(VecReader::new(
paths.iter().map(|e| { Value::File(e.to_path_buf()) }).collect(),
ValueType::File)))
}
_ => None, _ => None,
} })
} }
pub fn value_type(&self) -> ValueType { pub fn value_type(&self) -> ValueType {
@ -234,7 +283,7 @@ impl Value {
Value::String(_) => ValueType::String, Value::String(_) => ValueType::String,
Value::Integer(_) => ValueType::Integer, Value::Integer(_) => ValueType::Integer,
Value::Time(_) => ValueType::Time, Value::Time(_) => ValueType::Time,
Value::Field(_) => ValueType::Field, Value::Symbol(_) => ValueType::Symbol,
Value::Glob(_) => ValueType::Glob, Value::Glob(_) => ValueType::Glob,
Value::Regex(_, _) => ValueType::Regex, Value::Regex(_, _) => ValueType::Regex,
Value::Command(_) => ValueType::Command, Value::Command(_) => ValueType::Command,
@ -262,7 +311,7 @@ impl Value {
Value::File(p) => v.push(p.clone()), Value::File(p) => v.push(p.clone()),
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), v)?, Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), v)?,
Value::Regex(_, re) => re.match_files(&cwd()?, v, printer), Value::Regex(_, re) => re.match_files(&cwd()?, v, printer),
val => match val.stream() { val => match val.stream()? {
None => return error("Expected a file name"), None => return error("Expected a file name"),
Some(mut s) => { Some(mut s) => {
let t = s.types(); let t = s.types();
@ -329,7 +378,7 @@ impl Value {
ValueType::File => Ok(Value::File(PathBuf::from(str_val.as_str()))), ValueType::File => Ok(Value::File(PathBuf::from(str_val.as_str()))),
ValueType::Glob => Ok(Value::Glob(Glob::new(str_val.as_str()))), ValueType::Glob => Ok(Value::Glob(Glob::new(str_val.as_str()))),
ValueType::Integer => to_crush_error(str_val.parse::<i128>()).map(Value::Integer), ValueType::Integer => to_crush_error(str_val.parse::<i128>()).map(Value::Integer),
ValueType::Field => Ok(Value::Field(vec![str_val])), ValueType::Symbol => Ok(Value::Symbol(str_val)),
ValueType::Regex => { ValueType::Regex => {
to_crush_error(Regex::new(str_val.as_str()).map(|v| Value::Regex(str_val, v))) to_crush_error(Regex::new(str_val.as_str()).map(|v| Value::Regex(str_val, v)))
} }
@ -363,13 +412,13 @@ impl Value {
} }
/** /**
Format this value in a way appropriate for use in the pretty printer. Format this value in a way appropriate for use in the pretty printer.
* Escape non-printable strings * Escape non-printable strings
* Respect integer grouping, but use _ intead of whatever number group * Respect integer grouping, but use _ intead of whatever number group
separator the locale prescribes, so that the number can be copied separator the locale prescribes, so that the number can be copied
and pasted into the terminal again. and pasted into the terminal again.
*/ */
pub fn to_pretty_string(&self, grouping: Grouping) -> String { pub fn to_pretty_string(&self, grouping: Grouping) -> String {
match self { match self {
Value::String(val) => Value::String(val) =>
@ -442,7 +491,7 @@ impl Clone for Value {
Value::String(v) => Value::String(v.clone()), Value::String(v) => Value::String(v.clone()),
Value::Integer(v) => Value::Integer(*v), Value::Integer(v) => Value::Integer(*v),
Value::Time(v) => Value::Time(*v), Value::Time(v) => Value::Time(*v),
Value::Field(v) => Value::Field(v.clone()), Value::Symbol(v) => Value::Symbol(v.clone()),
Value::Glob(v) => Value::Glob(v.clone()), Value::Glob(v) => Value::Glob(v.clone()),
Value::Regex(v, r) => Value::Regex(v.clone(), r.clone()), Value::Regex(v, r) => Value::Regex(v.clone(), r.clone()),
Value::Command(v) => Value::Command(v.as_ref().copy()), Value::Command(v) => Value::Command(v.as_ref().copy()),
@ -488,7 +537,7 @@ impl std::hash::Hash for Value {
Value::String(v) => v.hash(state), Value::String(v) => v.hash(state),
Value::Integer(v) => v.hash(state), Value::Integer(v) => v.hash(state),
Value::Time(v) => v.hash(state), Value::Time(v) => v.hash(state),
Value::Field(v) => v.hash(state), Value::Symbol(v) => v.hash(state),
Value::Glob(v) => v.hash(state), Value::Glob(v) => v.hash(state),
Value::Regex(v, _) => v.hash(state), Value::Regex(v, _) => v.hash(state),
Value::Command(_) => {} Value::Command(_) => {}
@ -530,7 +579,7 @@ impl std::cmp::PartialEq for Value {
(Value::Integer(val1), Value::Integer(val2)) => val1 == val2, (Value::Integer(val1), Value::Integer(val2)) => val1 == val2,
(Value::Time(val1), Value::Time(val2)) => val1 == val2, (Value::Time(val1), Value::Time(val2)) => val1 == val2,
(Value::Duration(val1), Value::Duration(val2)) => val1 == val2, (Value::Duration(val1), Value::Duration(val2)) => val1 == val2,
(Value::Field(val1), Value::Field(val2)) => val1 == val2, (Value::Symbol(val1), Value::Symbol(val2)) => val1 == val2,
(Value::Glob(val1), Value::Glob(val2)) => val1 == val2, (Value::Glob(val1), Value::Glob(val2)) => val1 == val2,
(Value::Regex(val1, _), Value::Regex(val2, _)) => val1 == val2, (Value::Regex(val1, _), Value::Regex(val2, _)) => val1 == val2,
(Value::File(val1), Value::String(val2)) => { (Value::File(val1), Value::String(val2)) => {
@ -569,7 +618,7 @@ impl std::cmp::PartialOrd for Value {
(Value::Integer(val1), Value::Integer(val2)) => Some(val1.cmp(val2)), (Value::Integer(val1), Value::Integer(val2)) => Some(val1.cmp(val2)),
(Value::Time(val1), Value::Time(val2)) => Some(val1.cmp(val2)), (Value::Time(val1), Value::Time(val2)) => Some(val1.cmp(val2)),
(Value::Duration(val1), Value::Duration(val2)) => Some(val1.cmp(val2)), (Value::Duration(val1), Value::Duration(val2)) => Some(val1.cmp(val2)),
(Value::Field(val1), Value::Field(val2)) => Some(val1.cmp(val2)), (Value::Symbol(val1), Value::Symbol(val2)) => Some(val1.cmp(val2)),
(Value::Glob(val1), Value::Glob(val2)) => Some(val1.cmp(val2)), (Value::Glob(val1), Value::Glob(val2)) => Some(val1.cmp(val2)),
(Value::Regex(val1, _), Value::Regex(val2, _)) => Some(val1.cmp(val2)), (Value::Regex(val1, _), Value::Regex(val2, _)) => Some(val1.cmp(val2)),
(Value::File(val1), Value::File(val2)) => Some(val1.cmp(val2)), (Value::File(val1), Value::File(val2)) => Some(val1.cmp(val2)),
@ -631,7 +680,7 @@ mod tests {
assert_eq!(Value::string("1d").convert(ValueType::File).is_err(), false); assert_eq!(Value::string("1d").convert(ValueType::File).is_err(), false);
assert_eq!(Value::string("1d").convert(ValueType::Time).is_err(), true); assert_eq!(Value::string("1d").convert(ValueType::Time).is_err(), true);
assert_eq!( assert_eq!(
Value::string("fad").convert(ValueType::Field).is_err(), Value::string("fad").convert(ValueType::Symbol).is_err(),
false false
); );
} }

View File

@ -1,47 +1,39 @@
use crate::lang::command::Parameter; use crate::lang::command::Parameter;
use crate::lang::errors::{block_error, mandate}; use crate::lang::errors::mandate;
use crate::lang::execution_context::CompileContext; use crate::lang::execution_context::CompileContext;
use crate::lang::{argument::ArgumentDefinition, command::CrushCommand, job::Job}; use crate::lang::{command::CrushCommand, job::Job};
use crate::{ use crate::{
lang::errors::CrushResult, lang::pipe::pipe, lang::pipe::empty_channel, lang::errors::CrushResult, lang::pipe::empty_channel, lang::pipe::pipe,
lang::value::Value, lang::value::Value,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::lang::ast::{Location, TrackedString}; use crate::lang::ast::tracked_string::TrackedString;
use crate::lang::ast::location::Location;
#[derive(Clone)] #[derive(Clone)]
pub enum ValueDefinition { pub enum ValueDefinition {
Value(Value, Location), Value(Value, Location),
ClosureDefinition(Option<TrackedString>, Option<Vec<Parameter>>, Vec<Job>, Location), ClosureDefinition(Option<TrackedString>, Option<Vec<Parameter>>, Vec<Job>, Location),
JobDefinition(Job), JobDefinition(Job),
Label(TrackedString), Identifier(TrackedString),
GetAttr(Box<ValueDefinition>, TrackedString), GetAttr(Box<ValueDefinition>, TrackedString),
Path(Box<ValueDefinition>, TrackedString), Path(Box<ValueDefinition>, TrackedString),
} }
fn file_get(f: &str) -> Option<Value> {
let p = PathBuf::from(f);
if p.exists() {
Some(Value::File(p))
} else {
None
}
}
impl ValueDefinition { impl ValueDefinition {
pub fn location(&self) -> Location { pub fn location(&self) -> Location {
match self { match self {
ValueDefinition::Value(_, l) => *l, ValueDefinition::Value(_, l) => *l,
ValueDefinition::ClosureDefinition(_, _, _, l) => *l, ValueDefinition::ClosureDefinition(_, _, _, l) => *l,
ValueDefinition::JobDefinition(j) => j.location(), ValueDefinition::JobDefinition(j) => j.location(),
ValueDefinition::Label(l) => l.location, ValueDefinition::Identifier(l) => l.location,
ValueDefinition::GetAttr(p, a) | ValueDefinition::GetAttr(p, a) |
ValueDefinition::Path(p, a)=> p.location().union(a.location), ValueDefinition::Path(p, a)=> p.location().union(a.location),
} }
} }
pub fn can_block(&self, _arg: &[ArgumentDefinition], context: &mut CompileContext) -> bool { pub fn can_block(&self, context: &mut CompileContext) -> bool {
match self { match self {
ValueDefinition::JobDefinition(j) => j.can_block(context), ValueDefinition::JobDefinition(j) => j.can_block(context),
ValueDefinition::GetAttr(_inner1, _inner2) => true, ValueDefinition::GetAttr(_inner1, _inner2) => true,
@ -49,33 +41,21 @@ impl ValueDefinition {
} }
} }
pub fn compile_unbound( pub fn eval_and_bind(&self, context: &mut CompileContext) -> CrushResult<Value> {
&self, let (t, v) = self.eval(context)?;
context: &mut CompileContext,
) -> CrushResult<(Option<Value>, Value)> {
self.compile_internal(context, true)
}
pub fn compile_bound(&self, context: &mut CompileContext) -> CrushResult<Value> {
let (t, v) = self.compile_internal(context, true)?;
Ok(t.map(|tt| v.clone().bind(tt)).unwrap_or(v)) Ok(t.map(|tt| v.clone().bind(tt)).unwrap_or(v))
} }
pub fn compile_internal( pub fn eval(
&self, &self,
context: &mut CompileContext, context: &mut CompileContext,
can_block: bool,
) -> CrushResult<(Option<Value>, Value)> { ) -> CrushResult<(Option<Value>, Value)> {
Ok(match self { Ok(match self {
ValueDefinition::Value(v, _) => (None, v.clone()), ValueDefinition::Value(v, _) => (None, v.clone()),
ValueDefinition::JobDefinition(def) => { ValueDefinition::JobDefinition(def) => {
let first_input = empty_channel(); let first_input = empty_channel();
let (last_output, last_input) = pipe(); let (last_output, last_input) = pipe();
if !can_block { def.eval(context.job_context(first_input, last_output))?;
return block_error();
}
def.invoke(context.job_context(first_input, last_output))?;
(None, last_input.recv()?) (None, last_input.recv()?)
} }
ValueDefinition::ClosureDefinition(name, p, c, _) => ( ValueDefinition::ClosureDefinition(name, p, c, _) => (
@ -88,23 +68,20 @@ impl ValueDefinition {
vec![], vec![],
)), )),
), ),
ValueDefinition::Label(s) => ( ValueDefinition::Identifier(s) => (
None, None,
mandate( mandate(
context.env.get(&s.string)?.or_else(|| file_get(&s.string)), context.env.get(&s.string)?,
&format!("Unknown variable {}", self), &format!("Unknown variable {}", self),
)?, )?,
), ),
ValueDefinition::GetAttr(parent_def, entry) => { ValueDefinition::GetAttr(parent_def, entry) => {
let (grand_parent, mut parent) = parent_def.compile_internal(context, can_block)?; let (grand_parent, mut parent) = parent_def.eval(context)?;
parent = if let Value::Command(parent_cmd) = &parent { parent = if let Value::Command(parent_cmd) = &parent {
if !can_block {
return block_error();
}
let first_input = empty_channel(); let first_input = empty_channel();
let (last_output, last_input) = pipe(); let (last_output, last_input) = pipe();
parent_cmd.invoke( parent_cmd.eval(
context context
.job_context(first_input, last_output) .job_context(first_input, last_output)
.command_context(vec![], grand_parent), .command_context(vec![], grand_parent),
@ -125,7 +102,7 @@ impl ValueDefinition {
} }
ValueDefinition::Path(parent_def, entry) => { ValueDefinition::Path(parent_def, entry) => {
let parent = parent_def.compile_internal(context, can_block)?.1; let parent = parent_def.eval(context)?.1;
let val = mandate( let val = mandate(
parent.path(&entry.string), parent.path(&entry.string),
&format!("Missing path entry {} in {}", entry, parent_def), &format!("Missing path entry {} in {}", entry, parent_def),
@ -140,9 +117,9 @@ impl Display for ValueDefinition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self { match &self {
ValueDefinition::Value(v, _location) => v.fmt(f), ValueDefinition::Value(v, _location) => v.fmt(f),
ValueDefinition::Label(v) => v.fmt(f), ValueDefinition::Identifier(v) => v.fmt(f),
ValueDefinition::ClosureDefinition(_, _, _, _location) => f.write_str("<closure>"), ValueDefinition::ClosureDefinition(_, _, _, _location) => f.write_str("<closure>"),
ValueDefinition::JobDefinition(_) => f.write_str("<job>"), ValueDefinition::JobDefinition(j) => j.fmt(f),
ValueDefinition::GetAttr(v, l) => { ValueDefinition::GetAttr(v, l) => {
v.fmt(f)?; v.fmt(f)?;
f.write_str(":")?; f.write_str(":")?;

View File

@ -17,7 +17,7 @@ pub enum ValueType {
Integer, Integer,
Time, Time,
Duration, Duration,
Field, Symbol,
Glob, Glob,
Regex, Regex,
Command, Command,
@ -82,7 +82,7 @@ impl ValueType {
| ValueType::Integer | ValueType::Integer
| ValueType::Time | ValueType::Time
| ValueType::Duration | ValueType::Duration
| ValueType::Field | ValueType::Symbol
| ValueType::Glob | ValueType::Glob
| ValueType::Regex | ValueType::Regex
| ValueType::Command | ValueType::Command
@ -131,7 +131,7 @@ impl ValueType {
Ok(n) => Ok(Value::Integer(n)), Ok(n) => Ok(Value::Integer(n)),
Err(e) => error(e.to_string().as_str()), Err(e) => error(e.to_string().as_str()),
}, },
ValueType::Field => Ok(Value::Field(mandate(parse_name(s), "Invalid field name")?)), ValueType::Symbol => Ok(Value::Symbol(s.to_string())),
ValueType::Glob => Ok(Value::Glob(Glob::new(s))), ValueType::Glob => Ok(Value::Glob(Glob::new(s))),
ValueType::Regex => Ok(Value::Regex(s.to_string(), to_crush_error(Regex::new(s))?)), ValueType::Regex => Ok(Value::Regex(s.to_string(), to_crush_error(Regex::new(s))?)),
ValueType::File => Ok(Value::string(s)), ValueType::File => Ok(Value::string(s)),
@ -155,7 +155,7 @@ impl Help for ValueType {
ValueType::Integer => "A numeric type representing an integer number.", ValueType::Integer => "A numeric type representing an integer number.",
ValueType::Time => "A point in time with nanosecond precision", ValueType::Time => "A point in time with nanosecond precision",
ValueType::Duration => "A difference between two points in time", ValueType::Duration => "A difference between two points in time",
ValueType::Field => "A field is used to represent a path into a datastructure", ValueType::Symbol => "A field is used to represent a path into a datastructure",
ValueType::Glob => "A pattern containing wildcards", ValueType::Glob => "A pattern containing wildcards",
ValueType::Regex => "An advanced pattern that can be used for matching and replacing", ValueType::Regex => "An advanced pattern that can be used for matching and replacing",
ValueType::Command => "A piece fo code that can be called", ValueType::Command => "A piece fo code that can be called",
@ -184,8 +184,8 @@ impl Help for ValueType {
let mut lines = match self { let mut lines = match self {
ValueType::Time => { ValueType::Time => {
vec![" All time instances use the local time zone.\n".to_string()] vec![" All time instances use the local time zone.\n".to_string()]
}, }
_ => {Vec::new()} _ => { Vec::new() }
}; };
let mut keys: Vec<_> = self.fields().into_iter().collect(); let mut keys: Vec<_> = self.fields().into_iter().collect();
@ -218,7 +218,7 @@ impl Display for ValueType {
ValueType::Integer => f.write_str("integer"), ValueType::Integer => f.write_str("integer"),
ValueType::Time => f.write_str("time"), ValueType::Time => f.write_str("time"),
ValueType::Duration => f.write_str("duration"), ValueType::Duration => f.write_str("duration"),
ValueType::Field => f.write_str("field"), ValueType::Symbol => f.write_str("field"),
ValueType::Glob => f.write_str("glob"), ValueType::Glob => f.write_str("glob"),
ValueType::Regex => f.write_str("regex"), ValueType::Regex => f.write_str("regex"),
ValueType::Command => f.write_str("command"), ValueType::Command => f.write_str("command"),

View File

@ -17,7 +17,7 @@ pub fn and(mut context: CommandContext) -> CrushResult<()> {
Value::Command(c) => { Value::Command(c) => {
let (sender, receiver) = pipe(); let (sender, receiver) = pipe();
let cc = context.empty().with_output(sender); let cc = context.empty().with_output(sender);
c.invoke(cc)?; c.eval(cc)?;
match receiver.recv()? { match receiver.recv()? {
Value::Bool(b) => { Value::Bool(b) => {
if !b { if !b {
@ -48,7 +48,7 @@ pub fn or(mut context: CommandContext) -> CrushResult<()> {
Value::Command(c) => { Value::Command(c) => {
let (sender, receiver) = pipe(); let (sender, receiver) = pipe();
let cc = context.empty().with_output(sender); let cc = context.empty().with_output(sender);
c.invoke(cc)?; c.eval(cc)?;
match receiver.recv()? { match receiver.recv()? {
Value::Bool(b) => { Value::Bool(b) => {
if b { if b {

View File

@ -3,16 +3,30 @@ use crate::lang::errors::{mandate, CrushResult};
use crate::lang::execution_context::{ArgumentVector, CommandContext}; use crate::lang::execution_context::{ArgumentVector, CommandContext};
use crate::lang::value::Value; use crate::lang::value::Value;
use crate::lang::data::r#struct::Struct; use crate::lang::data::r#struct::Struct;
use crate::lang::value::ValueType;
use crate::lang::data::table::ColumnType;
use lazy_static::lazy_static;
use crate::data::table::Row;
use crate::lang::command::OutputType::Known;
use crate::lang::pipe::pipe;
lazy_static! {
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("value", ValueType::Any),
];
}
pub fn r#for(mut context: CommandContext) -> CrushResult<()> { pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
context.output.send(Value::Empty())?; let output = context.output.initialize(OUTPUT_TYPE.clone())?;
let (sender, receiver) = pipe();
context.arguments.check_len(2)?; context.arguments.check_len(2)?;
let location = context.arguments[0].location; let location = context.arguments[0].location;
let body = context.arguments.command(1)?; let body = context.arguments.command(1)?;
let iter = context.arguments.remove(0); let iter = context.arguments.remove(0);
let name = iter.argument_type; let name = iter.argument_type;
let mut input = mandate(iter.value.stream(), "Expected a stream")?; let mut input = mandate(iter.value.stream()?, "Expected a stream")?;
while let Ok(line) = input.read() { while let Ok(line) = input.read() {
let env = context.scope.create_child(&context.scope, true); let env = context.scope.create_child(&context.scope, true);
@ -38,7 +52,8 @@ pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
} }
} }
}; };
body.invoke(context.empty())?; body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
output.send(Row::new(vec![receiver.recv()?]))?;
if env.is_stopped() { if env.is_stopped() {
break; break;
} }

View File

@ -22,10 +22,10 @@ fn r#if(context: CommandContext) -> CrushResult<()> {
let cfg: If = If::parse(context.arguments.clone(), &context.global_state.printer())?; let cfg: If = If::parse(context.arguments.clone(), &context.global_state.printer())?;
if cfg.condition { if cfg.condition {
cfg.true_clause.invoke(context.with_args(vec![], None)) cfg.true_clause.eval(context.with_args(vec![], None))
} else { } else {
cfg.false_clause cfg.false_clause
.map(|v| v.invoke(context.with_args(vec![], None))) .map(|v| v.eval(context.with_args(vec![], None)))
.unwrap_or(Ok(())) .unwrap_or(Ok(()))
} }
} }

View File

@ -1,11 +1,24 @@
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::errors::CrushResult; use crate::lang::errors::CrushResult;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::value::ValueType;
use crate::lang::data::table::ColumnType;
use lazy_static::lazy_static;
use crate::data::table::Row;
use crate::lang::pipe::pipe;
use crate::lang::command::OutputType::Known;
use signature::signature; use signature::signature;
lazy_static! {
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("value", ValueType::Any),
];
}
#[signature( #[signature(
r#loop, r#loop,
condition = true, condition = true,
output = Known(ValueType::TableInputStream(OUTPUT_TYPE.clone())),
short = "Repeatedly execute the body until the break command is called.", short = "Repeatedly execute the body until the break command is called.",
example = "loop {\n if (i_am_tired) {\n break\n }\n echo \"Working\"\n }" example = "loop {\n if (i_am_tired) {\n break\n }\n echo \"Working\"\n }"
)] )]
@ -16,10 +29,12 @@ pub struct Loop {
fn r#loop(context: CommandContext) -> CrushResult<()> { fn r#loop(context: CommandContext) -> CrushResult<()> {
let cfg: Loop = Loop::parse(context.arguments.clone(), &context.global_state.printer())?; let cfg: Loop = Loop::parse(context.arguments.clone(), &context.global_state.printer())?;
context.output.initialize(vec![])?; let output = context.output.initialize(OUTPUT_TYPE.clone())?;
let (sender, receiver) = pipe();
loop { loop {
let env = context.scope.create_child(&context.scope, true); let env = context.scope.create_child(&context.scope, true);
cfg.body.invoke(context.empty())?; cfg.body.eval(context.empty().with_scope(env.clone()).with_output(sender.clone()))?;
output.send(Row::new(vec![receiver.recv()?]))?;
if env.is_stopped() { if env.is_stopped() {
break; break;
} }

View File

@ -193,7 +193,7 @@ example = "pipe := ((table_input_stream value=integer):pipe)\n _1 := (seq 100
struct Fg {} struct Fg {}
fn fg(context: CommandContext) -> CrushResult<()> { fn fg(context: CommandContext) -> CrushResult<()> {
let mut result_stream = mandate(context.input.recv()?.stream(), "Invalid input")?; let mut result_stream = mandate(context.input.recv()?.stream()?, "Invalid input")?;
let mut result: Vec<Value> = result_stream.read()?.into(); let mut result: Vec<Value> = result_stream.read()?.into();
if result.len() != 1 { if result.len() != 1 {
data_error("Expected a single row, single column result") data_error("Expected a single row, single column result")

View File

@ -2,12 +2,24 @@ use crate::lang::command::Command;
use crate::lang::errors::{data_error, CrushResult}; use crate::lang::errors::{data_error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::value::Value; use crate::lang::value::Value;
use crate::lang::value::ValueType;
use crate::lang::data::table::ColumnType;
use lazy_static::lazy_static;
use crate::data::table::Row;
use signature::signature; use signature::signature;
use crate::lang::command::OutputType::Known;
use crate::lang::pipe::pipe; use crate::lang::pipe::pipe;
lazy_static! {
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("value", ValueType::Any),
];
}
#[signature( #[signature(
r#while, r#while,
condition = true, condition = true,
output = Known(ValueType::TableInputStream(OUTPUT_TYPE.clone())),
short = "Repeatedly execute the body for as long the condition is met.", short = "Repeatedly execute the body for as long the condition is met.",
long = "The loop body is optional. If not specified, the condition is executed until it returns false.\n This effectively means that the condition becomes the body, and the loop break check comes at\n the end of the loop.", long = "The loop body is optional. If not specified, the condition is executed until it returns false.\n This effectively means that the condition becomes the body, and the loop break check comes at\n the end of the loop.",
example = "while {./some_file:exists} {echo \"hello\"}" example = "while {./some_file:exists} {echo \"hello\"}"
@ -20,14 +32,15 @@ pub struct While {
} }
fn r#while(mut context: CommandContext) -> CrushResult<()> { fn r#while(mut context: CommandContext) -> CrushResult<()> {
context.output.initialize(vec![])?; let output = context.output.initialize(OUTPUT_TYPE.clone())?;
let (body_sender, body_receiver) = pipe();
let cfg: While = While::parse(context.remove_arguments(), &context.global_state.printer())?; let cfg: While = While::parse(context.remove_arguments(), &context.global_state.printer())?;
loop { loop {
let (sender, receiver) = pipe(); let (sender, receiver) = pipe();
let cond_env = context.scope.create_child(&context.scope, true); let cond_env = context.scope.create_child(&context.scope, true);
cfg.condition.invoke(context.empty().with_output(sender))?; cfg.condition.eval(context.empty().with_scope(cond_env.clone()).with_output(sender))?;
if cond_env.is_stopped() { if cond_env.is_stopped() {
break; break;
} }
@ -36,7 +49,8 @@ fn r#while(mut context: CommandContext) -> CrushResult<()> {
Value::Bool(true) => match &cfg.body { Value::Bool(true) => match &cfg.body {
Some(body) => { Some(body) => {
let body_env = context.scope.create_child(&context.scope, true); let body_env = context.scope.create_child(&context.scope, true);
body.invoke(context.empty())?; body.eval(context.empty().with_scope(body_env.clone()).with_output(body_sender.clone()))?;
output.send(Row::new(vec![body_receiver.recv()?]))?;
if body_env.is_stopped() { if body_env.is_stopped() {
break; break;
} }

View File

@ -9,6 +9,7 @@ use nix::unistd::Pid;
use crate::lang::data::dict::Dict; use crate::lang::data::dict::Dict;
use std::env; use std::env;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::data::list::List;
use crate::lang::command::Command; use crate::lang::command::Command;
fn make_env() -> Value { fn make_env() -> Value {
@ -19,6 +20,11 @@ fn make_env() -> Value {
Value::Dict(e) Value::Dict(e)
} }
fn make_arguments() -> Value {
Value::List(List::new(ValueType::String, env::args().map(|a| {Value::string(a)}).collect()))
}
lazy_static! { lazy_static! {
static ref THREADS_OUTPUT_TYPE: Vec<ColumnType> = vec![ static ref THREADS_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("created", ValueType::Time), ColumnType::new("created", ValueType::Time),
@ -65,6 +71,33 @@ fn prompt(context: CommandContext) -> CrushResult<()> {
context.output.send(Value::Empty()) context.output.send(Value::Empty())
} }
lazy_static! {
static ref JOBS_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("id", ValueType::Integer),
ColumnType::new("description", ValueType::String),
];
}
#[signature(
jobs,
can_block = false,
short = "List running jobs",
output = Known(ValueType::TableInputStream(JOBS_OUTPUT_TYPE.clone())),
long = "All currently running jobs")]
struct Jobs {}
fn jobs(context: CommandContext) -> CrushResult<()> {
let output = context.output.initialize(JOBS_OUTPUT_TYPE.clone())?;
for job in context.global_state.jobs() {
output.send(Row::new(vec![
Value::Integer(usize::from(job.id) as i128),
Value::string(job.description),
]))?;
}
Ok(())
}
mod locale { mod locale {
use super::*; use super::*;
use num_format::SystemLocale; use num_format::SystemLocale;
@ -165,9 +198,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
crush.declare("highlight", Value::Dict(highlight))?; crush.declare("highlight", Value::Dict(highlight))?;
crush.declare("env", make_env())?; crush.declare("env", make_env())?;
crush.declare("arguments", make_arguments())?;
Prompt::declare(crush)?; Prompt::declare(crush)?;
Threads::declare(crush)?; Threads::declare(crush)?;
Exit::declare(crush)?; Exit::declare(crush)?;
Jobs::declare(crush)?;
crush.create_namespace( crush.create_namespace(
"locale", "locale",

View File

@ -17,7 +17,7 @@ use std::convert::TryFrom;
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use std::time::Duration; use std::time::Duration;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
struct DBusThing { struct DBusThing {
connection: Connection, connection: Connection,

View File

@ -1,5 +1,5 @@
use crate::lang::command::OutputType::Known; use crate::lang::command::OutputType::Known;
use crate::lang::errors::{to_crush_error, CrushResult}; use crate::lang::errors::{to_crush_error, CrushResult, error};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::help::Help; use crate::lang::help::Help;
use crate::lang::printer::Printer; use crate::lang::printer::Printer;
@ -104,6 +104,10 @@ members of a value, write "dir <value>".
} }
Some(v) => { Some(v) => {
match v { match v {
Value::Symbol(f) => match &context.scope.get_calling_scope()?.get(&f)? {
None => error(format!("Unknown identifier {}", &f))?,
Some(v) => halp(v, &context.global_state.printer()),
},
Value::Command(cmd) => halp(cmd.help(), &context.global_state.printer()), Value::Command(cmd) => halp(cmd.help(), &context.global_state.printer()),
Value::Type(t) => halp(&t, &context.global_state.printer()), Value::Type(t) => halp(&t, &context.global_state.printer()),
v => halp(&v, &context.global_state.printer()), v => halp(&v, &context.global_state.printer()),

View File

@ -1,4 +1,4 @@
use crate::lang::errors::{to_crush_error, CrushResult}; use crate::lang::errors::{to_crush_error, CrushResult, error};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::r#struct::Struct; use crate::lang::data::r#struct::Struct;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
@ -12,7 +12,13 @@ use battery::State;
use chrono::Duration; use chrono::Duration;
use crate::lang::command::OutputType::Known; use crate::lang::command::OutputType::Known;
#[signature(name, can_block = false, short = "name of this host")] extern crate uptime_lib;
#[signature(
name,
can_block = false,
output = Known(ValueType::String),
short = "name of this host")]
struct Name {} struct Name {}
fn name(context: CommandContext) -> CrushResult<()> { fn name(context: CommandContext) -> CrushResult<()> {
@ -34,14 +40,18 @@ lazy_static! {
]; ];
} }
#[signature(uptime, can_block = false, short = "uptime of this host")] #[signature(
uptime,
can_block = false,
output = Known(ValueType::Duration),
short = "uptime of this host")]
struct Uptime {} struct Uptime {}
fn uptime(context: CommandContext) -> CrushResult<()> { fn uptime(context: CommandContext) -> CrushResult<()> {
Ok(()) match uptime_lib::get() {
// context Ok(d) => context.output.send(Value::Duration(Duration::nanoseconds(i64::try_from(d.as_nanos()).unwrap()))),
// .output Err(e) => error(e),
// .send(Value::Duration(Duration::seconds(to_crush_error(psutil::host::uptime())))) }
} }
@ -86,10 +96,14 @@ fn battery(context: CommandContext) -> CrushResult<()> {
Ok(()) Ok(())
} }
#[signature(mem, can_block = false, short = "memory usage of this host.")] #[signature(
struct Mem {} memory,
can_block = false,
output = Known(ValueType::Struct),
short = "memory usage of this host.")]
struct Memory {}
fn mem(context: CommandContext) -> CrushResult<()> { fn memory(context: CommandContext) -> CrushResult<()> {
let mem = to_crush_error(sys_info::mem_info())?; let mem = to_crush_error(sys_info::mem_info())?;
context.output.send(Value::Struct(Struct::new( context.output.send(Value::Struct(Struct::new(
vec![ vec![
@ -114,7 +128,11 @@ fn mem(context: CommandContext) -> CrushResult<()> {
mod os { mod os {
use super::*; use super::*;
#[signature(name, can_block = false, short = "name of the operating system")] #[signature(
name,
can_block = false,
output = Known(ValueType::String),
short = "name of the operating system")]
pub struct Name {} pub struct Name {}
fn name(context: CommandContext) -> CrushResult<()> { fn name(context: CommandContext) -> CrushResult<()> {
@ -124,9 +142,10 @@ mod os {
} }
#[signature( #[signature(
version, version,
can_block = false, can_block = false,
short = "version of the operating system kernel" output = Known(ValueType::String),
short = "version of the operating system kernel"
)] )]
pub struct Version {} pub struct Version {}
@ -140,7 +159,11 @@ mod os {
mod cpu { mod cpu {
use super::*; use super::*;
#[signature(count, can_block = false, short = "number of CPU cores")] #[signature(
count,
can_block = false,
output = Known(ValueType::Integer),
short = "number of CPU cores")]
pub struct Count {} pub struct Count {}
fn count(context: CommandContext) -> CrushResult<()> { fn count(context: CommandContext) -> CrushResult<()> {
@ -149,7 +172,11 @@ mod cpu {
.send(Value::Integer(to_crush_error(sys_info::cpu_num())? as i128)) .send(Value::Integer(to_crush_error(sys_info::cpu_num())? as i128))
} }
#[signature(load, can_block = false, short = "current CPU load")] #[signature(
load,
can_block = false,
output = Known(ValueType::Struct),
short = "current CPU load")]
pub struct Load {} pub struct Load {}
fn load(context: CommandContext) -> CrushResult<()> { fn load(context: CommandContext) -> CrushResult<()> {
@ -164,7 +191,11 @@ mod cpu {
))) )))
} }
#[signature(speed, can_block = false, short = "current CPU frequency")] #[signature(
speed,
can_block = false,
output = Known(ValueType::Integer),
short = "current CPU frequency")]
pub struct Speed {} pub struct Speed {}
fn speed(context: CommandContext) -> CrushResult<()> { fn speed(context: CommandContext) -> CrushResult<()> {
@ -180,7 +211,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
"Metadata about this host", "Metadata about this host",
Box::new(move |host| { Box::new(move |host| {
Battery::declare(host)?; Battery::declare(host)?;
Mem::declare(host)?; Memory::declare(host)?;
Name::declare(host)?; Name::declare(host)?;
Uptime::declare(host)?; Uptime::declare(host)?;
host.create_namespace( host.create_namespace(

View File

@ -66,7 +66,7 @@ fn from(context: CommandContext) -> CrushResult<()> {
skipped += 1; skipped += 1;
continue; continue;
} }
let line_without_newline = &line[0..line.len() - 1]; let line_without_newline = if line.ends_with('\n') {&line[0..line.len() - 1]} else {&line};
let mut split: Vec<&str> = line_without_newline let mut split: Vec<&str> = line_without_newline
.split(separator) .split(separator)
.map(|s| trim.map(|c| s.trim_matches(c)).unwrap_or(s)) .map(|s| trim.map(|c| s.trim_matches(c)).unwrap_or(s))

View File

@ -1,8 +1,6 @@
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::{ use crate::lang::errors::CrushError;
lang::errors::CrushError, use crate::lang::{data::table::Row, value::Value, value::ValueType};
lang::{data::table::Row, value::Value, value::ValueType},
};
use std::io::{BufReader, Write}; use std::io::{BufReader, Write};
use crate::lang::command::OutputType::Unknown; use crate::lang::command::OutputType::Unknown;

View File

@ -64,7 +64,7 @@ struct To {
pub fn to(context: CommandContext) -> CrushResult<()> { pub fn to(context: CommandContext) -> CrushResult<()> {
let cfg: To = To::parse(context.arguments, &context.global_state.printer())?; let cfg: To = To::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let mut out = cfg.file.writer(context.output)?; let mut out = cfg.file.writer(context.output)?;
if input.types().len() != 1 || input.types()[0].cell_type != ValueType::String { if input.types().len() != 1 || input.types()[0].cell_type != ValueType::String {

View File

@ -3,7 +3,7 @@ use crate::lang::errors::{argument_error_legacy, data_error, mandate, CrushResul
use crate::lang::data::list::List; use crate::lang::data::list::List;
use crate::lang::pretty::PrettyPrinter; use crate::lang::pretty::PrettyPrinter;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::value::{Field, ValueType}; use crate::lang::value::ValueType;
use crate::lang::{execution_context::CommandContext, value::Value}; use crate::lang::{execution_context::CommandContext, value::Value};
use signature::signature; use signature::signature;
use rustyline::Editor; use rustyline::Editor;
@ -87,11 +87,11 @@ fn echo(context: CommandContext) -> CrushResult<()> {
member, member,
can_block = false, can_block = false,
short = "Extracts one member from the input struct.", short = "Extracts one member from the input struct.",
example = "http \"example.com\" | member ^body | json:from" example = "http \"example.com\" | member body | json:from"
)] )]
struct Member { struct Member {
#[description("the member to extract.")] #[description("the member to extract.")]
field: Field, field: String,
} }
fn member(context: CommandContext) -> CrushResult<()> { fn member(context: CommandContext) -> CrushResult<()> {
@ -101,8 +101,8 @@ fn member(context: CommandContext) -> CrushResult<()> {
} }
match context.input.recv()? { match context.input.recv()? {
Value::Struct(s) => context.output.send(mandate( Value::Struct(s) => context.output.send(mandate(
s.get(&cfg.field[0]), s.get(&cfg.field),
format!("Unknown field \"{}\"", cfg.field[0]).as_str(), format!("Unknown field \"{}\"", cfg.field).as_str(),
)?), )?),
_ => data_error("Expected a struct"), _ => data_error("Expected a struct"),
} }

View File

@ -12,7 +12,7 @@ to,
can_block = true, can_block = true,
output = Unknown, output = Unknown,
short = "Serialize to pup format", short = "Serialize to pup format",
long = "Pup is the native crush serialization format. All pup types, including", long = "Pup is the native crush serialization format. All Crush types, including",
long = "lambdas can be serialized to this format.", long = "lambdas can be serialized to this format.",
example = "ls | pup:to")] example = "ls | pup:to")]
struct To { struct To {

View File

@ -12,7 +12,7 @@ use std::io::{BufRead, BufReader};
#[signature( #[signature(
from, from,
can_block = true, can_block = true,
short = "Read specified files (or input) as a table, split on the specified separator" short = "Read specified files (or input) as a table, split on the specified separator characters.",
)] )]
struct From { struct From {
#[unnamed()] #[unnamed()]
@ -22,7 +22,8 @@ struct From {
separator: String, separator: String,
#[description("characters to trim from start and end of each token.")] #[description("characters to trim from start and end of each token.")]
trim: Option<String>, trim: Option<String>,
#[description("if false, discard empty tokens.")] #[default(false)]
#[description("allow empty tokens.")]
allow_empty: bool, allow_empty: bool,
} }

View File

@ -1,10 +1,8 @@
use lazy_static::lazy_static;
use crate::lang::command::OutputType::Known; use crate::lang::command::OutputType::Known;
use crate::lang::errors::{to_crush_error, CrushResult}; use crate::lang::errors::{to_crush_error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::data::table::ColumnType; use crate::{lang::value::Value, lang::value::ValueType};
use crate::{data::table::Row, lang::value::Value, lang::value::ValueType};
use nix::sys::signal; use nix::sys::signal;
use nix::unistd::Pid; use nix::unistd::Pid;
use signature::signature; use signature::signature;
@ -23,7 +21,7 @@ mod macos {
use signature::signature; use signature::signature;
lazy_static! { lazy_static! {
static ref PS_OUTPUT_TYPE: Vec<ColumnType> = vec![ static ref LIST_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("pid", ValueType::Integer), ColumnType::new("pid", ValueType::Integer),
ColumnType::new("ppid", ValueType::Integer), ColumnType::new("ppid", ValueType::Integer),
ColumnType::new("user", ValueType::String), ColumnType::new("user", ValueType::String),
@ -58,12 +56,12 @@ mod macos {
} }
#[signature( #[signature(
ps, list,
can_block = true, can_block = true,
short = "Return a table stream containing information on all running processes on the system", short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())), output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")] long = "proc:list accepts no arguments.")]
pub struct Ps {} pub struct List {}
use libproc::libproc::bsd_info::BSDInfo; use libproc::libproc::bsd_info::BSDInfo;
use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType}; use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType};
@ -124,11 +122,11 @@ mod macos {
TaskAllInfo { pbsd, ptinfo } TaskAllInfo { pbsd, ptinfo }
} }
fn ps(context: CommandContext) -> CrushResult<()> { fn list(context: CommandContext) -> CrushResult<()> {
let mut base_procs = Vec::new(); let mut base_procs = Vec::new();
let arg_max = 2048;//get_arg_max(); let arg_max = 2048;//get_arg_max();
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?; let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
let users = create_user_map()?; let users = create_user_map()?;
let mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0}; let mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0};
@ -214,8 +212,8 @@ mod macos {
Value::Integer(i128::from(curr_task.ptinfo.pti_virtual_size)), Value::Integer(i128::from(curr_task.ptinfo.pti_virtual_size)),
Value::Duration(Duration::nanoseconds( Value::Duration(Duration::nanoseconds(
i64::try_from(curr_task.ptinfo.pti_total_user + curr_task.ptinfo.pti_total_system)? * i64::try_from(curr_task.ptinfo.pti_total_user + curr_task.ptinfo.pti_total_system)? *
i64::try_from(info.numer)? / i64::from(info.numer) /
i64::try_from(info.denom)?)), i64::from(info.denom))),
Value::String(name) Value::String(name)
])); ]));
} }
@ -243,7 +241,7 @@ mod linux {
use std::collections::HashMap; use std::collections::HashMap;
lazy_static! { lazy_static! {
static ref PS_OUTPUT_TYPE: Vec<ColumnType> = vec![ static ref LIST_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("pid", ValueType::Integer), ColumnType::new("pid", ValueType::Integer),
ColumnType::new("ppid", ValueType::Integer), ColumnType::new("ppid", ValueType::Integer),
ColumnType::new("status", ValueType::String), ColumnType::new("status", ValueType::String),
@ -276,16 +274,16 @@ mod linux {
#[signature( #[signature(
ps, list,
can_block = true, can_block = true,
short = "Return a table stream containing information on all running processes on the system", short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())), output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")] long = "proc:list accepts no arguments.")]
pub struct Ps {} pub struct List {}
fn ps(context: CommandContext) -> CrushResult<()> { fn list(context: CommandContext) -> CrushResult<()> {
Ps::parse(context.arguments.clone(), &context.global_state.printer())?; List::parse(context.arguments.clone(), &context.global_state.printer())?;
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?; let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
let users = create_user_map()?; let users = create_user_map()?;
match psutil::process::processes() { match psutil::process::processes() {
@ -349,32 +347,6 @@ fn kill(context: CommandContext) -> CrushResult<()> {
context.output.send(Value::Empty()) context.output.send(Value::Empty())
} }
lazy_static! {
static ref JOBS_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("id", ValueType::Integer),
ColumnType::new("description", ValueType::String),
];
}
#[signature(
jobs,
can_block = false,
short = "List running jobs",
output = Known(ValueType::TableInputStream(JOBS_OUTPUT_TYPE.clone())),
long = "All currently running jobs")]
struct Jobs {}
fn jobs(context: CommandContext) -> CrushResult<()> {
let output = context.output.initialize(JOBS_OUTPUT_TYPE.clone())?;
for job in context.global_state.jobs() {
output.send(Row::new(vec![
Value::Integer(usize::from(job.id) as i128),
Value::string(job.description),
]))?;
}
Ok(())
}
pub fn declare(root: &Scope) -> CrushResult<()> { pub fn declare(root: &Scope) -> CrushResult<()> {
let e = root.create_namespace( let e = root.create_namespace(
@ -382,13 +354,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
"Process related commands", "Process related commands",
Box::new(move |env| { Box::new(move |env| {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
macos::Ps::declare(env)?; linux::List::declare(env)?;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos::Ps::declare(env)?; macos::List::declare(env)?;
Kill::declare(env)?; Kill::declare(env)?;
Jobs::declare(env)?;
Ok(()) Ok(())
}))?; }))?;
root.r#use(&e);
Ok(()) Ok(())
} }

View File

@ -2,12 +2,15 @@ use crate::lang::errors::CrushResult;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::value::Value; use crate::lang::value::Value;
use crate::lang::value::ValueType;
use crate::lang::command::OutputType::Known;
use signature::signature; use signature::signature;
#[signature( #[signature(
float, float,
can_block = false, can_block = false,
short = "generate a random floating point number between 0 (inclusive) and 1 (exclusive)" short = "generate a random floating point number between 0 (inclusive) and 1 (exclusive)",
output = Known(ValueType::Float),
)] )]
struct Float { struct Float {
#[default(1.0)] #[default(1.0)]
@ -26,7 +29,8 @@ fn float(context: CommandContext) -> CrushResult<()> {
#[signature( #[signature(
integer, integer,
can_block = false, can_block = false,
short = "generate a random integer between 0 and 1 (or some other specified number)" short = "generate a random integer between 0 and 1 (or some other specified number)",
output = Known(ValueType::Integer),
)] )]
struct Integer { struct Integer {
#[default(2)] #[default(2)]

View File

@ -17,7 +17,7 @@ pub fn count(context: CommandContext) -> CrushResult<()> {
Value::Table(r) => context.output.send(Value::Integer(r.rows().len() as i128)), Value::Table(r) => context.output.send(Value::Integer(r.rows().len() as i128)),
Value::List(r) => context.output.send(Value::Integer(r.len() as i128)), Value::List(r) => context.output.send(Value::Integer(r.len() as i128)),
Value::Dict(r) => context.output.send(Value::Integer(r.len() as i128)), Value::Dict(r) => context.output.send(Value::Integer(r.len() as i128)),
v => match v.stream() { v => match v.stream()? {
Some(mut input) => { Some(mut input) => {
let mut res: i128 = 0; let mut res: i128 = 0;
while let Ok(_) = input.read() { while let Ok(_) = input.read() {

View File

@ -3,7 +3,7 @@ use crate::lang::execution_context::CommandContext;
use crate::lang::data::table::{Row, ColumnVec}; use crate::lang::data::table::{Row, ColumnVec};
use signature::signature; use signature::signature;
use crate::lang::command::OutputType::Unknown; use crate::lang::command::OutputType::Unknown;
use crate::lang::value::Field; use crate::lang::value::Symbol;
use std::collections::HashSet; use std::collections::HashSet;
#[signature( #[signature(
@ -11,17 +11,17 @@ drop,
can_block = true, can_block = true,
short = "Drop all fields mentioned from input, copy remainder of input", short = "Drop all fields mentioned from input, copy remainder of input",
long = "This command is does the opposite of the select command.\n It copies all column except the ones specified from input to output.", long = "This command is does the opposite of the select command.\n It copies all column except the ones specified from input to output.",
example= "ps | drop ^vms ^rss # Drop memory usage columns from output of ps", example= "ps | drop vms rss # Drop memory usage columns from output of ps",
output = Unknown, output = Unknown,
)] )]
pub struct Drop { pub struct Drop {
#[unnamed()] #[unnamed()]
drop: Vec<Field>, drop: Vec<Symbol>,
} }
fn drop(context: CommandContext) -> CrushResult<()> { fn drop(context: CommandContext) -> CrushResult<()> {
let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?; let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let t = input.types(); let t = input.types();
let drop = cfg.drop.iter() let drop = cfg.drop.iter()

View File

@ -1,13 +1,12 @@
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::errors::{error, CrushResult}; use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{black_hole, empty_channel};
use crate::lang::{argument::Argument, data::table::ColumnType}; use crate::lang::{argument::Argument, data::table::ColumnType};
use crate::lang::{data::table::Row, value::Value}; use crate::lang::{data::table::Row, value::Value};
use signature::signature; use signature::signature;
use crate::lang::value::ValueType::Empty; use crate::lang::value::ValueType::Empty;
use crate::lang::command::OutputType::Known; use crate::lang::command::OutputType::Known;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
#[signature( #[signature(
r#each, r#each,
@ -15,7 +14,7 @@ can_block = true,
output = Known(Empty), output = Known(Empty),
short = "Runs a command one for each row of input", short = "Runs a command one for each row of input",
long = "The columns of the row are exported to the environment using the column names.", long = "The columns of the row are exported to the environment using the column names.",
example = "ps | where {status != \"Sleeping\"} | each {echo (\"{} is sleepy\":format name)}")] example = "ps | where {status != \"Sleeping\"} | each {echo (\"{} is sleepy\":format $name)}")]
pub struct Each { pub struct Each {
#[description("the command to run.")] #[description("the command to run.")]
body: Command, body: Command,
@ -34,7 +33,7 @@ fn run(
.map(|(c, t)| Argument::named(t.name.as_ref(), c, location)) .map(|(c, t)| Argument::named(t.name.as_ref(), c, location))
.collect(); .collect();
condition.invoke( condition.eval(
base_context base_context
.clone() .clone()
.with_args(arguments, None) .with_args(arguments, None)
@ -46,7 +45,7 @@ pub fn each(context: CommandContext) -> CrushResult<()> {
let location = context.arguments[0].location; let location = context.arguments[0].location;
context.output.send(Value::Empty())?; context.output.send(Value::Empty())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let base_context = context.empty(); let base_context = context.empty();

View File

@ -6,22 +6,29 @@ use signature::signature;
#[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")] #[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")]
pub struct Enumerate { pub struct Enumerate {
#[description("the index to use for the first row.")]
#[default(0)]
start_index: i128,
#[description("the step between rows.")]
#[default(1)]
step: i128,
} }
fn enumerate(context: CommandContext) -> CrushResult<()> { fn enumerate(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let mut output_type = vec![ let mut output_type = vec![
ColumnType::new("idx", ValueType::Integer)]; ColumnType::new("idx", ValueType::Integer)];
output_type.extend(input.types().to_vec()); output_type.extend(input.types().to_vec());
let output = context.output.initialize(output_type)?; let output = context.output.initialize(output_type)?;
let mut line: i128 = 0; let mut line: i128 = cfg.start_index;
while let Ok(row) = input.read() { while let Ok(row) = input.read() {
let mut out = vec![Value::Integer(line)]; let mut out = vec![Value::Integer(line)];
out.extend(Vec::from(row)); out.extend(Vec::from(row));
output.send(Row::new(out))?; output.send(Row::new(out))?;
line += 1; line += cfg.step;
} }
Ok(()) Ok(())
} }

View File

@ -7,7 +7,7 @@ use crate::lang::data::scope::Scope;
use crate::lang::pipe::{pipe, InputStream}; use crate::lang::pipe::{pipe, InputStream};
use crate::lang::data::table::ColumnType; use crate::lang::data::table::ColumnType;
use crate::lang::data::table::ColumnVec; use crate::lang::data::table::ColumnVec;
use crate::lang::value::Field; use crate::lang::value::Symbol;
use crate::{ use crate::{
lang::errors::argument_error_legacy, lang::errors::argument_error_legacy,
lang::pipe::{unlimited_streams, OutputStream}, lang::pipe::{unlimited_streams, OutputStream},
@ -23,12 +23,12 @@ use crate::lang::global_state::GlobalState;
group, group,
can_block = true, can_block = true,
short = "Group stream by the specified column(s)", short = "Group stream by the specified column(s)",
example = "find . | group ^user ^type file_count={count} size={sum ^size}" example = "find . | group user type file_count={count} size={sum size}"
)] )]
pub struct Group { pub struct Group {
#[unnamed()] #[unnamed()]
#[description("the column(s) to group by and copy into the output stream.")] #[description("the column(s) to group by and copy into the output stream.")]
group_by: Vec<Field>, group_by: Vec<Symbol>,
#[named()] #[named()]
#[description("create these additional columns by aggregating the grouped rows using the supplied aggregation command.")] #[description("create these additional columns by aggregating the grouped rows using the supplied aggregation command.")]
command: OrderedStringMap<Command>, command: OrderedStringMap<Command>,
@ -53,7 +53,7 @@ fn aggregate(
let (output_sender, output_receiver) = pipe(); let (output_sender, output_receiver) = pipe();
input_sender.send(Value::TableInputStream(rows))?; input_sender.send(Value::TableInputStream(rows))?;
drop(input_sender); drop(input_sender);
commands[0].invoke( commands[0].eval(
CommandContext::new(&scope, &global_state) CommandContext::new(&scope, &global_state)
.with_input(input_receiver) .with_input(input_receiver)
.with_output(output_sender) .with_output(output_sender)
@ -74,7 +74,7 @@ fn aggregate(
let local_scope = scope.clone(); let local_scope = scope.clone();
let local_state = global_state.clone(); let local_state = global_state.clone();
threads.spawn("group:aggr", move || threads.spawn("group:aggr", move ||
local_command.invoke( local_command.eval(
CommandContext::new(&local_scope, &local_state) CommandContext::new(&local_scope, &local_state)
.with_input(input_receiver) .with_input(input_receiver)
.with_output(output_sender)))?; .with_output(output_sender)))?;
@ -141,7 +141,7 @@ fn create_worker_thread(
pub fn group(context: CommandContext) -> CrushResult<()> { pub fn group(context: CommandContext) -> CrushResult<()> {
let cfg: Group = Group::parse(context.arguments, &context.global_state.printer())?; let cfg: Group = Group::parse(context.arguments, &context.global_state.printer())?;
let mut input = mandate( let mut input = mandate(
context.input.recv()?.stream(), context.input.recv()?.stream()?,
"Expected input to be a stream", "Expected input to be a stream",
)?; )?;
let input_type = input.types().to_vec(); let input_type = input.types().to_vec();

View File

@ -17,7 +17,7 @@ pub struct Head {
fn head(context: CommandContext) -> CrushResult<()> { fn head(context: CommandContext) -> CrushResult<()> {
let cfg: Head = Head::parse(context.arguments, &context.global_state.printer())?; let cfg: Head = Head::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let output = context.output.initialize(input.types().to_vec())?; let output = context.output.initialize(input.types().to_vec())?;
let mut count = 0; let mut count = 0;

View File

@ -72,11 +72,11 @@ pub fn join(mut context: CommandContext) -> CrushResult<()> {
context.arguments.check_len(2)?; context.arguments.check_len(2)?;
let l = context.arguments.remove(0); let l = context.arguments.remove(0);
let r = context.arguments.remove(0); let r = context.arguments.remove(0);
match (l.argument_type, mandate(l.value.stream(), "Expected a stream")?, match (l.argument_type, mandate(l.value.stream()?, "Expected a stream")?,
r.argument_type, mandate(r.value.stream(), "Expected a stream")?) { r.argument_type, mandate(r.value.stream()?, "Expected a stream")?) {
(Some(left_name), left_stream, Some(right_name), right_stream) => { (Some(left_name), left_stream, Some(right_name), right_stream) => {
let left_idx = left_stream.types().find_str(&left_name)?; let left_idx = left_stream.types().find(&left_name)?;
let right_idx = right_stream.types().find_str(&right_name)?; let right_idx = right_stream.types().find(&right_name)?;
let output_type = get_output_type(left_stream.types(), right_stream.types(), right_idx)?; let output_type = get_output_type(left_stream.types(), right_stream.types(), right_idx)?;
let output = context.output.initialize(output_type)?; let output = context.output.initialize(output_type)?;

View File

@ -45,11 +45,12 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
sum_avg::Avg::declare(env)?; sum_avg::Avg::declare(env)?;
sum_avg::Min::declare(env)?; sum_avg::Min::declare(env)?;
sum_avg::Max::declare(env)?; sum_avg::Max::declare(env)?;
sum_avg::Mul::declare(env)?;
env.declare_command( env.declare_command(
"select", select::select, true, "select", select::select, true,
"select copy_fields:field... [%] new_field=definition:command", "select copy_fields:field... [%] new_field=definition:command",
"Pass on some old fields and calculate new ones for each line of input", "Pass on some old fields and calculate new ones for each line of input",
example!(r#"ls | select ^user path={"{}/{}":format (pwd) file}"#), Unknown, example!(r#"ls | select user path={"{}/{}":format (pwd) file}"#), Unknown,
vec![], vec![],
)?; )?;
seq::Seq::declare(env)?; seq::Seq::declare(env)?;

View File

@ -14,7 +14,7 @@ pub struct Reverse {
fn reverse(context: CommandContext) -> CrushResult<()> { fn reverse(context: CommandContext) -> CrushResult<()> {
Reverse::parse(context.arguments.clone(), &context.global_state.printer())?; Reverse::parse(context.arguments.clone(), &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let output = context.output.initialize(input.types().to_vec())?; let output = context.output.initialize(input.types().to_vec())?;
let mut q: Vec<Row> = Vec::new(); let mut q: Vec<Row> = Vec::new();

View File

@ -1,7 +1,7 @@
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::errors::error; use crate::lang::errors::error;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{pipe, empty_channel, Stream}; use crate::lang::pipe::{pipe, Stream};
use crate::lang::data::table::ColumnVec; use crate::lang::data::table::ColumnVec;
use crate::{ use crate::{
lang::errors::argument_error_legacy, lang::errors::argument_error_legacy,
@ -10,7 +10,7 @@ use crate::{
lang::{argument::Argument, data::table::Row, value::Value}, lang::{argument::Argument, data::table::Row, value::Value},
util::replace::Replace, util::replace::Replace,
}; };
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
enum Action { enum Action {
Replace(usize), Replace(usize),
@ -55,7 +55,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
Argument::named(cell_type.name.as_ref(), cell.clone(), config.location) Argument::named(cell_type.name.as_ref(), cell.clone(), config.location)
}) })
.collect(); .collect();
closure.invoke(context.empty().with_output(sender))?; closure.eval(context.empty().with_args(arguments, None).with_output(sender))?;
receiver.recv()? receiver.recv()?
} }
Source::Argument(idx) => row.cells()[*idx].clone(), Source::Argument(idx) => row.cells()[*idx].clone(),
@ -98,7 +98,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
.map(|(cell, cell_type)| Argument::named(&cell_type.name, cell.clone(), config.location)) .map(|(cell, cell_type)| Argument::named(&cell_type.name, cell.clone(), config.location))
.collect(); .collect();
let (sender, receiver) = pipe(); let (sender, receiver) = pipe();
closure.invoke(context.empty().with_output(sender))?; closure.eval(context.empty().with_args(arguments, None).with_output(sender))?;
receiver.recv()? receiver.recv()?
} }
Source::Argument(idx) => row.cells()[*idx].clone(), Source::Argument(idx) => row.cells()[*idx].clone(),
@ -118,7 +118,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
} }
pub fn select(mut context: CommandContext) -> CrushResult<()> { pub fn select(mut context: CommandContext) -> CrushResult<()> {
match context.input.clone().recv()?.stream() { match context.input.clone().recv()?.stream()? {
Some(input) => { Some(input) => {
let mut copy = false; let mut copy = false;
let mut columns = Vec::new(); let mut columns = Vec::new();
@ -142,7 +142,7 @@ pub fn select(mut context: CommandContext) -> CrushResult<()> {
location = location.union(a.location); location = location.union(a.location);
match (a.argument_type.as_deref(), a.value.clone()) { match (a.argument_type.as_deref(), a.value.clone()) {
(Some(name), Value::Command(closure)) => { (Some(name), Value::Command(closure)) => {
match (copy, input_type.find_str(name)) { match (copy, input_type.find(name)) {
(true, Ok(idx)) => { (true, Ok(idx)) => {
columns.push((Action::Replace(idx), Source::Closure(closure))) columns.push((Action::Replace(idx), Source::Closure(closure)))
} }
@ -152,16 +152,16 @@ pub fn select(mut context: CommandContext) -> CrushResult<()> {
)), )),
} }
} }
(None, Value::Field(name)) => { (None, Value::Symbol(name)) => {
if name.len() != 1 { if name.len() != 1 {
return argument_error_legacy("Invalid field"); return argument_error_legacy("Invalid field");
} }
match (copy, input_type.find_str(name[0].as_ref())) { match (copy, input_type.find(name.as_ref())) {
(false, Ok(idx)) => columns (false, Ok(idx)) => columns
.push((Action::Append(name[0].clone()), Source::Argument(idx))), .push((Action::Append(name.clone()), Source::Argument(idx))),
_ => { _ => {
return argument_error_legacy( return argument_error_legacy(
format!("Unknown field {}", name[0]).as_str(), format!("Unknown field {}", name).as_str(),
); );
} }
} }

View File

@ -1,3 +1,4 @@
use std::mem;
use crate::lang::errors::CrushResult; use crate::lang::errors::CrushResult;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::table::ColumnType; use crate::lang::data::table::ColumnType;
@ -22,9 +23,7 @@ pub fn seq(context: CommandContext) -> CrushResult<()> {
.initialize(vec![ColumnType::new("value", ValueType::Integer)])?; .initialize(vec![ColumnType::new("value", ValueType::Integer)])?;
if (cfg.to > cfg.from) != (cfg.step > 0) { if (cfg.to > cfg.from) != (cfg.step > 0) {
let tmp = cfg.to; mem::swap(&mut cfg.to, &mut cfg.from);
cfg.to = cfg.from;
cfg.from = tmp;
} }
let mut idx = cfg.from; let mut idx = cfg.from;

View File

@ -3,7 +3,7 @@ use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::table::ColumnVec; use crate::lang::data::table::ColumnVec;
use crate::lang::data::table::Row; use crate::lang::data::table::Row;
use crate::lang::value::Field; use crate::lang::value::Symbol;
use crate::lang::errors::argument_error_legacy; use crate::lang::errors::argument_error_legacy;
use signature::signature; use signature::signature;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -11,19 +11,19 @@ use std::cmp::Ordering;
#[signature( #[signature(
sort, sort,
short = "Sort input based on column", short = "Sort input based on column",
example = "ps | sort ^cpu", example = "ps | sort cpu",
output = Passthrough)] output = Passthrough)]
pub struct Sort { pub struct Sort {
#[unnamed()] #[unnamed()]
#[description("the columns to sort on. Optional if input only has one column.")] #[description("the columns to sort on. Optional if input only has one column.")]
field: Vec<Field>, field: Vec<Symbol>,
#[description("reverse the sort order.")] #[description("reverse the sort order.")]
#[default(false)] #[default(false)]
reverse: bool, reverse: bool,
} }
fn sort(context: CommandContext) -> CrushResult<()> { fn sort(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let output = context.output.initialize(input.types().to_vec())?; let output = context.output.initialize(input.types().to_vec())?;
let cfg: Sort = Sort::parse(context.arguments, &context.global_state.printer())?; let cfg: Sort = Sort::parse(context.arguments, &context.global_state.printer())?;

View File

@ -7,9 +7,9 @@ use crate::lang::{value::Value, value::ValueType};
use chrono::Duration; use chrono::Duration;
use float_ord::FloatOrd; use float_ord::FloatOrd;
use signature::signature; use signature::signature;
use crate::lang::value::Field; use crate::lang::value::Symbol;
fn parse(input_type: &[ColumnType], field: Option<Field>) -> CrushResult<usize> { fn parse(input_type: &[ColumnType], field: Option<String>) -> CrushResult<usize> {
field.map(|f| input_type.find(&f)) field.map(|f| input_type.find(&f))
.unwrap_or_else(|| .unwrap_or_else(||
if input_type.len() == 1 { if input_type.len() == 1 {
@ -42,13 +42,13 @@ sum_function!(sum_duration, Duration, Duration::seconds(0), Duration);
#[signature( #[signature(
sum, sum,
short = "Calculate the sum for the specific column across all rows.", short = "Calculate the sum for the specific column across all rows.",
example = "ps | sum ^cpu")] example = "proc:list | sum cpu")]
pub struct Sum { pub struct Sum {
field: Option<Field>, field: Option<Symbol>,
} }
fn sum(context: CommandContext) -> CrushResult<()> { fn sum(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(input) => { Some(input) => {
let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?; let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?; let column = parse(input.types(), cfg.field)?;
@ -94,13 +94,13 @@ avg_function!(avg_duration, Duration, Duration::seconds(0), Duration, i32);
#[signature( #[signature(
avg, avg,
short = "Calculate the average for the specific column across all rows.", short = "Calculate the average for the specific column across all rows.",
example = "ps | avg ^cpu")] example = "proc:list | avg cpu")]
pub struct Avg { pub struct Avg {
field: Option<Field>, field: Option<Symbol>,
} }
fn avg(context: CommandContext) -> CrushResult<()> { fn avg(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(input) => { Some(input) => {
let cfg: Avg = Avg::parse(context.arguments, &context.global_state.printer())?; let cfg: Avg = Avg::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?; let column = parse(input.types(), cfg.field)?;
@ -159,13 +159,13 @@ aggr_function!(max_time, Time, |a, b| std::cmp::max(a, b));
#[signature( #[signature(
min, min,
short = "Calculate the minimum for the specific column across all rows.", short = "Calculate the minimum for the specific column across all rows.",
example = "ps | min ^cpu")] example = "proc:list | min cpu")]
pub struct Min { pub struct Min {
field: Option<Field>, field: Option<Symbol>,
} }
fn min(context: CommandContext) -> CrushResult<()> { fn min(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(input) => { Some(input) => {
let cfg: Min = Min::parse(context.arguments, &context.global_state.printer())?; let cfg: Min = Min::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?; let column = parse(input.types(), cfg.field)?;
@ -186,13 +186,13 @@ fn min(context: CommandContext) -> CrushResult<()> {
#[signature( #[signature(
max, max,
short = "Calculate the maximum for the specific column across all rows.", short = "Calculate the maximum for the specific column across all rows.",
example = "ps | max ^cpu")] example = "proc:list | max cpu")]
pub struct Max { pub struct Max {
field: Option<Field>, field: Option<Symbol>,
} }
fn max(context: CommandContext) -> CrushResult<()> { fn max(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(input) => { Some(input) => {
let cfg: Max = Max::parse(context.arguments, &context.global_state.printer())?; let cfg: Max = Max::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?; let column = parse(input.types(), cfg.field)?;
@ -209,3 +209,46 @@ fn max(context: CommandContext) -> CrushResult<()> {
_ => error("Expected a stream"), _ => error("Expected a stream"),
} }
} }
macro_rules! mul_function {
($name:ident, $var_type:ident, $var_initializer:expr, $value_type:ident) => {
fn $name(mut s: Stream, column: usize) -> CrushResult<Value> {
let mut res: $var_type = $var_initializer;
while let Ok(row) = s.read() {
match row.cells()[column] {
Value::$value_type(i) => res = res * i,
_ => return error("Invalid cell value"),
}
}
Ok(Value::$value_type(res))
}
};
}
mul_function!(mul_int, i128, 1, Integer);
mul_function!(mul_float, f64, 1.0, Float);
#[signature(
mul,
short = "Calculate the product for the specific column across all rows.",
example = "seq 5 10 | mul")]
pub struct Mul {
field: Option<Symbol>,
}
fn mul(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream()? {
Some(input) => {
let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?;
match &input.types()[column].cell_type {
ValueType::Integer => context.output.send(mul_int(input, column)?),
ValueType::Float => context.output.send(mul_float(input, column)?),
t => argument_error_legacy(
&format!("Can't calculate product of elements of type {}", t),
),
}
}
_ => error("Expected a stream"),
}
}

View File

@ -19,7 +19,7 @@ pub struct Tail {
fn tail(context: CommandContext) -> CrushResult<()> { fn tail(context: CommandContext) -> CrushResult<()> {
let cfg: Tail = Tail::parse(context.arguments, &context.global_state.printer())?; let cfg: Tail = Tail::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let output = context.output.initialize(input.types().to_vec())?; let output = context.output.initialize(input.types().to_vec())?;
let mut q: VecDeque<Row> = VecDeque::new(); let mut q: VecDeque<Row> = VecDeque::new();

View File

@ -2,23 +2,22 @@ use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::table::ColumnVec; use crate::lang::data::table::ColumnVec;
use crate::lang::data::table::Row; use crate::lang::data::table::Row;
use crate::lang::value::Value; use crate::lang::value::{Symbol, Value};
use std::collections::HashSet; use std::collections::HashSet;
use signature::signature; use signature::signature;
use crate::lang::command::OutputType::Passthrough; use crate::lang::command::OutputType::Passthrough;
use crate::lang::value::Field;
#[signature( #[signature(
uniq, uniq,
output = Passthrough, output = Passthrough,
short = "Only output the first row if multiple rows has the same value for the specified column", short = "Only output the first row if multiple rows has the same value for the specified column",
example = "ps | uniq ^user")] example = "ps | uniq user")]
pub struct Uniq { pub struct Uniq {
field: Option<Field>, field: Option<Symbol>,
} }
pub fn uniq(context: CommandContext) -> CrushResult<()> { pub fn uniq(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let cfg: Uniq = Uniq::parse(context.arguments, &context.global_state.printer())?; let cfg: Uniq = Uniq::parse(context.arguments, &context.global_state.printer())?;
let output = context.output.initialize(input.types().to_vec())?; let output = context.output.initialize(input.types().to_vec())?;

View File

@ -2,11 +2,11 @@ use crate::lang::command::Command;
use crate::lang::command::OutputType::Passthrough; use crate::lang::command::OutputType::Passthrough;
use crate::lang::errors::{error, CrushResult}; use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{black_hole, pipe, empty_channel};
use crate::lang::{argument::Argument, data::table::ColumnType}; use crate::lang::{argument::Argument, data::table::ColumnType};
use crate::lang::{data::table::Row, value::Value}; use crate::lang::{data::table::Row, value::Value};
use signature::signature; use signature::signature;
use crate::lang::ast::Location; use crate::lang::ast::location::Location;
use crate::lang::pipe::pipe;
#[signature( #[signature(
r#where, r#where,
@ -35,7 +35,7 @@ fn evaluate(
let (sender, reciever) = pipe(); let (sender, reciever) = pipe();
condition.invoke( condition.eval(
base_context base_context
.clone() .clone()
.with_args(arguments, None) .with_args(arguments, None)
@ -52,7 +52,7 @@ pub fn r#where(context: CommandContext) -> CrushResult<()> {
let cfg: Where = Where::parse(context.arguments.clone(), &context.global_state.printer())?; let cfg: Where = Where::parse(context.arguments.clone(), &context.global_state.printer())?;
let location = context.arguments[0].location; let location = context.arguments[0].location;
match context.input.recv()?.stream() { match context.input.recv()?.stream()? {
Some(mut input) => { Some(mut input) => {
let base_context = context.empty(); let base_context = context.empty();

View File

@ -3,7 +3,12 @@ use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::Stream; use crate::lang::pipe::Stream;
use signature::signature; use signature::signature;
#[signature(zip, can_block = true, short = "Combine two streams of data into one")] #[signature(
zip,
can_block = true,
short = "Combine two streams of data into one containing one row of each input stream in each row of output.",
long = "If the two streams have different numbers of rows, the longer stream will be truncated to the length\nof the shorter one."
)]
pub struct Zip { pub struct Zip {
#[description("the first stream.")] #[description("the first stream.")]
first: Stream, first: Stream,

View File

@ -85,6 +85,46 @@ lazy_static! {
Known(ValueType::Bool), Known(ValueType::Bool),
vec![], vec![],
); );
res.declare(
full("max"),
max,
false,
"float:max",
"Largest finite float value",
None,
Known(ValueType::Float),
vec![],
);
res.declare(
full("min"),
min,
false,
"float:min",
"Smallest finite float value",
None,
Known(ValueType::Float),
vec![],
);
res.declare(
full("nan"),
nan,
false,
"float:nan",
"Not a Number",
None,
Known(ValueType::Float),
vec![],
);
res.declare(
full("infinity"),
infinity,
false,
"float:infinity",
"Infinity",
None,
Known(ValueType::Float),
vec![],
);
res res
}; };
} }
@ -148,3 +188,31 @@ fn is_infinite(context: CommandContext) -> CrushResult<()> {
.output .output
.send(Value::Bool(context.this.float()?.is_infinite())) .send(Value::Bool(context.this.float()?.is_infinite()))
} }
fn max(context: CommandContext) -> CrushResult<()> {
context.arguments.check_len(0)?;
context
.output
.send(Value::Float(f64::MAX))
}
fn min(context: CommandContext) -> CrushResult<()> {
context.arguments.check_len(0)?;
context
.output
.send(Value::Float(f64::MIN))
}
fn nan(context: CommandContext) -> CrushResult<()> {
context.arguments.check_len(0)?;
context
.output
.send(Value::Float(f64::NAN))
}
fn infinity(context: CommandContext) -> CrushResult<()> {
context.arguments.check_len(0)?;
context
.output
.send(Value::Float(f64::INFINITY))
}

View File

@ -207,7 +207,7 @@ fn of(mut context: CommandContext) -> CrushResult<()> {
match context.arguments.len() { match context.arguments.len() {
0 => { 0 => {
let mut lst = Vec::new(); let mut lst = Vec::new();
let mut input = mandate(context.input.recv()?.stream(), "Expected a stream")?; let mut input = mandate(context.input.recv()?.stream()?, "Expected a stream")?;
if input.types().len() != 1 { if input.types().len() != 1 {
return data_error("Expected input with exactly one column"); return data_error("Expected input with exactly one column");
} }

View File

@ -48,7 +48,7 @@ fn new(mut context: CommandContext) -> CrushResult<()> {
if let Some(Value::Command(c)) = res.get("__init__") { if let Some(Value::Command(c)) = res.get("__init__") {
context.output = black_hole(); context.output = black_hole();
context.this = Some(Value::Struct(res.clone())); context.this = Some(Value::Struct(res.clone()));
c.invoke(context)?; c.eval(context)?;
} }
o.send(Value::Struct(res)) o.send(Value::Struct(res))
} }
@ -219,7 +219,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
env.declare("scope", Value::Type(ValueType::Scope))?; env.declare("scope", Value::Type(ValueType::Scope))?;
env.declare("binary", Value::Type(ValueType::Binary))?; env.declare("binary", Value::Type(ValueType::Binary))?;
env.declare("binary_stream", Value::Type(ValueType::BinaryInputStream))?; env.declare("binary_stream", Value::Type(ValueType::BinaryInputStream))?;
env.declare("field", Value::Type(ValueType::Field))?; env.declare("field", Value::Type(ValueType::Symbol))?;
env.declare("empty", Value::Type(ValueType::Empty))?; env.declare("empty", Value::Type(ValueType::Empty))?;
env.declare("float", Value::Type(ValueType::Float))?; env.declare("float", Value::Type(ValueType::Float))?;
env.declare("integer", Value::Type(ValueType::Integer))?; env.declare("integer", Value::Type(ValueType::Integer))?;

View File

@ -62,7 +62,7 @@ struct Write {}
fn write(context: CommandContext) -> CrushResult<()> { fn write(context: CommandContext) -> CrushResult<()> {
context.output.send(Value::Empty())?; context.output.send(Value::Empty())?;
let real_output = context.this.table_output_stream()?; let real_output = context.this.table_output_stream()?;
let mut stream = mandate(context.input.recv()?.stream(), "Expected a stream")?; let mut stream = mandate(context.input.recv()?.stream()?, "Expected a stream")?;
while let Ok(row) = stream.read() { while let Ok(row) = stream.read() {
real_output.send(row)?; real_output.send(row)?;

View File

@ -1,3 +1,4 @@
use std::collections::VecDeque;
/** /**
A simple wrapper around std::fs::read_dir to allow for unit testing via fakes. A simple wrapper around std::fs::read_dir to allow for unit testing via fakes.
@ -9,10 +10,9 @@ you'll need something cleverer.
*/ */
use std::path::{PathBuf}; use std::path::{PathBuf};
use crate::lang::errors::{CrushResult, to_crush_error, mandate}; use crate::lang::errors::{CrushResult, mandate, to_crush_error};
use std::fs::{ReadDir, read_dir}; use std::fs::{ReadDir, read_dir};
use ordered_map::{OrderedMap, Entry}; use ordered_map::{Entry, OrderedMap};
use std::collections::VecDeque;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Directory { pub struct Directory {

View File

@ -1,6 +1,7 @@
use crate::util::hex::from_hex; use crate::util::hex::from_hex;
use crate::lang::errors::{CrushResult, to_crush_error, mandate, data_error}; use crate::lang::errors::{to_crush_error, data_error};
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::CrushResult;
pub fn escape_without_quotes(s: &str) -> String { pub fn escape_without_quotes(s: &str) -> String {
let mut res = String::with_capacity(s.len()); let mut res = String::with_capacity(s.len());
@ -41,7 +42,6 @@ enum State {
pub fn unescape(s: &str) -> CrushResult<String> { pub fn unescape(s: &str) -> CrushResult<String> {
use State::*; use State::*;
use crate::lang::errors::CrushResult;
let mut res = "".to_string(); let mut res = "".to_string();
let mut state = Normal; let mut state = Normal;

View File

@ -1,4 +1,4 @@
touch ./foo touch ./foo
foo:chmod "a=" "o+xr" "u+w" "g-r" ./foo:chmod "a=" "o+xr" "u+w" "g-r"
find ./foo | select ^permissions find ./foo | select permissions
rm ./foo rm ./foo

View File

@ -1,22 +1,23 @@
Point := (class) $Point := (class)
Point:__init__ = { $Point:__init__ = {
|x:float y:float| |$x:$float $y:$float|
this:x = x this:x = $x
this:y = y this:y = $y
} }
Point:len = { $Point:len = {
|| ||
math:sqrt this:x*this:x + this:y*this:y math:sqrt $this:x*$this:x + $this:y*$this:y
} }
Point:__add__ = { $Point:__add__ = {
|@unnamed| |@ $unnamed|
other := unnamed[0] $other := $unnamed[0]
Point:new x=(this:x + other:x) y=(this:y + other:y) Point:new x=($this:x + $other:x) y=($this:y + $other:y)
} }
p1 := (Point:new x=0.0 y=4.0)
p2 := (Point:new x=3.0 y=0.0) $p1 := (Point:new x=0.0 y=4.0)
p3 := p1 + p2 $p2 := (Point:new x=3.0 y=0.0)
p3:len $p3 := $p1 + $p2
$p3:len

View File

@ -1,3 +1,3 @@
ggg := {|a : (dict integer integer)| echo a} $ggg := {|$a : (dict $integer $integer)| echo $a}
hhh := ((dict integer integer):new) $hhh := ((dict $integer $integer):new)
ggg a=hhh ggg a=$hhh

View File

@ -1,4 +1,4 @@
echo "Integer comparisons" "Integer comparisons"
1>2 1>2
1<2 1<2
@ -21,7 +21,7 @@ echo "Integer comparisons"
2!=1 2!=1
2==1 2==1
echo "Float comparisons" "Float comparisons"
1.0>2.0 1.0>2.0
1.0<2.0 1.0<2.0
@ -30,7 +30,7 @@ echo "Float comparisons"
1.0!=2.0 1.0!=2.0
1.0==2.0 1.0==2.0
echo "Duration comparisons" "Duration comparisons"
(duration:of seconds=1) > (duration:of seconds=2) (duration:of seconds=1) > (duration:of seconds=2)
(duration:of seconds=1) < (duration:of seconds=2) (duration:of seconds=1) < (duration:of seconds=2)
@ -39,18 +39,18 @@ echo "Duration comparisons"
(duration:of seconds=1) != (duration:of seconds=2) (duration:of seconds=1) != (duration:of seconds=2)
(duration:of seconds=1) == (duration:of seconds=2) (duration:of seconds=1) == (duration:of seconds=2)
echo "Time comparisons" "Time comparisons"
t1 := (time:now) $t1 := (time:now)
t2 := (time:now + (duration:of seconds=1)) $t2 := (time:now + (duration:of seconds=1))
t1 > t2 $t1 > $t2
t1 < t2 $t1 < $t2
t1 >= t2 $t1 >= $t2
t1 <= t2 $t1 <= $t2
t1 != t2 $t1 != $t2
t1 == t2 $t1 == $t2
echo "String comparisons" "String comparisons"
"a" > "b" "a" > "b"
"a" < "b" "a" < "b"
"a" >= "b" "a" >= "b"

View File

@ -1,4 +1,4 @@
convert 1.0 integer convert 1.0 $integer
convert 0.5 integer convert 0.5 $integer
convert 5 string convert 5 $string
typeof (convert 5 string) typeof (convert 5 $string)

View File

@ -1 +1 @@
find example_data/tree | select ^file | sort ^file find ./example_data/tree | select file | sort file

View File

@ -1,33 +1,33 @@
for i=(list:of 1 2) { for i=(list:of 1 2) {
echo i echo $i
} }
d := ((dict string integer):new) $d := ((dict $string $integer):new)
d["fooo"] = 3 $d["fooo"] = 3
for d { for $d {
echo key value echo $key $value
} }
for i=d { for i=$d {
echo i:key i:value echo $i:key $i:value
} }
table := (seq 3 | materialize) $table := (seq 3 | materialize)
for table { for $table {
echo value echo $value
} }
for i=table { for i=$table {
echo i echo $i
} }
for (seq 3) { for (seq 3) {
echo value echo $value
} }
for i=(seq 3) { for i=(seq 3) {
echo i echo $i
} }

View File

@ -1,3 +1,3 @@
seq 1000 | select num={(value*5):mod 7} | group ^num | sort ^num seq 10 | select num={($value*5):mod 7} | group num #| sort num
seq 1000 | select num={(value*5):mod 7} | group ^num c={count} | sort ^num #seq 1000 | select num={($value*5):mod 7} | group num c={count} | sort num
seq 1000 | select num={(value*5):mod 7} | group ^num c={count} s={sum} | sort ^num #seq 1000 | select num={($value*5):mod 7} | group num c={count} s={sum} | sort num

View File

@ -1,4 +1,4 @@
home:=(csv:from example_data/home.csv name=string country=string) $home:=(csv:from ./example_data/home.csv name=$string country=$string)
age:=(csv:from example_data/age.csv name=string age=integer) $age:=(csv:from ./example_data/age.csv name=$string age=$integer)
join name=home name=age | sort ^name join name=$home name=$age | sort name

View File

@ -1,8 +1,8 @@
json:from example_data/din%.json | json:from example_data/din%.json |
sort ^name sort name
json:from example_data/din%.json | json:from example_data/din%.json |
where {name =~ re"Tri.*"} where {$name =~ re"Tri.*"}
# Check serialisation and deserialisation, including field order # Check serialisation and deserialisation, including field order
seq 5|select ^value half={0.5 * value} str={convert value string} struct={data value=value} | json:to |json:from seq 5|select value half={0.5 * $value} str={convert $value $string} struct={data value=$value} | json:to |json:from

View File

@ -4,10 +4,10 @@ loop {
echo "NO" echo "NO"
} }
a := false $a := $false
loop { loop {
if a break if $a $break
a = true $a = $true
echo 2 echo 2
continue continue
echo "NO" echo "NO"

View File

@ -2,20 +2,20 @@
# of multiple commands into the same pipe. # of multiple commands into the same pipe.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
_1 := (seq 100_000 | pipe:output:write | bg) $_1 := (seq 100_000 | pipe:output:write | bg)
_2 := (seq 100_000 | pipe:output:write | bg) $_2 := (seq 100_000 | pipe:output:write | bg)
_3 := (seq 100_000 | pipe:output:write | bg) $_3 := (seq 100_000 | pipe:output:write | bg)
_4 := (seq 100_000 | pipe:output:write | bg) $_4 := (seq 100_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id := (pipe:input | sum | bg) $sum_job_id := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above 4 invocation exit # reach EOF once the above 4 invocation exit
pipe:close pipe:close
# Wait for the sum command to finish # Wait for the sum command to finish
sum_job_id | fg $sum_job_id | fg

View File

@ -2,19 +2,19 @@
# of a command between multiple commands via the same pipe. # of a command between multiple commands via the same pipe.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
input := (seq 10_000 | pipe:output:write | bg) $input := (seq 10_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id1 := (pipe:input | sum | bg) $sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg) $sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg) $sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg) $sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above 4 invocation exit # reach EOF once the above 4 invocation exit
pipe:close pipe:close
# Wait for the sum command to finish # Wait for the sum command to finish
val (sum_job_id1 | fg) + (sum_job_id2 | fg) + (sum_job_id3 | fg) + (sum_job_id4 | fg) val ($sum_job_id1 | fg) + ($sum_job_id2 | fg) + ($sum_job_id3 | fg) + ($sum_job_id4 | fg)

View File

@ -2,23 +2,23 @@
# can use a single pipe both for input and output. # can use a single pipe both for input and output.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
input1 := (seq 100_000 | pipe:output:write | bg) $input1 := (seq 100_000 | pipe:output:write | bg)
input2 := (seq 100_000 | pipe:output:write | bg) $input2 := (seq 100_000 | pipe:output:write | bg)
input3 := (seq 100_000 | pipe:output:write | bg) $input3 := (seq 100_000 | pipe:output:write | bg)
input4 := (seq 100_000 | pipe:output:write | bg) $input4 := (seq 100_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id1 := (pipe:input | sum | bg) $sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg) $sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg) $sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg) $sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above invocations exit # reach EOF once the above invocations exit
pipe:close pipe:close
# Wait for the sum commands to finish # Wait for the sum commands to finish
val (sum_job_id1 | fg) + (sum_job_id2 | fg) + (sum_job_id3 | fg) + (sum_job_id4 | fg) val ($sum_job_id1 | fg) + ($sum_job_id2 | fg) + ($sum_job_id3 | fg) + ($sum_job_id4 | fg)

View File

@ -1,15 +1,15 @@
rm ./.test_file rm ./.test_file
# Pipe output of find into a file # Pipe output of find into a file
find example_data/tree|select ^file ^type | pup:to ./.test_file find ./example_data/tree|select file type | sort file | pup:to ./.test_file
# And read it back out again # And read it back out again
pup:from ./.test_file pup:from ./.test_file
# Create a closure that close on outside variables # Create a closure that close on outside variables
a := 4 $a := 4
b := 7.5 $b := 7.5
fff := {|c:integer=1 d| echo a*b*c; for (seq 3) d } $fff := {|$c:$integer=1 $d| echo $a*$b*$c; for (seq 3) $d }
# Serialize the closure # Serialize the closure
val fff | pup:to ./.test_file val $fff | pup:to ./.test_file
# Unset the variables used by the closure # Unset the variables used by the closure
var:unset "a" "b" "fff" var:unset "a" "b" "fff"
# Deserialize the closure and check that the variables still exists. # Deserialize the closure and check that the variables still exists.

Some files were not shown because too many files have changed in this diff Show More