Fix various context bugs

This commit is contained in:
Axel Liljencrantz 2022-08-31 18:06:19 +02:00
parent 21c5395679
commit 11e09d09c5
16 changed files with 268 additions and 239 deletions

View File

@ -122,25 +122,25 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
let mut res = Vec::new();
for a in self {
if a.argument_type.is_this() {
this = Some(a.value.compile_bound(context)?);
this = Some(a.value.eval_and_bind(context)?);
} else {
match &a.argument_type {
ArgumentType::Some(name) => {
res.push(Argument::named(
&name.string,
a.value.compile_bound(context)?,
a.value.eval_and_bind(context)?,
a.location,
))
}
ArgumentType::None => {
res.push(Argument::unnamed(
a.value.compile_bound(context)?,
a.value.eval_and_bind(context)?,
a.location,
))
}
ArgumentType::ArgumentList => match a.value.compile_bound(context)? {
ArgumentType::ArgumentList => match a.value.eval_and_bind(context)? {
Value::List(l) => {
let mut copy = l.dump();
for v in copy.drain(..) {
@ -153,7 +153,7 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
_ => 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) => {
let mut copy = d.elements();
for (key, value) in copy.drain(..) {

View File

@ -16,6 +16,8 @@ use crate::lang::pipe::{black_hole, empty_channel};
use crate::lang::value::{Value, ValueDefinition, ValueType};
use std::collections::HashMap;
use std::fmt::Display;
use std::io;
use std::io::Write;
use crate::lang::ast::{TrackedString, Location};
pub struct Closure {
@ -201,7 +203,6 @@ impl<'a> ClosureSerializer<'a> {
model::parameter::Parameter::Named(n.serialize(self.elements, self.state)? as u64),
Parameter::Parameter(n, t, d) => {
model::parameter::Parameter::Normal(model::NormalParameter {
name: n.serialize(self.elements, self.state)? as u64,
r#type: Some(self.value_definition(t)?),
@ -531,13 +532,14 @@ impl Help for Closure {
}
}
/** Extracts the help message from a closure definition */
fn extract_help(jobs: &mut Vec<Job>) -> String {
if jobs.is_empty() {
return "".to_string();
}
let j = &jobs[0];
match j.as_string() {
match j.extract_help_message() {
Some(help) => {
if jobs.len() > 1 {
jobs.remove(0);
@ -570,80 +572,89 @@ 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_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.eval_and_bind(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");
}
Ok(())
}
fn push_arguments_to_env(
signature: &Option<Vec<Parameter>>,
mut arguments: Vec<Argument>,
context: &mut CompileContext,
) -> CrushResult<()> {
if let Some(signature) = signature {
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.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");
}
Self::push_arguments_to_env_with_signature(signature, arguments, context)
} else {
for arg in arguments.drain(..) {
match arg.argument_type {
@ -655,8 +666,8 @@ impl Closure {
}
}
}
Ok(())
}
Ok(())
}
pub fn deserialize(

View File

@ -4,11 +4,12 @@ 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;
use crate::lang::value::{ValueDefinition, ValueType};
use std::ops::Deref;
use std::path::PathBuf;
use std::fmt::{Display, Formatter};
use std::thread::ThreadId;
use crate::data::r#struct::Struct;
use crate::lang::ast::Location;
#[derive(Clone)]
@ -38,7 +39,6 @@ fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathB
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;
}
}
@ -50,7 +50,8 @@ impl CommandInvocation {
CommandInvocation { command, arguments }
}
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.arguments.len() != 0 {
return None;
}
@ -69,34 +70,6 @@ impl CommandInvocation {
&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>,
@ -112,7 +85,7 @@ impl CommandInvocation {
}
pub fn can_block(&self, arg: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
let cmd = self.command.compile_internal(context, false);
let cmd = self.command.eval(context, false);
match cmd {
Ok((_, Value::Command(command))) => {
command.can_block(arg, context) || arg_can_block(&self.arguments, context)
@ -121,12 +94,10 @@ impl CommandInvocation {
}
}
pub fn invoke(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
match self
.command
.compile_internal(&mut CompileContext::from(&context), false)
pub fn eval(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
match self.command.eval(&mut CompileContext::from(&context), false)
{
Ok((this, value)) => invoke_value(this, value, self.arguments.clone(), context, self.command.location()),
Ok((this, value)) => eval_internal(this, value, self.arguments.clone(), context, self.command.location()),
Err(err) => {
if err.is(CrushErrorType::BlockError) {
let cmd = self.command.clone();
@ -136,8 +107,8 @@ impl CommandInvocation {
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(
match cmd.clone().eval(&mut CompileContext::from(&context), true) {
Ok((this, value)) => context.global_state.printer().handle_error(eval_internal(
this,
value,
arguments,
@ -162,7 +133,7 @@ impl CommandInvocation {
}
}
fn invoke_value(
fn eval_internal(
this: Option<Value>,
value: Value,
local_arguments: Vec<ArgumentDefinition>,
@ -171,92 +142,9 @@ fn invoke_value(
) -> 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(),
)
}
}
},
Value::File(f) => eval_file(f, local_arguments, context, location),
Value::Type(t) => eval_type(t, local_arguments, context, location),
Value::Struct(s) => invoke_struct(s, local_arguments, context, location),
_ => {
if local_arguments.len() == 0 {
invoke_command(
@ -272,27 +160,135 @@ fn invoke_value(
}
}
fn eval_file(
file: PathBuf,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
if local_arguments.len() == 0 {
let meta = file.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(file),
location,
))],
context,
)
} else {
invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::File(file),
location,
))],
context,
)
}
} else {
error(
format!(
"Not a command {}",
file.to_str().unwrap_or("<invalid filename>")
)
.as_str(),
)
}
}
fn eval_type(
value_type: ValueType,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match value_type.fields().get("__call__") {
None => invoke_command(
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
None,
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
Value::Type(value_type),
location,
))],
context,
),
Some(call) => invoke_command(
call.as_ref().copy(),
Some(Value::Type(value_type)),
local_arguments,
context,
),
}
}
fn invoke_struct(
struct_value: Struct,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
location: Location,
) -> CrushResult<Option<ThreadId>> {
match struct_value.get("__call__") {
Some(Value::Command(call)) => {
invoke_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 {
invoke_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 invoke_command(
action: Command,
command: Command,
this: Option<Value>,
local_arguments: Vec<ArgumentDefinition>,
context: JobContext,
) -> CrushResult<Option<ThreadId>> {
if !action.can_block(&local_arguments, &mut CompileContext::from(&context))
if !command.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));
context.global_state.printer().handle_error(command.invoke(new_context));
Ok(None)
} else {
let t = context.global_state.threads().clone();
let name = action.name().to_string();
let name = command.name().to_string();
Ok(Some(t.spawn(
&name,
move || {
let res = CommandInvocation::execution_context(local_arguments, this, context.clone())?;
action.invoke(res)
command.invoke(res)
},
)?))
}
@ -341,7 +337,7 @@ fn try_external_command(
),
arguments,
};
call.invoke(context)
call.eval(context)
}
}
}

View File

@ -332,7 +332,7 @@ impl CommandContext {
arguments,
this,
global_state: self.global_state,
handle: self.handle.clone(),
handle: self.handle,
}
}
@ -347,7 +347,22 @@ impl CommandContext {
arguments: self.arguments,
this: self.this,
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

@ -46,7 +46,7 @@ pub fn execute_prompt(
ValueDefinition::Value(Value::Command(prompt), Location::new(0, 0)),
vec![]);
let (snd, recv) = pipe();
cmd.invoke(JobContext::new(
cmd.eval(JobContext::new(
empty_channel(),
snd,
env.clone(),

View File

@ -39,7 +39,7 @@ impl Job {
let last_job_idx = self.commands.len() - 1;
for call_def in &self.commands[..last_job_idx] {
let (output, next_input) = pipe();
call_def.invoke(context.with_io(input, output))?;
call_def.eval(context.with_io(input, output))?;
input = next_input;
if context.scope.is_stopped() {
@ -52,15 +52,16 @@ impl Job {
}
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 {
return None;
}
self.commands[0].as_string()
self.commands[0].extract_help_message()
}
}

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) {
let (output, input) = bounded(128);
(

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};
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, pipe, printer_pipe};
use crate::lang::data::table::ColumnType;
use crate::lang::data::table::Row;
use crate::lang::data::table::Table;
@ -44,7 +44,7 @@ pub fn create_pretty_printer(
global_state: &GlobalState,
) -> ValueSender {
let global_state = global_state.clone();
let (o, i) = pipe();
let (o, i) = printer_pipe();
let printer_clone = printer.clone();
printer_clone.handle_error(to_crush_error(
thread::Builder::new()

View File

@ -49,20 +49,12 @@ impl ValueDefinition {
}
}
pub fn compile_unbound(
&self,
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)?;
pub fn eval_and_bind(&self, context: &mut CompileContext) -> CrushResult<Value> {
let (t, v) = self.eval(context, true)?;
Ok(t.map(|tt| v.clone().bind(tt)).unwrap_or(v))
}
pub fn compile_internal(
pub fn eval(
&self,
context: &mut CompileContext,
can_block: bool,
@ -97,7 +89,7 @@ impl ValueDefinition {
),
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, can_block)?;
parent = if let Value::Command(parent_cmd) = &parent {
if !can_block {
return block_error();
@ -125,7 +117,7 @@ impl ValueDefinition {
}
ValueDefinition::Path(parent_def, entry) => {
let parent = parent_def.compile_internal(context, can_block)?.1;
let parent = parent_def.eval(context, can_block)?.1;
let val = mandate(
parent.path(&entry.string),
&format!("Missing path entry {} in {}", entry, parent_def),

View File

@ -38,7 +38,7 @@ pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
}
}
};
body.invoke(context.empty())?;
body.invoke(context.empty().with_scope(env.clone()).with_args(arguments, None))?;
if env.is_stopped() {
break;
}

View File

@ -19,7 +19,7 @@ fn r#loop(context: CommandContext) -> CrushResult<()> {
context.output.initialize(vec![])?;
loop {
let env = context.scope.create_child(&context.scope, true);
cfg.body.invoke(context.empty())?;
cfg.body.invoke(context.empty().with_scope(env.clone()))?;
if env.is_stopped() {
break;
}

View File

@ -27,7 +27,7 @@ fn r#while(mut context: CommandContext) -> CrushResult<()> {
let (sender, receiver) = pipe();
let cond_env = context.scope.create_child(&context.scope, true);
cfg.condition.invoke(context.empty().with_output(sender))?;
cfg.condition.invoke(context.empty().with_scope(cond_env.clone()).with_output(sender))?;
if cond_env.is_stopped() {
break;
}

View File

@ -214,8 +214,8 @@ mod macos {
Value::Integer(i128::from(curr_task.ptinfo.pti_virtual_size)),
Value::Duration(Duration::nanoseconds(
i64::try_from(curr_task.ptinfo.pti_total_user + curr_task.ptinfo.pti_total_system)? *
i64::try_from(info.numer)? /
i64::try_from(info.denom)?)),
i64::from(info.numer) /
i64::from(info.denom))),
Value::String(name)
]));
}

View File

@ -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)
})
.collect();
closure.invoke(context.empty().with_output(sender))?;
closure.invoke(context.empty().with_args(arguments, None).with_output(sender))?;
receiver.recv()?
}
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))
.collect();
let (sender, receiver) = pipe();
closure.invoke(context.empty().with_output(sender))?;
closure.invoke(context.empty().with_args(arguments, None).with_output(sender))?;
receiver.recv()?
}
Source::Argument(idx) => row.cells()[*idx].clone(),

View File

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

View File

@ -1,6 +1,6 @@
rm ./.test_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
pup:from ./.test_file