native and user defined commands.

main
bog 2024-03-03 17:59:26 +01:00
parent 76e5db35b4
commit ec957f0b37
14 changed files with 746 additions and 28 deletions

View File

@ -4,9 +4,15 @@ INSTR ::=
| ASSIGN semicolon
| ASSIGN_IF semicolon
| ASSIGN_ADD semicolon
| CMD_CALL semicolon
| CMD_DEF
ASSIGN ::= ident assign EXPR
ASSIGN_IF ::= ident assign_if EXPR
ASSIGN_ADD ::= ident assign_add EXPR
CMD_CALL ::= ident EXPR*
CMD_DEF ::= command ident colon EXPR* rarrow EXPR* BLOCK
BLOCK ::= obrace INSTR* cbrace
EXPR ::= LITERAL
LITERAL ::=
| BUILTIN

View File

@ -21,7 +21,9 @@ impl Lexer {
text: String::from(""),
separators: vec![
';', '[', ']',
'=', '?', '+'
'=', '?', '+', ':',
'(', ')', '{', '}',
]
}
}
@ -87,6 +89,9 @@ impl Lexer {
}
if !c.is_ascii_alphabetic() && c != '_'
&& c != '%'
&& c != '@'
&& c != '$'
&& !c.is_digit(10) {
break;
}
@ -334,6 +339,11 @@ impl Iterator for Lexer {
self.skip_spaces();
self.skip_comments();
if let Some(token) = self.scan_text("->") {
self.cursor = token.position;
return Some(Contextual(Node::RArrow, Context(self.line)));
}
if let Some(token) = self.scan_text("+=") {
self.cursor = token.position;
return Some(Contextual(Node::AssignAdd, Context(self.line)));
@ -349,6 +359,26 @@ impl Iterator for Lexer {
return Some(Contextual(Node::Assign, Context(self.line)));
}
if let Some(token) = self.scan_text("(") {
self.cursor = token.position;
return Some(Contextual(Node::OPar, Context(self.line)))
}
if let Some(token) = self.scan_text(")") {
self.cursor = token.position;
return Some(Contextual(Node::CPar, Context(self.line)))
}
if let Some(token) = self.scan_text("{") {
self.cursor = token.position;
return Some(Contextual(Node::OBrace, Context(self.line)))
}
if let Some(token) = self.scan_text("}") {
self.cursor = token.position;
return Some(Contextual(Node::CBrace, Context(self.line)))
}
if let Some(token) = self.scan_text("[") {
self.cursor = token.position;
return Some(Contextual(Node::OSquare, Context(self.line)))
@ -359,6 +389,11 @@ impl Iterator for Lexer {
return Some(Contextual(Node::CSquare, Context(self.line)))
}
if let Some(token) = self.scan_keyword("command") {
self.cursor = token.position;
return Some(Contextual(Node::Command, Context(self.line)))
}
if let Some(token) = self.scan_keyword("true") {
self.cursor = token.position;
return Some(Contextual(Node::Bool(token.value == "true"),
@ -376,6 +411,11 @@ impl Iterator for Lexer {
return Some(Contextual(Node::Semicolon, Context(self.line)))
}
if let Some(token) = self.scan_text(":") {
self.cursor = token.position;
return Some(Contextual(Node::Colon, Context(self.line)))
}
if let Some(token) = self.scan_string() {
self.cursor = token.position;
return Some(Contextual(Node::String(token.value.clone()),
@ -441,6 +481,17 @@ mod test {
Ok(result)
}
#[test]
fn test_cmd_call() {
let mut lex = Lexer::new();
let res = lexer_run(&mut lex, " (true) ").unwrap();
assert_eq!(3, res.len());
assert_eq!(Node::OPar, res.get(0).unwrap().0);
assert_eq!(Node::Bool(true), res.get(1).unwrap().0);
assert_eq!(Node::CPar, res.get(2).unwrap().0);
}
#[test]
fn test_booleans() {
let mut lex = Lexer::new();
@ -561,13 +612,13 @@ mod test {
assert_eq!(4, res.len());
assert_eq!(Node::Symbol(String::from("abc")),
assert_eq!(Node::Symbol(String::from("abc")),
res.get(0).unwrap().0);
assert_eq!(Node::Symbol(String::from("+-*/")),
assert_eq!(Node::Symbol(String::from("+-*/")),
res.get(1).unwrap().0);
assert_eq!(Node::Symbol(String::from("a")),
assert_eq!(Node::Symbol(String::from("a")),
res.get(2).unwrap().0);
assert_eq!(Node::Semicolon, res.get(3).unwrap().0);
@ -590,4 +641,45 @@ mod test {
Ok(())
}
#[test]
fn test_command() -> Result<(), error::AstError> {
let mut lex = Lexer::new();
let res = lexer_run(&mut lex,
" command: ->{ } %4 % $4 $ @47 @")?;
assert_eq!(11, res.len());
assert_eq!(Node::Command, res.get(0).unwrap().0);
assert_eq!(Node::Colon, res.get(1).unwrap().0);
assert_eq!(Node::RArrow, res.get(2).unwrap().0);
assert_eq!(Node::OBrace, res.get(3).unwrap().0);
assert_eq!(Node::CBrace, res.get(4).unwrap().0);
assert_eq!(Node::Ident(String::from("%4")),
res.get(5).unwrap().0);
assert_eq!(Node::Ident(String::from("%")),
res.get(6).unwrap().0);
assert_eq!(Node::Ident(String::from("$4")),
res.get(7).unwrap().0);
assert_eq!(Node::Ident(String::from("$")),
res.get(8).unwrap().0);
assert_eq!(Node::Ident(String::from("@47")),
res.get(9).unwrap().0);
assert_eq!(Node::Ident(String::from("@")),
res.get(10).unwrap().0);
Ok(())
}
}

View File

@ -1,6 +1,7 @@
use crate::context::Contextual;
#[derive(Debug)]
#[derive(Clone)]
#[derive(PartialEq)]
pub enum Node {
Module(String, Vec<Contextual<Node>>),
@ -9,8 +10,8 @@ pub enum Node {
Float(f64),
String(String),
Symbol(String),
OSquare,
CSquare,
OSquare, CSquare,
OPar, CPar,
Array(Vec<Contextual<Node>>),
Assign,
AssignIf,
@ -19,7 +20,18 @@ pub enum Node {
AssignIfInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
AssignAddInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
Ident(String),
Semicolon
Semicolon, Colon,
RArrow,
Command,
CmdCall(Contextual<Box<Node>>, Vec<Contextual<Node>>),
OBrace, CBrace,
CmdDef(
Contextual<Box<Node>>, // target
Vec<Contextual<Node>>, // requires
Vec<Contextual<Node>>, // provides
Contextual<Box<Node>> // body
),
Block(Vec<Contextual<Node>>),
}

View File

@ -51,6 +51,10 @@ impl Parser {
-> ParseResult {
match self.lexer.next() {
Some(Contextual(Node::Semicolon, _)) => result,
Some(Contextual(node, Context(line)))
=> Err(error::AstError {
msg: format!("unexpected node {:?}", node),
line}),
_ => Err(error::AstError {
msg: String::from("missing semicolon"),
line: self.lexer.line()})
@ -58,6 +62,24 @@ impl Parser {
}
fn parse_instr(&mut self) -> ParseResult {
match self.lexer.peek(0) {
Some(Contextual(Node::Command, _)) => { return self.parse_cmd_def(); },
Some(Contextual(Node::Ident(_), _)) => {
match self.lexer.peek(1) {
Some(Contextual(Node::Semicolon, _))
| Some(Contextual(Node::Assign, _))
| Some(Contextual(Node::AssignIf, _))
| Some(Contextual(Node::AssignAdd, _)) => {},
_ => {
let cmd_call = self.parse_cmd_call();
return self.ensure_semicolon(cmd_call);
}
}
},
_ => {}
}
match self.lexer.peek(1) {
Some(Contextual(Node::Assign, _)) => {
let assign = self.parse_assign();
@ -74,6 +96,7 @@ impl Parser {
self.ensure_semicolon(assign)
},
_ =>
match self.parse_expr() {
Ok(Some(expr)) =>
@ -144,6 +167,135 @@ impl Parser {
}
}
fn parse_cmd_call(&mut self) -> ParseResult {
match self.lexer.next() {
Some(Contextual(Node::Ident(id), ctx))
=> {
let mut args: Vec<Contextual<Node>> = vec![];
loop {
match self.lexer.peek(0) {
Some(Contextual(Node::CPar, _))
| Some(Contextual(Node::Semicolon, _))
=> break,
Some(node) => {
args.push(node);
self.lexer.next();
}
None => {
return Err(error::AstError {
msg: String::from("unexpected end of command"),
line: self.lexer.line()
});
}
}
}
Ok(Some(Contextual(Node::CmdCall(
Contextual(Box::new(Node::Ident(id)), ctx),
args
), ctx)))
},
Some(Contextual(node, Context(line))) => {
Err(error::AstError {
msg: format!("unexpected node <{:?}>", node),
line
})
},
None => {
Err(error::AstError {
msg: format!("unexpected end"),
line: self.lexer.line()
})
},
}
}
fn parse_cmd_def(&mut self) -> ParseResult {
let _command = self.lexer.next();
let ident : Node;
match self.lexer.next() {
Some(Contextual(node, _)) => { ident = node; },
_ => {
return Err(error::AstError {
msg: String::from("invalid command target"),
line: self.lexer.line()
})
}
}
let _colon = self.lexer.next();
let mut requires: Vec<Contextual<Node>> = vec![];
let mut provides: Vec<Contextual<Node>> = vec![];
let mut block: Vec<Contextual<Node>> = vec![];
loop {
if let Some(Contextual(Node::RArrow, _)) = self.lexer.peek(0) {
break;
}
match self.parse_expr() {
Ok(Some(node)) => { requires.push(node); },
Ok(None) => {
return Err(error::AstError {
msg: String::from("invalid command requirement"),
line: self.lexer.line()
});
},
Err(err) => { return Err(err); }
}
}
let _rarrow = self.lexer.next();
loop {
if let Some(Contextual(Node::OBrace, _)) = self.lexer.peek(0) {
break;
}
match self.parse_expr() {
Ok(Some(node)) => { provides.push(node); },
Ok(None) => {
return Err(error::AstError {
msg: String::from("invalid command provide value"),
line: self.lexer.line()
});
},
Err(err) => { return Err(err); }
}
}
let _obrace = self.lexer.next();
loop {
if let Some(Contextual(Node::CBrace, _)) = self.lexer.peek(0) {
break;
}
match self.parse_instr() {
Ok(Some(node)) => { block.push(node); },
Ok(None) => {
return Err(error::AstError {
msg: String::from("invalid command subcommand"),
line: self.lexer.line()
});
},
Err(err) => { return Err(err); }
}
}
let _cbrace = self.lexer.next();
let ctx = Context(self.lexer.line());
Ok(Some(Contextual(Node::CmdDef(
Contextual(Box::new(ident), ctx),
requires,
provides,
Contextual(Box::new(Node::Block(block)), ctx)
),ctx)))
}
fn parse_expr(&mut self) -> ParseResult {
self.parse_literal()
}
@ -155,7 +307,16 @@ impl Parser {
self.lexer.next();
Ok(Some(Contextual(Node::Ident(id), ctx)))
},
Some(Contextual(Node::OSquare, _)) => self.parse_array(),
Some(Contextual(Node::OPar, _)) => {
let _opar = self.lexer.next();
let res = self.parse_cmd_call();
let _cpar = self.lexer.next();
res
},
Some(_) => self.parse_builtin(),
None => { self.lexer.next(); Ok(None) }
}
@ -232,6 +393,14 @@ mod test {
}
}
fn ctx(node: Node) -> Contextual<Node> {
Contextual(node, Context(1))
}
fn ctx_box(node: Node) -> Contextual<Box<Node>> {
Contextual(Box::new(node), Context(1))
}
#[test]
fn premature_end() -> TestResult {
let mut lex = lexer::Lexer::new();
@ -287,8 +456,9 @@ mod test {
fn symbols() -> TestResult {
test_parser(
Node::Module("mod".to_owned(), vec![
Contextual(Node::Symbol(String::from("bonjour++")),
Context(0))
Contextual(Node::Symbol(
String::from("bonjour++")
), Context(0))
]),
" 'bonjour++ ; "
)?;
@ -300,7 +470,7 @@ mod test {
fn strings() -> TestResult {
test_parser(
Node::Module("mod".to_owned(), vec![
Contextual(Node::String(String::from("pizza! ")),
Contextual(Node::String(String::from("pizza! ")),
Context(0))
]),
" \"pizza! \"; "
@ -373,4 +543,73 @@ mod test {
Ok(())
}
#[test]
fn cmd_call_instr() -> TestResult {
test_parser(
Node::Module("mod".to_owned(), vec![
ctx(Node::CmdCall(
ctx_box(Node::Ident(String::from("hello"))),
vec![]
))
]),
" (hello) ; "
)?;
test_parser(
Node::Module("mod".to_owned(), vec![
ctx(Node::CmdCall(
ctx_box(Node::Ident(String::from("hello"))),
vec![
ctx(Node::Int(4)),
ctx(Node::Symbol(String::from("b"))),
ctx(Node::Symbol(String::from("c"))),
]
))
]),
" hello 4 'b 'c; "
)?;
Ok(())
}
#[test]
fn cmd_def() -> TestResult {
test_parser(
Node::Module("mod".to_owned(), vec![
ctx(Node::CmdDef(
ctx_box(Node::Ident(String::from("hello"))),
vec![
],
vec![
],
ctx_box(Node::Block(vec![
]))
))
]),
" command hello: -> { } "
)?;
test_parser(
Node::Module("mod".to_owned(), vec![
ctx(Node::CmdDef(
ctx_box(Node::Ident(String::from("hello"))),
vec![
ctx(Node::Ident(String::from("a"))),
ctx(Node::Ident(String::from("b"))),
ctx(Node::Ident(String::from("c"))),
],
vec![
ctx(Node::Ident(String::from("d"))),
ctx(Node::Ident(String::from("e"))),
],
ctx_box(Node::Block(vec![
]))
))
]),
" command hello: a b c -> d e { } "
)?;
Ok(())
}
}

View File

@ -5,10 +5,11 @@
pub struct Context(pub i32);
#[derive(Debug)]
#[derive(Clone)]
pub struct Contextual<T>(pub T, pub Context);
impl<T: PartialEq> PartialEq<Contextual<T>> for Contextual<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
}

40
src/eval/cmd/builder.rs Normal file
View File

@ -0,0 +1,40 @@
use crate::eval::{
cmd::Command,
value::Value
};
use crate::ast::node::Node;
pub struct CmdBuilder {
command: Command
}
impl CmdBuilder {
pub fn new() -> Self {
Self {
command: Command {
requires: vec![],
provides: vec![],
body: vec![]
}
}
}
pub fn require(&mut self, dep: Value) -> &mut Self {
self.command.requires.push(dep);
self
}
pub fn provide(&mut self, dep: Value) -> &mut Self {
self.command.provides.push(dep);
self
}
pub fn instr(&mut self, cmd: Node) -> &mut Self {
self.command.body.push(cmd);
self
}
pub fn build(self) -> Command {
self.command
}
}

72
src/eval/cmd/mod.rs Normal file
View File

@ -0,0 +1,72 @@
pub mod runner;
pub mod builder;
use crate::eval::{
value::Value,
error::EvalError,
evaluator::Evaluator
};
use crate::context::{Contextual, Context};
use crate::ast::node::Node;
pub type CmdCallResult = Result<(), EvalError>;
pub trait Cmd {
fn requires(&self) -> Vec<Value> {
vec![]
}
fn provides(&self) -> Vec<Value> {
vec![]
}
fn call(&mut self, args: Vec<Value>) -> CmdCallResult;
}
pub struct Command {
requires: Vec<Value>,
provides: Vec<Value>,
body: Vec<Node>
}
impl Cmd for Command {
fn requires(&self) -> Vec<Value> {
self.requires.clone().into_iter().collect()
}
fn provides(&self) -> Vec<Value> {
self.provides.clone().into_iter().collect()
}
fn call(&mut self, args: Vec<Value>) -> CmdCallResult {
let mut eval = Evaluator::new();
eval.symbols(|table| {
// Arguments
table.set("%", Value::Array(args.clone()));
for i in 0..args.len() {
table.set(&format!("%{}", i),
args.get(i).unwrap().clone());
}
// Requirements
table.set("$", Value::Array(self.requires()));
for (i, req) in self.requires().iter().enumerate() {
table.set(&format!("${}", i), req.clone());
}
// Provided values
table.set("@", Value::Array(self.provides()));
for (i, prov) in self.provides().iter().enumerate() {
table.set(&format!("@{}", i), prov.clone());
}
});
for instr in self.body.iter() {
let i = instr.clone();
let c = Contextual(i, Context(0));
eval.run(&c, &args)?;
}
Ok(())
}
}

45
src/eval/cmd/runner.rs Normal file
View File

@ -0,0 +1,45 @@
use std::collections::HashMap;
use crate::eval::{
error::EvalError,
cmd::Cmd,
value::Value
};
pub struct CommandRunner {
commands: HashMap<String, Box<dyn Cmd>>
}
impl CommandRunner {
pub fn new() -> Self {
Self {
commands: HashMap::new()
}
}
pub fn register(&mut self, name: &str, cmd: Box<dyn Cmd>) -> Result<(), EvalError> {
if self.commands.contains_key(&name.to_owned()) {
return Err(EvalError {
msg: format!("command {} already defined", name),
line: 0
});
}
self.commands.insert(name.to_owned(), cmd);
Ok(())
}
pub fn call(&mut self, name: &str, args: Vec<Value>) -> Result<(), EvalError> {
match self.commands.get_mut(&name.to_owned()) {
Some(cmd) => {
cmd.call(args)
},
None => {
Err(EvalError {
msg: format!("command {} not defined", name),
line: 0
})
}
}
}
}

View File

@ -4,30 +4,53 @@ use crate::{
eval::{
value::Value,
error::EvalError,
symtable::SymTable
symtable::SymTable,
cmd::{
runner::CommandRunner,
builder::CmdBuilder
},
}
};
use crate::natives::cmd::*;
pub struct Evaluator {
table: SymTable
table: SymTable,
runner: CommandRunner
}
type EvalResult = Result<Option<Value>, EvalError>;
impl Evaluator {
pub fn new() -> Self {
let mut runner = CommandRunner::new();
runner.register("print",
Box::new(PrintCmd {})).expect("cannot initialize print");
runner.register("sh",
Box::new(ShCmd {})).expect("cannot initialize sh");
Self {
table: SymTable::new()
table: SymTable::new(),
runner
}
}
pub fn run(&mut self, root: &Contextual<Node>) -> EvalResult {
pub fn symbols<T>(&mut self, callback: T)
where T: Fn(&mut SymTable)
{
callback(&mut self.table);
}
pub fn run(&mut self, root: &Contextual<Node>, args: &Vec<Value>)
-> EvalResult {
match &root.0 {
Node::Module(_, nodes) => {
let mut res = None;
for node in nodes.iter() {
res = self.run(node)?
res = self.run(node, &args)?
}
Ok(res)
@ -63,7 +86,7 @@ impl Evaluator {
}
for node_val in values {
let val = self.run(&node_val)?;
let val = self.run(&node_val, &args)?;
if let Some(v) = val {
val_array.push(v);
}
@ -93,7 +116,7 @@ impl Evaluator {
Some(_) => {},
None => {
if let Some(value)
= self.nodes_to_value(&values)? {
= self.nodes_to_value(&values)? {
self.table.set(&name, value);
}
}
@ -142,15 +165,15 @@ impl Evaluator {
Node::String(value)
=> Ok(Some(Value::String(value.clone()))),
Node::Symbol(value)
=> Ok(Some(Value::Symbol(value.clone()))),
Node::Array(nodes) => {
let mut values: Vec<Value> = vec![];
for node in nodes {
match self.run(&node) {
match self.run(&node, &args) {
Ok(Some(value)) => {
values.push(value);
},
@ -168,15 +191,122 @@ impl Evaluator {
Ok(Some(Value::Array(values)))
}
_ => Ok(None)
Node::CmdDef(
Contextual(target, ctx),
requires,
provides,
Contextual(body, body_ctx)
) => {
match &**target {
Node::Ident(name) => {
let mut builder = CmdBuilder::new();
for req in requires.iter() {
if let Some(val) = self.run(req, &args)? {
builder.require(val);
} else {
return Err(EvalError {
msg: format!("bad requirement {:?}", req),
line: body_ctx.0
});
}
}
for prov in provides.iter() {
if let Some(val) = self.run(prov, &args)? {
builder.provide(val);
} else {
return Err(EvalError {
msg: format!("bad provided value {:?}", prov),
line: body_ctx.0
});
}
}
match **body {
Node::Block(ref commands) => {
for subcmd in commands.iter() {
let Contextual(node, _) = subcmd;
builder.instr(node.clone());
}
self.runner.register(
name, Box::new(builder.build())
)?;
Ok(None)
},
_ => {
Err(EvalError {
msg: String::from("wrong block"),
line: body_ctx.0
})
}
}
},
_ => { Err(EvalError {
msg: String::from(
"wrong target command",
),
line: ctx.0
}) }
}
},
Node::CmdCall(
Contextual(target, ctx),
args_ctx
) => {
let mut args: Vec<Value> = vec![];
for a in args_ctx.iter() {
match self.run(a, &args) {
Ok(Some(val)) => { args.push(val); },
Err(err) => { return Err(err); },
_ => {
return Err(EvalError {
msg: String::from("unexpected command argument"),
line: ctx.0
});
}
}
}
match &**target {
Node::Ident(name) => {
match self.runner.call(&name, args) {
Ok(()) => {},
Err(err) => { return Err(err); }
}
}
_ => {
return Err(EvalError {
msg: String::from("target error"),
line: ctx.0
});
}
}
Ok(None)
},
_ => {
Err(EvalError {
msg: format!("invalid node <{:?}>", root),
line: root.1.0
})
}
//_ => Ok(None)
}
}
fn nodes_to_value(&mut self, nodes: &Vec<Contextual<Node>>) -> EvalResult {
let mut val_array: Vec<Value> = vec![];
let args: Vec<Value> = vec![];
for node in nodes {
match self.run(&node) {
match self.run(&node, &args) {
Ok(Some(val)) => val_array.push(val),
Ok(None) => {},
Err(err) => { return Err(err); }
@ -211,10 +341,11 @@ mod test {
let mut parser = Parser::new("mod", lex);
let mut evaluator = Evaluator::new();
let root = parser.run()?;
let args: Vec<Value> = vec![];
match root {
Some(root) => {
if let Some(val) = evaluator.run(&root)? {
if let Some(val) = evaluator.run(&root, &args)? {
if val != value {
Err(Box::new(EvalError {
msg: format!("expected: {:?}, got: {:?}", value, val),
@ -244,8 +375,9 @@ mod test {
let root = parser.run()?.unwrap();
let mut evaluator = Evaluator::new();
let args: Vec<Value> = vec![];
match evaluator.run(&root) {
match evaluator.run(&root, &args) {
Err(_) => Ok(()),
_ => Err(Box::new(EvalError {
msg: format!("\"{}\" should fail", source),

View File

@ -2,3 +2,4 @@ pub mod value;
pub mod evaluator;
pub mod error;
pub mod symtable;
pub mod cmd;

View File

@ -10,3 +10,25 @@ pub enum Value {
Array(Vec<Value>)
}
pub fn concat_value(value: Value) -> String {
let mut result: Vec<String> = vec![];
match value {
Value::String(value) => {
result.push(value.clone());
},
Value::Symbol(value) => {
result.push(value.clone());
},
Value::Array(values) => {
for v in values {
result.push(concat_value(v));
}
},
Value::Int(value) => {result.push(format!("{}", value))},
Value::Float(value) => {result.push(format!("{}", value))},
Value::Bool(value) => {result.push(format!("{}", value))},
}
result.join(" ")
}

View File

@ -1,8 +1,9 @@
use std::fs;
use crate::eval::value::Value;
mod ast;
mod eval;
mod context;
mod natives;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
@ -19,7 +20,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut evaluator = eval::evaluator::Evaluator::new();
if let Some(val) = evaluator.run(&root)? {
let args: Vec<Value> = vec![];
if let Some(val) = evaluator.run(&root, &args)? {
println!("{:?}", val);
}
}

52
src/natives/cmd.rs Normal file
View File

@ -0,0 +1,52 @@
use std::process;
use crate::eval::cmd::{
Cmd,
CmdCallResult
};
use crate::eval::value::{
Value,
concat_value
};
pub struct PrintCmd;
impl Cmd for PrintCmd {
fn call(&mut self, args: Vec<Value>) -> CmdCallResult {
let mut sep = String::from("");
for arg in args.iter() {
print!("{}{}", sep, concat_value(arg.clone()));
sep = String::from(" ");
}
print!("\n");
Ok(())
}
}
pub struct ShCmd;
impl Cmd for ShCmd {
fn call(&mut self, args: Vec<Value>) -> CmdCallResult {
let mut str_args: Vec<String> = vec![];
for arg in args.iter() {
str_args.push(concat_value(arg.clone()));
}
let my_cmd = str_args.join(" ");
let out = process::Command::new("sh")
.arg("-c")
.arg(my_cmd)
.stdout(process::Stdio::piped())
.output()
.expect("command failed");
print!("{}", String::from_utf8_lossy(&out.stdout));
Ok(())
}
}

1
src/natives/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod cmd;