Change syntax for the whole language... :-/

This commit is contained in:
Axel Liljencrantz 2022-09-02 21:03:01 +02:00
parent ecba86aaac
commit 8d96142ac9
46 changed files with 428 additions and 294 deletions

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
resemble a Christmas tree:
| Name | Description |
| --- | --- |
| Name | Description |
| --- |----------------------------------------------------------|
| `operator` | All the different Crush operators, such as `neg` and `+` |
| `numeric_literal` | Integer and floating point literals, such as `6` |
| `string_literal` | String literals, like `"Burrow"` |
| `file_literal` | File literals, like `'Cargo.toml'` |
| `label` | Variables and members, like `global` |
| `field` | Field definitions, such as `^name` |
| `numeric_literal` | Integer and floating point literals, such as `6` |
| `string_literal` | String literals, like `"Burrow"` |
| `file_literal` | File literals, like `'Cargo.toml'` |
| `label` | Variables and members, like `$global` |
| `field` | Field definitions, such as `name` |
The `term` namespace contains useful constants containing ANSI color codes.
A configuration example:

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,
aggregate and group rows of data.
crush# ll | sort ^size
crush# ll | sort size
user size modified type file
fox 31 2019-10-03 13:43:12 +0200 file .gitignore
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
...
crush# ll | where {type == "directory"}
crush# ll | where {$type == "directory"}
user size modified type file
fox 4_096 2019-11-22 21:56:30 +0100 directory target
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
# 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
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,
@ -302,7 +302,7 @@ former displays a help messages, the latter lists the content of a value.
Example:
ps | sort ^cpu
ps | sort cpu
crush# dir list
[type, truncate, remove, clone, of, __call__, __setitem__, pop, push, empty, len, peek, new, clear]
@ -369,10 +369,10 @@ re-executed until the stream is empty.
### More SQL-like data stream operations
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:
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
Idle 108
Sleeping 170

View File

@ -1,23 +1,21 @@
ls := {
|sort_by:field=^file @args|
$ls := {
|@ $args |
"List names of files non-recursively"
" 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:
ls / sort_by=^size"
find recursive=false @args | sort sort_by| select ^file
ls /"
find recursive=$false @ $args | sort file| select file
}
ll := {
|sort_by:field=^file @args|
$ll := {
|@ $args|
"List files non-recursively"
" sort_by can be one of ^user, ^size, ^modified, ^type or ^file.
"
Example:
ll .. sort_by=^modified"
find recursive=false @args | sort sort_by
ll .."
find recursive=$false @ $args | sort file
}

View File

@ -166,7 +166,7 @@ impl CommandNode {
error("Stray arguments")
}
} else {
let cmd = self.expressions[0].generate_argument(env)?;
let cmd = self.expressions[0].generate_command(env)?;
let arguments = self.expressions[1..]
.iter()
.map(|e| e.generate_argument(env))
@ -259,7 +259,7 @@ pub enum Node {
Regex(TrackedString),
Field(TrackedString),
String(TrackedString),
File(TrackedString, bool),
File(TrackedString, bool), // true if filename is quoted
Integer(TrackedString),
Float(TrackedString),
GetItem(Box<Node>, Box<Node>),
@ -311,16 +311,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> {
self.generate(env, false)
}
pub fn type_name(&self) -> &str {
match self {
Node::Assignment(_, _, _) => "assignment",
Node::Unary(_, _) => "unary operator",
Node::Glob(_) => "glob",
Node::Label(_) => "label",
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 {
Node::Assignment(target, op, value) => match op.deref() {
"=" => {
return match target.as_ref() {
Node::Label(t) => Ok(ArgumentDefinition::named(
Node::Field(t) => Ok(ArgumentDefinition::named(
t.deref(),
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"),
@ -367,7 +395,7 @@ impl Node {
)?),
s.location),
Node::GetAttr(node, label) => {
let parent = node.generate_argument(env)?;
let parent = node.generate(env, is_command)?;
match parent.unnamed_value()? {
ValueDefinition::Value(Value::Field(mut f), location) => {
f.push(label.string.clone());
@ -380,7 +408,12 @@ impl Node {
Box::new(node.generate_argument(env)?.unnamed_value()?),
label.clone(),
),
Node::Field(f) => ValueDefinition::Value(Value::Field(vec![f.string[1..].to_string()]), f.location),
Node::Field(f) =>
if is_command {
ValueDefinition::Label(f.clone())
} else {
ValueDefinition::Value(Value::Field(vec![f.string.to_string()]), f.location)
},
Node::Substitution(s) => ValueDefinition::JobDefinition(s.generate(env)?),
Node::Closure(s, c) => {
let param = s.as_ref().map(|v| {
@ -429,6 +462,7 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
],
env,
true,
),
Node::GetAttr(container, attr) => container.method_invocation(
@ -441,6 +475,7 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
],
env,
true,
),
_ => error("Invalid left side in assignment"),
@ -467,7 +502,7 @@ impl Node {
}
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() {
@ -506,24 +541,31 @@ impl Node {
name: &TrackedString,
arguments: Vec<ArgumentDefinition>,
env: &Scope,
as_command: bool,
) -> CrushResult<Option<CommandInvocation>> {
Ok(Some(CommandInvocation::new(
ValueDefinition::GetAttr(
Box::from(self.generate_argument(env)?.unnamed_value()?),
Box::from(self.generate(env, as_command)?.unnamed_value()?),
name.clone(),
),
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('?') {
Box::from(Node::Glob(s.clone()))
} else if s.string.contains('/') || s.string.contains('.') {
Box::from(Node::File(s.clone(), false))
} else {
Box::from(Node::Label(s.clone()))
Box::from(Node::Field(s.clone()))
}
}
pub fn parse_label(s: &TrackedString) -> Box<Node> {
Box::from(Node::Label(TrackedString::from(&s.string[1..], s.location)))
}
pub fn parse_file_or_wildcard(s: &TrackedString) -> Box<Node> {
if s.string.contains('%') || s.string.contains('?') {
Box::from(Node::Glob(s.clone()))
@ -584,7 +626,7 @@ fn expand_user(s: &str, location: Location) -> Box<Node> {
simple_substitution(
vec![
attr(&vec!["global", "user", "find"], location),
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location))
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)),
],
location,
),
@ -660,7 +702,8 @@ pub enum TokenType {
FactorOperator,
TermOperator,
QuotedString,
LabelOrWildcard,
StringOrWildcard,
Label,
Flag,
Field,
QuotedFile,

View File

@ -2,7 +2,7 @@ use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType};
use crate::lang::command::{BoundCommand, Command, CrushCommand, OutputType, Parameter, ArgumentDescription};
use crate::lang::command_invocation::CommandInvocation;
use crate::lang::data::dict::Dict;
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult};
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult, argument_error};
use crate::lang::execution_context::{CompileContext, CommandContext, JobContext};
use crate::lang::help::Help;
use crate::lang::job::Job;
@ -595,7 +595,11 @@ impl Closure {
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");
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() {
@ -604,7 +608,12 @@ impl Closure {
let env = context.env.clone();
env.redeclare(&name.string, default.eval_and_bind(context)?)?;
} else {
return argument_error_legacy("Missing variable!!!");
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");

View File

@ -1,11 +1,10 @@
use crate::lang::errors::{error, CrushResult, CrushErrorType};
use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::{CompileContext, JobContext};
use crate::lang::data::scope::Scope;
use crate::lang::{argument::ArgumentDefinition, argument::ArgumentVecCompiler, value::Value};
use crate::lang::command::Command;
use crate::lang::execution_context::CommandContext;
use crate::lang::value::{ValueDefinition, ValueType};
use std::ops::Deref;
use std::path::PathBuf;
use std::fmt::{Display, Formatter};
use std::thread::ThreadId;
@ -18,24 +17,6 @@ pub struct CommandInvocation {
arguments: Vec<ArgumentDefinition>,
}
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
if let Some(Value::List(path)) = env.get("cmd_path")? {
let path_vec = path.dump();
for val in path_vec {
match val {
Value::File(el) => {
let full = el.join(name);
if full.exists() {
return Ok(Some(full));
}
}
_ => {}
}
}
}
Ok(None)
}
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
for arg in local_arguments {
if arg.value.can_block(context) {
@ -107,22 +88,12 @@ pub fn eval_non_blocking(command: &ValueDefinition, arguments: &Vec<ArgumentDefi
Ok((this, value)) => {
eval_internal(this, value, arguments.clone(), context, command.location())
}
Err(err) => {
let (cmd, sub) = match command {
ValueDefinition::Label(str) => (str, None),
ValueDefinition::GetAttr(parent, sub) => match parent.deref() {
ValueDefinition::Label(str) => if context.scope.get(str.string.as_str())?.is_none() {
(str, Some(sub))
} else {
return Err(err);
},
_ => return Err(err),
},
_ => return Err(err),
};
try_external_command(cmd, sub, arguments.clone(), context)
}
Err(err) =>
if let ValueDefinition::Label(str) = command {
try_external_command(str, arguments.clone(), context)
} else {
return Err(err);
}
}
}
@ -275,13 +246,29 @@ fn eval_command(
}
}
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
if let Some(Value::List(path)) = env.get("cmd_path")? {
let path_vec = path.dump();
for val in path_vec {
match val {
Value::File(el) => {
let full = el.join(name);
if full.exists() {
return Ok(Some(full));
}
}
_ => {}
}
}
}
Ok(None)
}
fn try_external_command(
cmd: &TrackedString,
sub: Option<&TrackedString>,
mut arguments: Vec<ArgumentDefinition>,
context: JobContext,
) -> CrushResult<Option<ThreadId>> {
match resolve_external_command(&cmd.string, &context.scope)? {
None => error(format!("Unknown command name {}", cmd).as_str()),
Some(path) => {
@ -289,16 +276,6 @@ fn try_external_command(
0,
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.clone()),
subcmd.location,
)),
);
}
let call = CommandInvocation {
command: ValueDefinition::Value(
Value::Command(

View File

@ -281,6 +281,9 @@ fn complete_partial_argument(
LastArgument::Label(label) => {
complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?;
}
LastArgument::Field(label) => {
if parse_result.last_argument_name.is_none() {
if let CompletionCommand::Known(cmd) = parse_result.command {
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)?;
}
@ -320,6 +323,10 @@ pub fn complete(
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) => {
complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?;
}
@ -642,8 +649,8 @@ mod tests {
#[test]
fn complete_namespaced_argument() {
let line = "xxx abcd:bc";
let cursor = 11;
let line = "xxx $abcd:bc";
let cursor = line.len();
let s = Scope::create_root();
s.create_namespace("abcd", "bla", Box::new(|env| {
@ -653,19 +660,19 @@ mod tests {
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "xxx abcd:bcde ");
assert_eq!(&completions[0].complete(line), "xxx $abcd:bcde ");
}
#[test]
fn complete_simple_argument() {
let line = "abcd ab";
let cursor = 7;
let line = "abcd $ab";
let cursor = line.len();
let s = Scope::create_root();
s.declare("abcd", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "abcd abcd ");
assert_eq!(&completions[0].complete(line), "abcd $abcd ");
}
#[test]
@ -682,26 +689,26 @@ mod tests {
#[test]
fn check_multiple_token() {
let line = "ab cd ef";
let cursor = 5;
let line = "ab $cd ef";
let cursor = 6;
let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab cdef ef");
assert_eq!(&completions[0].complete(line), "ab $cdef ef");
}
#[test]
fn check_named_argument() {
let line = "ab foo=cd";
let cursor = 9;
let line = "ab foo=$cd";
let cursor = 10;
let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab foo=cdef ");
assert_eq!(&completions[0].complete(line), "ab foo=$cdef ");
}
#[test]
@ -718,7 +725,7 @@ mod tests {
#[test]
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 s = scope_with_function();
@ -726,7 +733,7 @@ mod tests {
s.declare("type", Value::Type(ValueType::Empty)).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
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]

View File

@ -9,6 +9,7 @@ use crate::util::glob::Glob;
use crate::lang::parser::Parser;
use crate::util::escape::unescape;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
pub enum CompletionCommand {
Unknown,
@ -28,7 +29,8 @@ impl Clone for CompletionCommand {
pub enum LastArgument {
Unknown,
Label(String),
Field(Value, String),
Field(String),
Member(Value, String),
File(String, bool),
QuotedString(String),
Switch(String),
@ -68,9 +70,9 @@ impl PartialCommandResult {
if false && cmd.arguments().len() == 1 {
Some(&cmd.arguments()[0])
} else {
let mut previous_named = HashSet::new();
let mut previous_unnamed = 0usize;
for arg in &self.previous_arguments {
match &arg.name {
Some(name) => {
@ -83,9 +85,9 @@ impl PartialCommandResult {
let mut unnamed_used = 0usize;
for arg in cmd.arguments() {
if arg.unnamed {
return Some(arg)
return Some(arg);
}
if previous_named.contains(&arg.name ) {
if previous_named.contains(&arg.name) {
continue;
} else {
unnamed_used += 1;
@ -115,12 +117,44 @@ impl PartialCommandResult {
pub enum ParseResult {
Nothing,
PartialLabel(String),
PartialField(String),
PartialMember(Value, String),
PartialFile(String, bool),
PartialQuotedString(String),
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> {
match node {
Node::Label(label) => Ok(label.string.clone()),
@ -192,12 +226,19 @@ fn find_command_in_job_list(ast: JobListNode, cursor: usize) -> CrushResult<Comm
.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 {
Node::Label(l) => scope.get(&l.string),
Node::Field(l) =>
if is_command {
scope.get(&l.string)
} else {
Ok(None)
},
Node::GetAttr(n, l) =>
match fetch_value(n, scope)? {
match fetch_value(n, scope, is_command)? {
Some(parent) => parent.field(&l.string),
None => Ok(None),
},
@ -225,7 +266,7 @@ fn fetch_value(node: &Node, scope: &Scope) -> CrushResult<Option<Value>> {
}
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)),
_ => Ok(CompletionCommand::Unknown),
}
@ -235,18 +276,18 @@ fn parse_previous_argument(arg: &Node) -> PreviousArgument {
match arg {
Node::Assignment(key, op, value) => {
match (key.as_ref(), op.as_str()) {
(Node::Label(name), "=") => {
(Node::Field(name), "=") => {
let inner = parse_previous_argument(value.as_ref());
return PreviousArgument {
name: Some(name.string.clone()),
value: inner.value
}
value: inner.value,
};
}
_ => {}
}
}
_ => {},
_ => {}
}
PreviousArgument {
name: None,
@ -278,9 +319,13 @@ pub fn parse(
Ok(ParseResult::PartialLabel(
label.prefix(cursor).string)),
Node::Field(label) =>
Ok(ParseResult::PartialField(
label.prefix(cursor).string)),
Node::GetAttr(parent, field) =>
Ok(ParseResult::PartialMember(
mandate(fetch_value(parent, scope)?, "Unknown value")?,
mandate(fetch_value(parent, scope, true)?, "Unknown value")?,
field.prefix(cursor).string)),
Node::Path(_, _) =>
@ -296,7 +341,7 @@ pub fn parse(
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 {
Ok(ParseResult::PartialArgument(
@ -358,13 +403,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) =>
Ok(ParseResult::PartialArgument(
PartialCommandResult {
command: c,
previous_arguments,
last_argument: LastArgument::Field(
mandate(fetch_value(parent, scope)?, "unknown value")?,
last_argument: LastArgument::Member(
mandate(fetch_value(parent, scope, false)?, "unknown value")?,
field.prefix(cursor).string),
last_argument_name,
})),
@ -451,8 +506,8 @@ mod tests {
#[test]
fn find_command_in_operator() {
let ast = ast("ps | where {^cpu == (max_)}").unwrap();
let cmd = find_command_in_job_list(ast, 25).unwrap();
assert_eq!(cmd.location, Location::new(21, 25))
let ast = ast("ps | where {cpu == (max_)}").unwrap();
let cmd = find_command_in_job_list(ast, 24).unwrap();
assert_eq!(cmd.location, Location::new(20, 24))
}
}

View File

@ -57,7 +57,7 @@ impl RustylineHelper {
QuotedString => highlight.get(&Value::string("string_literal")),
Regex => highlight.get(&Value::string("string_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")),
Float => highlight.get(&Value::string("numeric_literal")),
Field => highlight.get(&Value::string("field")),

View File

@ -102,15 +102,15 @@ ParameterList: Vec<ParameterNode> = {
}
Parameter: ParameterNode = {
<start: @L> <l: LabelOrWildcard> <end: @R> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), None, d),
<start: @L> <l: Label> <end: @R> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), None, d),
<start: @L> <l: LabelOrWildcard> <end: @R> Colon <t: Item> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), Some(t), d),
<start: @L> <l: Label> <end: @R> Colon <t: Item> <d: Default> =>
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: Label> <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: Label> <end: @R> => ParameterNode::Named(TrackedString::from(&l[1..], Location::new(start, end))),
}
Default: Option<Node> = {
@ -119,9 +119,11 @@ Default: Option<Node> = {
}
Item: Box<Node> = {
<start: @L> <l: LabelOrWildcard> <end: @R> =>
Node::parse_label_or_wildcard(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: StringOrWildcard> <end: @R> =>
Node::parse_string_or_wildcard(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Label> <end: @R> =>
Node::parse_label(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Regex> <end: @R> =>
Box::from(Node::Regex(TrackedString::from(&l[3..l.len()-1], Location::new(start, end)))),
@ -145,12 +147,12 @@ Item: Box<Node> = {
<start: @L> <l:Flag> <end: @R> =>
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(),
Box::from(Node::Label(TrackedString::from("true", Location::new(start, start+2)))))),
<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)),
"(" <j:Job> ")" => Box::from(Node::Substitution(j)),
}
@ -169,7 +171,8 @@ Token: TokenNode = {
@L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>),
@L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>),
@L QuotedString @R => TokenNode::new(TokenType::QuotedString, <>),
@L LabelOrWildcard @R => TokenNode::new(TokenType::LabelOrWildcard, <>),
@L Label @R => TokenNode::new(TokenType::Label, <>),
@L StringOrWildcard @R => TokenNode::new(TokenType::StringOrWildcard, <>),
@L QuotedFile @R => TokenNode::new(TokenType::QuotedFile, <>),
@L FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>),
@L Flag @R => TokenNode::new(TokenType::Flag, <>),
@ -202,7 +205,8 @@ match {
r"(\*|//)" => FactorOperator,
r"(\+|-)" => TermOperator,
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]*" => Label,
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,

View File

@ -6,7 +6,7 @@ use crate::lang::data::binary::BinaryReader;
use crate::lang::errors::to_crush_error;
use crate::lang::data::list::ListReader;
use crate::lang::printer::Printer;
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, pipe, printer_pipe};
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, printer_pipe};
use crate::lang::data::table::ColumnType;
use crate::lang::data::table::Row;
use crate::lang::data::table::Table;

View File

@ -38,7 +38,7 @@ use crate::util::regex::RegexFileMatcher;
use ordered_map::OrderedMap;
pub use value_definition::ValueDefinition;
pub use value_type::ValueType;
use std::fmt::{Display, Formatter, Debug};
use std::fmt::{Display, Formatter};
use num_format::Grouping;
use crate::util::escape::escape;
@ -75,10 +75,7 @@ impl Display for Value {
Value::String(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::Field(val) => {
f.write_str("^")?;
f.write_str(&val.join(":"))
}
Value::Field(val) => f.write_str(&val.join(":")),
Value::Glob(val) => std::fmt::Display::fmt(val, f),
Value::Regex(val, _) => {
f.write_str("re\"")?;

View File

@ -1,7 +1,7 @@
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::{argument::ArgumentDefinition, command::CrushCommand, job::Job};
use crate::lang::{command::CrushCommand, job::Job};
use crate::{
lang::errors::CrushResult, lang::pipe::pipe, lang::pipe::empty_channel,
lang::value::Value,

View File

@ -87,7 +87,7 @@ fn echo(context: CommandContext) -> CrushResult<()> {
member,
can_block = false,
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 {
#[description("the member to extract.")]

View File

@ -1,10 +1,8 @@
use lazy_static::lazy_static;
use crate::lang::command::OutputType::Known;
use crate::lang::errors::{to_crush_error, CrushResult};
use crate::lang::execution_context::CommandContext;
use crate::lang::data::scope::Scope;
use crate::lang::data::table::ColumnType;
use crate::{data::table::Row, lang::value::Value, lang::value::ValueType};
use crate::{lang::value::Value, lang::value::ValueType};
use nix::sys::signal;
use nix::unistd::Pid;
use signature::signature;
@ -23,7 +21,7 @@ mod macos {
use signature::signature;
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("ppid", ValueType::Integer),
ColumnType::new("user", ValueType::String),
@ -58,12 +56,12 @@ mod macos {
}
#[signature(
ps,
list,
can_block = true,
short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")]
pub struct Ps {}
output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "proc:list accepts no arguments.")]
pub struct List {}
use libproc::libproc::bsd_info::BSDInfo;
use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType};
@ -124,11 +122,11 @@ mod macos {
TaskAllInfo { pbsd, ptinfo }
}
fn ps(context: CommandContext) -> CrushResult<()> {
fn list(context: CommandContext) -> CrushResult<()> {
let mut base_procs = Vec::new();
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 mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0};
@ -243,7 +241,7 @@ mod linux {
use std::collections::HashMap;
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("ppid", ValueType::Integer),
ColumnType::new("status", ValueType::String),
@ -276,16 +274,16 @@ mod linux {
#[signature(
ps,
list,
can_block = true,
short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")]
pub struct Ps {}
output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "proc:list accepts no arguments.")]
pub struct List {}
fn ps(context: CommandContext) -> CrushResult<()> {
Ps::parse(context.arguments.clone(), &context.global_state.printer())?;
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?;
fn list(context: CommandContext) -> CrushResult<()> {
List::parse(context.arguments.clone(), &context.global_state.printer())?;
let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
let users = create_user_map()?;
match psutil::process::processes() {
@ -356,12 +354,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
"Process related commands",
Box::new(move |env| {
#[cfg(target_os = "linux")]
linux::Ps::declare(env)?;
linux::List::declare(env)?;
#[cfg(target_os = "macos")]
macos::Ps::declare(env)?;
macos::List::declare(env)?;
Kill::declare(env)?;
Ok(())
}))?;
root.r#use(&e);
Ok(())
}

View File

@ -11,7 +11,7 @@ drop,
can_block = true,
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.",
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,
)]
pub struct Drop {

View File

@ -1,7 +1,6 @@
use crate::lang::command::Command;
use crate::lang::errors::{error, CrushResult};
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::{data::table::Row, value::Value};
use signature::signature;
@ -15,7 +14,7 @@ can_block = true,
output = Known(Empty),
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.",
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 {
#[description("the command to run.")]
body: Command,

View File

@ -6,9 +6,16 @@ use signature::signature;
#[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")]
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<()> {
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream() {
Some(mut input) => {
let mut output_type = vec![
@ -16,12 +23,12 @@ fn enumerate(context: CommandContext) -> CrushResult<()> {
output_type.extend(input.types().to_vec());
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() {
let mut out = vec![Value::Integer(line)];
out.extend(Vec::from(row));
output.send(Row::new(out))?;
line += 1;
line += cfg.step;
}
Ok(())
}

View File

@ -23,7 +23,7 @@ use crate::lang::global_state::GlobalState;
group,
can_block = true,
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 {
#[unnamed()]

View File

@ -45,11 +45,12 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
sum_avg::Avg::declare(env)?;
sum_avg::Min::declare(env)?;
sum_avg::Max::declare(env)?;
sum_avg::Mul::declare(env)?;
env.declare_command(
"select", select::select, true,
"select copy_fields:field... [%] new_field=definition:command",
"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![],
)?;
seq::Seq::declare(env)?;

View File

@ -1,7 +1,7 @@
use crate::lang::command::Command;
use crate::lang::errors::error;
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::errors::argument_error_legacy,

View File

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

View File

@ -11,7 +11,7 @@ use std::cmp::Ordering;
#[signature(
sort,
short = "Sort input based on column",
example = "ps | sort ^cpu",
example = "ps | sort cpu",
output = Passthrough)]
pub struct Sort {
#[unnamed()]

View File

@ -42,7 +42,7 @@ sum_function!(sum_duration, Duration, Duration::seconds(0), Duration);
#[signature(
sum,
short = "Calculate the sum for the specific column across all rows.",
example = "ps | sum ^cpu")]
example = "proc:list | sum cpu")]
pub struct Sum {
field: Option<Field>,
}
@ -94,7 +94,7 @@ avg_function!(avg_duration, Duration, Duration::seconds(0), Duration, i32);
#[signature(
avg,
short = "Calculate the average for the specific column across all rows.",
example = "ps | avg ^cpu")]
example = "proc:list | avg cpu")]
pub struct Avg {
field: Option<Field>,
}
@ -159,7 +159,7 @@ aggr_function!(max_time, Time, |a, b| std::cmp::max(a, b));
#[signature(
min,
short = "Calculate the minimum for the specific column across all rows.",
example = "ps | min ^cpu")]
example = "proc:list | min cpu")]
pub struct Min {
field: Option<Field>,
}
@ -186,7 +186,7 @@ fn min(context: CommandContext) -> CrushResult<()> {
#[signature(
max,
short = "Calculate the maximum for the specific column across all rows.",
example = "ps | max ^cpu")]
example = "proc:list | max cpu")]
pub struct Max {
field: Option<Field>,
}
@ -209,3 +209,46 @@ fn max(context: CommandContext) -> CrushResult<()> {
_ => 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<Field>,
}
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

@ -12,7 +12,7 @@ use crate::lang::value::Field;
uniq,
output = Passthrough,
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 {
field: Option<Field>,
}

View File

@ -2,11 +2,11 @@ use crate::lang::command::Command;
use crate::lang::command::OutputType::Passthrough;
use crate::lang::errors::{error, CrushResult};
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::{data::table::Row, value::Value};
use signature::signature;
use crate::lang::ast::Location;
use crate::lang::pipe::pipe;
#[signature(
r#where,

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.
@ -9,10 +10,9 @@ you'll need something cleverer.
*/
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 ordered_map::{OrderedMap, Entry};
use std::collections::VecDeque;
use ordered_map::{Entry, OrderedMap};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Directory {

View File

@ -1,6 +1,7 @@
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 crate::CrushResult;
pub fn escape_without_quotes(s: &str) -> String {
let mut res = String::with_capacity(s.len());
@ -41,7 +42,6 @@ enum State {
pub fn unescape(s: &str) -> CrushResult<String> {
use State::*;
use crate::lang::errors::CrushResult;
let mut res = "".to_string();
let mut state = Normal;

View File

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

View File

@ -1,22 +1,23 @@
Point := (class)
Point:__init__ = {
|x:float y:float|
this:x = x
this:y = y
$Point := (class)
$Point:__init__ = {
|$x:$float $y:$float|
this:x = $x
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__ = {
|@unnamed|
other := unnamed[0]
Point:new x=(this:x + other:x) y=(this:y + other:y)
$Point:__add__ = {
|@ $unnamed|
$other := $unnamed[0]
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)
p3 := p1 + p2
p3:len
$p1 := (Point:new x=0.0 y=4.0)
$p2 := (Point:new x=3.0 y=0.0)
$p3 := $p1 + $p2
$p3:len

View File

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

View File

@ -41,14 +41,14 @@
"Time comparisons"
t1 := (time:now)
t2 := (time:now + (duration:of seconds=1))
t1 > t2
t1 < t2
t1 >= t2
t1 <= t2
t1 != t2
t1 == t2
$t1 := (time:now)
$t2 := (time:now + (duration:of seconds=1))
$t1 > $t2
$t1 < $t2
$t1 >= $t2
$t1 <= $t2
$t1 != $t2
$t1 == $t2
"String comparisons"
"a" > "b"

View File

@ -1,4 +1,4 @@
convert 1.0 integer
convert 0.5 integer
convert 5 string
typeof (convert 5 string)
convert 1.0 $integer
convert 0.5 $integer
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) {
echo i
echo $i
}
d := ((dict string integer):new)
d["fooo"] = 3
$d := ((dict $string $integer):new)
$d["fooo"] = 3
for d {
echo key value
for $d {
echo $key $value
}
for i=d {
echo i:key i:value
for i=$d {
echo $i:key $i:value
}
table := (seq 3 | materialize)
$table := (seq 3 | materialize)
for table {
echo value
for $table {
echo $value
}
for i=table {
echo i
for i=$table {
echo $i
}
for (seq 3) {
echo value
echo $value
}
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 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 | 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

View File

@ -1,4 +1,4 @@
home:=(csv:from example_data/home.csv name=string country=string)
age:=(csv:from example_data/age.csv name=string age=integer)
$home:=(csv:from ./example_data/home.csv name=$string country=$string)
$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 |
sort ^name
sort name
json:from example_data/din%.json |
where {name =~ re"Tri.*"}
where {$name =~ re"Tri.*"}
# 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"
}
a := false
$a := $false
loop {
if a break
a = true
if $a $break
$a = $true
echo 2
continue
echo "NO"

View File

@ -2,20 +2,20 @@
# of multiple commands into the same 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
_1 := (seq 100_000 | pipe:output:write | bg)
_2 := (seq 100_000 | pipe:output:write | bg)
_3 := (seq 100_000 | pipe:output:write | bg)
_4 := (seq 100_000 | pipe:output:write | bg)
$_1 := (seq 100_000 | pipe:output:write | bg)
$_2 := (seq 100_000 | pipe:output:write | bg)
$_3 := (seq 100_000 | pipe:output:write | bg)
$_4 := (seq 100_000 | pipe:output:write | bg)
# 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
# reach EOF once the above 4 invocation exit
pipe:close
# 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.
# 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
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
sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg)
$sum_job_id1 := (pipe:input | sum | bg)
$sum_job_id2 := (pipe:input | sum | bg)
$sum_job_id3 := (pipe:input | sum | bg)
$sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually
# reach EOF once the above 4 invocation exit
pipe:close
# 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.
# 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
input1 := (seq 100_000 | pipe:output:write | bg)
input2 := (seq 100_000 | pipe:output:write | bg)
input3 := (seq 100_000 | pipe:output:write | bg)
input4 := (seq 100_000 | pipe:output:write | bg)
$input1 := (seq 100_000 | pipe:output:write | bg)
$input2 := (seq 100_000 | pipe:output:write | bg)
$input3 := (seq 100_000 | pipe:output:write | bg)
$input4 := (seq 100_000 | pipe:output:write | bg)
# Attach the output end to the sum command and
sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg)
$sum_job_id1 := (pipe:input | sum | bg)
$sum_job_id2 := (pipe:input | sum | bg)
$sum_job_id3 := (pipe:input | sum | bg)
$sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually
# reach EOF once the above invocations exit
pipe:close
# 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
# Pipe output of find into a file
find example_data/tree|select ^file ^type | sort ^file | pup:to ./.test_file
find ./example_data/tree|select file type | sort file | pup:to ./.test_file
# And read it back out again
pup:from ./.test_file
# Create a closure that close on outside variables
a := 4
b := 7.5
fff := {|c:integer=1 d| echo a*b*c; for (seq 3) d }
$a := 4
$b := 7.5
$fff := {|$c:$integer=1 $d| echo $a*$b*$c; for (seq 3) $d }
# Serialize the closure
val fff | pup:to ./.test_file
val $fff | pup:to ./.test_file
# Unset the variables used by the closure
var:unset "a" "b" "fff"
# Deserialize the closure and check that the variables still exists.

View File

@ -1,8 +1,8 @@
tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07")
tm = tm - (duration:of hours=1 seconds=66)
tm:format "%s"
$tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07")
$tm = tm - (duration:of hours=1 seconds=66)
$tm:format "%s"
t1 := (time:now)
$t1 := (time:now)
sleep (duration:of milliseconds=50)
t2 := (time:now)
(t2 - t1) >= (duration:of milliseconds=50)
$t2 := (time:now)
($t2 - $t1) >= (duration:of milliseconds=50)

View File

@ -2,12 +2,12 @@ echo 1
# Check that newline is ignored immediately after pipe
(list:of 1 2 3) |
where {value == 2}
where {$value == 2}
# Check that newline is ignored if preceded by backslash
(list:of 1 2 3)\
| where {value == 3}
| where {$value == 3}
# Check that newline is ignored if preceded by backslash in job
(list:of 1\
2 3 4) | where {value == 4}
2 3 4) | where {$value == 4}

View File

@ -1,7 +1,7 @@
age := (lines:from example_data/age.csv|materialize)
home := (lines:from example_data/home.csv|materialize)
$age := (lines:from ./example_data/age.csv|materialize)
$home := (lines:from ./example_data/home.csv|materialize)
zip (lines:from example_data/age.csv) (lines:from example_data/home.csv)
zip age home | head 1
zip (lines:from example_data/age.csv) home | head 1
zip age (lines:from example_data/home.csv) | head 1
zip (lines:from ./example_data/age.csv) (lines:from ./example_data/home.csv)
zip $age $home | head 1
zip (lines:from ./example_data/age.csv) $home | head 1
zip $age (lines:from ./example_data/home.csv) | head 1