Misc fixes

This commit is contained in:
Axel Liljencrantz 2024-07-26 02:31:33 +02:00
parent 99f77f03c4
commit 8215da478b
21 changed files with 146 additions and 144 deletions

View File

@ -453,7 +453,7 @@ fn signature_real(metadata: TokenStream, input: TokenStream) -> SignatureResult<
#named_matchers
#named_fallback
(None, _value) => _unnamed.push_back((_value, _arg.location)),
(Some(_name), _value) => return crate::lang::errors::argument_error(format!("Unknown argument name \"{}\"", _name), _location),
(Some(_name), _value) => return crate::lang::errors::argument_error(format!("{}: Unexpected argument nameed \"{}\" with value of type {}", #command_name, _name, _value.value_type()), _location),
}
}
@ -466,9 +466,9 @@ fn signature_real(metadata: TokenStream, input: TokenStream) -> SignatureResult<
let mut output = s.to_token_stream();
output.extend(handler.into_token_stream());
if struct_name.to_string() == "AllowedValuesStringSignature" {
println!("{}", output.to_string());
}
// if struct_name.to_string() == "Sort" {
// println!("{}", output.to_string());
// }
Ok(output)
}
_ => fail!(root.span(), "Expected a struct"),

View File

@ -71,7 +71,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
"Logical operators (and and or)",
Box::new(|env| {
env.declare_condition_command(
"__and__",
"cond:__and__",
and,
"__and__ condition:(bool|command)... -> boolean",
"True if all arguments are true",
@ -85,7 +85,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
)?;
env.declare_condition_command(
"__or__",
"cond:__or__",
or,
"__or__ condition:(bool|command)... -> boolean",
"True if any argument is true",

View File

@ -1,47 +1,59 @@
use signature::signature;
use crate::lang::argument::Argument;
use crate::lang::errors::{CrushResult, mandate};
use crate::lang::command::Command;
use crate::lang::command::OutputType::Unknown;
use crate::lang::errors::{argument_error_legacy, CrushResult, mandate};
use crate::lang::state::contexts::CommandContext;
use crate::lang::value::Value;
use crate::lang::data::r#struct::Struct;
use crate::lang::pipe::pipe;
use crate::lang::ordered_string_map::OrderedStringMap;
use crate::lang::pipe::{pipe, Stream};
use crate::lang::state::argument_vector::ArgumentVector;
pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
#[signature(
control.r#for,
can_block = true,
short = "Execute a command once for each element in a stream.",
output = Unknown,
example = "for i=$(host:procs) {echo $(\"Iterating over process {}\":format $i:name)}",
example = "for i=$(seq 10) {echo $(\"Lap #{}\":format $i)}")]
pub struct For {
#[named()]
iterator: OrderedStringMap<Stream>,
body: Command,
}
fn r#for(mut context: CommandContext) -> CrushResult<()> {
let (sender, receiver) = pipe();
context.arguments.check_len(2)?;
if context.arguments.len() != 2 {
return argument_error_legacy("Expected two parameters: A stream and a command");
}
let location = context.arguments[0].location;
let body = context.arguments.command(1)?;
let iter = context.arguments.remove(0);
let name = iter.argument_type;
let mut input = mandate(iter.value.stream()?, "Expected a stream")?;
let mut cfg = For::parse(context.remove_arguments(), context.global_state.printer())?;
if cfg.iterator.len() != 1 {
return argument_error_legacy("Expected exactly one stream to iterate over");
}
let (name, mut input) = cfg.iterator.drain().next().unwrap();
while let Ok(line) = input.read() {
let env = context.scope.create_child(&context.scope, true);
let arguments = match &name {
None => Vec::from(line)
.drain(..)
.zip(input.types().iter())
.map(|(c, t)| Argument::named(&t.name, c, location))
.collect(),
Some(var_name) => {
if input.types().len() == 1 {
vec![Argument::new(
Some(var_name.clone()),
Vec::from(line).remove(0),
location,
)]
} else {
vec![Argument::new(
Some(var_name.clone()),
Value::Struct(Struct::from_vec(Vec::from(line), input.types().to_vec())),
location,
)]
}
}
let vvv = if input.types().len() == 1 {
Vec::from(line).remove(0)
} else {
Value::Struct(Struct::from_vec(Vec::from(line), input.types().to_vec()))
};
body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
let arguments =
vec![Argument::new(
Some(name.clone()),
vvv,
location,
)];
cfg.body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
if env.is_stopped() {
context.output.send(receiver.recv()?)?;
break;

View File

@ -122,22 +122,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
r#if::If::declare(env)?;
r#while::While::declare(env)?;
r#loop::Loop::declare(env)?;
env.declare_condition_command(
"for",
r#for::r#for,
"for [name=](table_input_stream|table|dict|list) body:command",
"Execute body once for every element in iterable.",
Some(
r#" Example:
for $(seq 10) {
echo $("Lap #{}":format $value)
}"#,
),
vec![],
)?;
r#for::For::declare(env)?;
cmd::Cmd::declare(env)?;
Break::declare(env)?;
timeit::TimeIt::declare(env)?;

View File

@ -313,7 +313,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
)?;
crush.create_namespace(
"byte_unit",
"Formating style for table columns containing byte size",
"Formating style for table columns containing byte size.",
Box::new(move |env| {
byte_unit::List::declare(env)?;
byte_unit::Get::declare(env)?;

View File

@ -29,7 +29,7 @@ mod yaml;
can_block = false,
short = "Return value",
output = Known(ValueType::Any),
example = "val val",
example = "val $val",
long = "This command is useful if you want to pass a command as input in\n a pipeline instead of executing it. It is different from the echo command\n in that val sends the value through the pipeline, whereas echo prints it to screen."
)]
struct Val {

View File

@ -221,7 +221,7 @@ fn max(context: CommandContext) -> CrushResult<()> {
}
}
macro_rules! mul_function {
macro_rules! prod_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;
@ -236,25 +236,25 @@ macro_rules! mul_function {
};
}
mul_function!(mul_int, i128, 1, Integer);
mul_function!(mul_float, f64, 1.0, Float);
prod_function!(prod_int, i128, 1, Integer);
prod_function!(prod_float, f64, 1.0, Float);
#[signature(
stream.mul,
stream.prod,
short = "Calculate the product for the specific column across all rows.",
example = "seq 5 10 | mul")]
pub struct Mul {
example = "seq 5 10 | prod")]
pub struct Prod {
field: Option<String>,
}
fn mul(context: CommandContext) -> CrushResult<()> {
fn prod(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream()? {
Some(input) => {
let cfg = Mul::parse(context.arguments, &context.global_state.printer())?;
let cfg = Prod::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)?),
ValueType::Integer => context.output.send(prod_int(input, column)?),
ValueType::Float => context.output.send(prod_float(input, column)?),
t => argument_error_legacy(
&format!("Can't calculate product of elements of type {}", t),
),

View File

@ -9,7 +9,7 @@ use crate::lang::command::OutputType::Known;
stream.count,
short = "Count the number of rows in the input.",
output = Known(ValueType::Integer),
example = "proc:list | count # Number of processes on the system")]
example = "host:procs | count # Number of processes on the system")]
pub struct Count {}
pub fn count(context: CommandContext) -> CrushResult<()> {

View File

@ -19,7 +19,7 @@ pub struct Drop {
}
fn drop(context: CommandContext) -> CrushResult<()> {
let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
let cfg = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
match context.input.recv()?.stream()? {
Some(mut input) => {
let t = input.types();

View File

@ -42,7 +42,7 @@ fn run(
}
pub fn each(context: CommandContext) -> CrushResult<()> {
let cfg: Each = Each::parse(context.arguments.clone(), &context.global_state.printer())?;
let cfg = Each::parse(context.arguments.clone(), &context.global_state.printer())?;
let location = context.arguments[0].location;
context.output.send(Value::Empty)?;

View File

@ -21,7 +21,7 @@ pub struct Enumerate {
}
fn enumerate(context: CommandContext) -> CrushResult<()> {
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
let cfg = Enumerate::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream()? {
Some(mut input) => {
let mut output_type = vec![

View File

@ -136,7 +136,7 @@ fn create_worker_thread(
}
pub fn group(mut context: CommandContext) -> CrushResult<()> {
let cfg: Group = Group::parse(context.remove_arguments(), &context.global_state.printer())?;
let cfg = Group::parse(context.remove_arguments(), &context.global_state.printer())?;
let mut input = mandate(
context.input.recv()?.stream()?,
"Expected input to be a stream",

View File

@ -90,7 +90,7 @@ fn get_output_type(left_type: &[ColumnType], right_type: &[ColumnType], right_ke
example = "join user=(files) name=(user:list)")]
pub struct Join {
#[named()]
#[description("Field to join")]
#[description("Fields to join")]
join: OrderedStringMap<Stream>,
}

View File

@ -40,7 +40,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
aggregation::Avg::declare(env)?;
aggregation::Min::declare(env)?;
aggregation::Max::declare(env)?;
aggregation::Mul::declare(env)?;
aggregation::Prod::declare(env)?;
aggregation::First::declare(env)?;
aggregation::Last::declare(env)?;
env.declare_command(

View File

@ -11,7 +11,8 @@ use crate::lang::command::OutputType::Passthrough;
stream.uniq,
output = Passthrough,
short = "Only output the first row if multiple rows has the same value for the specified column",
example = "host:procs | sort user | uniq user")]
long = "If no column is given, the entire rows are compared.",
example = "host:procs | uniq user")]
pub struct Uniq {
field: Option<String>,
}

View File

@ -45,15 +45,38 @@ pub enum Node {
}
impl Node {
pub fn to_command(self) -> CommandNode {
pub fn val(l: Location) -> Node {
Node::GetAttr(
Box::from(Node::GetAttr(
Box::from(Node::Identifier(TrackedString::new("global", l))),
TrackedString::new("io", l))),
TrackedString::new("val", l))
}
pub fn expression_to_command(self) -> CommandNode {
let l = self.location();
match self {
Node::Substitution(n) if n.commands.len() == 1 => {
n.commands[0].clone()
}
_ => CommandNode {
expressions: vec![self],
location: l,
_ => {
CommandNode {
expressions: vec![Node::val(self.location()), self],
location: l,
}
}
}
}
pub fn expression_to_job(self) -> JobNode {
if let Node::Substitution(s) = self {
s
} else {
let location = self.location();
let expressions = vec![Node::val(location), self];
JobNode {
commands: vec![CommandNode { expressions, location }],
location,
}
}
}
@ -188,7 +211,7 @@ pub struct CommandNode {
impl CommandNode {
pub fn compile(&self, env: &Scope) -> CrushResult<CommandInvocation> {
if let Some(c) = self.expressions[0].compile_as_command(env)? {
if let Some(c) = self.expressions[0].compile_as_special_command(env)? {
if self.expressions.len() == 1 {
Ok(c)
} else {
@ -291,7 +314,7 @@ impl Node {
Node::GetItem(a, o) => ValueDefinition::JobDefinition(
Job::new(vec![self
.compile_as_command(env)?
.compile_as_special_command(env)?
.unwrap()],
a.location().union(o.location()),
)),
@ -417,7 +440,7 @@ impl Node {
}
}
pub fn compile_as_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
pub fn compile_as_special_command(&self, env: &Scope) -> CrushResult<Option<CommandInvocation>> {
match self {
Node::Assignment(target, _style, op, value) => {
Node::compile_standalone_assignment(target, op, value, env)

View File

@ -140,6 +140,9 @@ struct SimpleCommand {
arguments: Vec<ArgumentDescription>,
}
/**
A command that can block iff any of its arguments can block, e.g. `and` or `or`.
*/
struct ConditionCommand {
call: fn(context: CommandContext) -> CrushResult<()>,
full_name: Vec<String>,

View File

@ -147,16 +147,7 @@ fn eval_other(
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(

View File

@ -34,20 +34,11 @@ Job: JobNode = {
};
Expr: Box<Node> = {
<j:JobExpr> => j.to_node()
};
JobExpr: Box<JobNode> = {
<c:AssignmentExpr> => {
let l = c.location();
Box::from(JobNode{
commands: vec![c.to_command()],
location: l,
})
},
<mut j:JobExpr> Pipe <c:AssignmentExpr> => {
j.commands.push(c.to_command());
j
AssignmentExpr,
<mut j:Expr> Pipe <c:AssignmentExpr> => {
let mut jj = j.expression_to_job();
jj.commands.push(c.expression_to_command());
Box::from(Node::Substitution(jj))
},
}
@ -70,21 +61,17 @@ ComparisonExpr: Box<Node> = {
}
CommandExpr: Box<Node> = {
<c:TermExpr> => c,
<lt:TermExpr> <t:Plus> <rt:TermExpr> => {
operator_method("__add__", t, lt, rt)
},
<lt:TermExpr> <t:Minus> <rt:TermExpr> => {
operator_method("__sub__", t, lt, rt)
},
};
TermExpr,
<lt: CommandExpr> <t:Plus> <rt:TermExpr> => operator_method("__add__", t, lt, rt),
<lt: CommandExpr> <t:Minus> <rt:TermExpr> => operator_method("__sub__", t, lt, rt),
};
TermExpr: Box<Node> = {
FactorExpr,
<l: @L> <lt:FactorExpr> <op:Star> <rt:FactorExpr> <r: @R> => {
<l: @L> <lt:TermExpr> <op:Star> <rt:FactorExpr> <r: @R> => {
operator_method("__mul__", op, lt, rt)
},
<l: @L> <lt:FactorExpr> <op:Slash> <rt:FactorExpr> <r: @R> => {
<l: @L> <lt:TermExpr> <op:Slash> <rt:FactorExpr> <r: @R> => {
operator_method("__div__", op, lt, rt)
},
}
@ -108,31 +95,35 @@ ItemExpr: Box<Node> = {
<f:Float> => Node::float(f),
<i: ItemExpr> GetItemStart <e: Assignment> GetItemEnd => Box::from(Node::GetItem(i, e)),
<i: ItemExpr> MemberOperator <l: Identifier> => Box::from(Node::GetAttr(i, l.into())),
<i: ItemExpr> ExprModeStart <mut v: OptExpressionList> SubEnd => {
<i: ItemExpr> ExprModeStart <mut ov: OptExpressionList> SubEnd => {
let l = i.location();
v.insert(0, *i);
let c = CommandNode {
expressions: v,
location: l,
let mut v = match ov {
Some(vv) => vv,
None => vec![],
};
v.insert(0, *i);
let c = CommandNode {
expressions: v,
location: l,
};
Box::from(Node::Substitution(
JobNode{
commands: vec![c],
location: l,
}
))
Box::from(Node::Substitution(
JobNode{
commands: vec![c],
location: l,
}
))
},
}
OptExpressionList: Vec<Node> = {
=> vec![],
ExpressionList,
OptExpressionList: Option<Vec<Node>> = {
=> None,
<e: ExpressionList> => Some(e),
}
ExpressionList: Vec<Node> = {
<e: Expr> => vec![*e],
<mut l: ExpressionList> Separator <e: Expr> => {
<e: AssignmentExpr> => vec![*e],
<mut l: ExpressionList> Separator <e: AssignmentExpr> => {
l.push(*e);
l
},
@ -226,15 +217,7 @@ Item: Box<Node> = {
<i: Item> MemberOperator <start: @L> <l: StringOrGlob> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(l))),
JobStart Separator? <s: Signature> <l: JobListWithoutSeparator> JobEnd => Box::from(Node::Closure(s, l)),
SubStart <j:Job> SubEnd => Box::from(Node::Substitution(j)),
<l: @L>ExprModeStart <e:Expr> SubEnd <r: @R> => Box::from(
Node::Substitution(
JobNode{
commands: vec![
CommandNode{
expressions: vec![*e],
location: Location::new(l, r)
}],
location: Location::new(l, r)})),
<l: @L>ExprModeStart <e:Expr> SubEnd <r: @R> => Box::from(Node::Substitution(e.expression_to_job())),
}
AssignmentOperator: TrackedString = {

View File

@ -131,7 +131,7 @@ pub struct CommandContext {
impl CommandContext {
/**
Return a new Command context with the same scope and state, but empty I/O and arguments.
Return an empty new Command context with the specified scope and state.
*/
pub fn new(scope: &Scope, state: &GlobalState) -> CommandContext {
CommandContext {
@ -147,6 +147,8 @@ impl CommandContext {
/**
Clear the argument vector and return the original.
This is useful when you want to parse the argument vector without consuming the whole context.
*/
pub fn remove_arguments(&mut self) -> Vec<Argument> {
let mut tmp = Vec::new(); // This does not cause a memory allocation

10
todo
View File

@ -1,4 +1,8 @@
Todo:
The automatic argument parser seems to allow stray arguemnts, e.g. `val 1 2 3`
Math commands for command mode
Rewrite all tests for new syntax
New regex literal syntax
Add saner default prompt
Add default syntax highlighting config
Add some way to extract the definition from a closure
@ -19,8 +23,6 @@ Avoid infinite loops when printing structs that reference each other
__getattr__ support?
fix dynamic loading deadlocks
tab completions for external commands
more file info: size, permissions, user, group, modified, created, type. Move to methods or add more to stat?
Rename len to size in file:stat?
Proper syntax for background jobs
More shell-like syntax for background jobs
Make IFS configurable for cmd command
Fix ^C and ^Z
Add signal handlers to fix ^C and ^Z during regular execution