✨ native and user defined commands.
parent
76e5db35b4
commit
ec957f0b37
|
@ -4,9 +4,15 @@ INSTR ::=
|
||||||
| ASSIGN semicolon
|
| ASSIGN semicolon
|
||||||
| ASSIGN_IF semicolon
|
| ASSIGN_IF semicolon
|
||||||
| ASSIGN_ADD semicolon
|
| ASSIGN_ADD semicolon
|
||||||
|
| CMD_CALL semicolon
|
||||||
|
| CMD_DEF
|
||||||
|
|
||||||
ASSIGN ::= ident assign EXPR
|
ASSIGN ::= ident assign EXPR
|
||||||
ASSIGN_IF ::= ident assign_if EXPR
|
ASSIGN_IF ::= ident assign_if EXPR
|
||||||
ASSIGN_ADD ::= ident assign_add 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
|
EXPR ::= LITERAL
|
||||||
LITERAL ::=
|
LITERAL ::=
|
||||||
| BUILTIN
|
| BUILTIN
|
||||||
|
|
|
@ -21,7 +21,9 @@ impl Lexer {
|
||||||
text: String::from(""),
|
text: String::from(""),
|
||||||
separators: vec![
|
separators: vec![
|
||||||
';', '[', ']',
|
';', '[', ']',
|
||||||
'=', '?', '+'
|
'=', '?', '+', ':',
|
||||||
|
'(', ')', '{', '}',
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +89,9 @@ impl Lexer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.is_ascii_alphabetic() && c != '_'
|
if !c.is_ascii_alphabetic() && c != '_'
|
||||||
|
&& c != '%'
|
||||||
|
&& c != '@'
|
||||||
|
&& c != '$'
|
||||||
&& !c.is_digit(10) {
|
&& !c.is_digit(10) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -334,6 +339,11 @@ impl Iterator for Lexer {
|
||||||
self.skip_spaces();
|
self.skip_spaces();
|
||||||
self.skip_comments();
|
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("+=") {
|
if let Some(token) = self.scan_text("+=") {
|
||||||
self.cursor = token.position;
|
self.cursor = token.position;
|
||||||
return Some(Contextual(Node::AssignAdd, Context(self.line)));
|
return Some(Contextual(Node::AssignAdd, Context(self.line)));
|
||||||
|
@ -349,6 +359,26 @@ impl Iterator for Lexer {
|
||||||
return Some(Contextual(Node::Assign, Context(self.line)));
|
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("[") {
|
if let Some(token) = self.scan_text("[") {
|
||||||
self.cursor = token.position;
|
self.cursor = token.position;
|
||||||
return Some(Contextual(Node::OSquare, Context(self.line)))
|
return Some(Contextual(Node::OSquare, Context(self.line)))
|
||||||
|
@ -359,6 +389,11 @@ impl Iterator for Lexer {
|
||||||
return Some(Contextual(Node::CSquare, Context(self.line)))
|
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") {
|
if let Some(token) = self.scan_keyword("true") {
|
||||||
self.cursor = token.position;
|
self.cursor = token.position;
|
||||||
return Some(Contextual(Node::Bool(token.value == "true"),
|
return Some(Contextual(Node::Bool(token.value == "true"),
|
||||||
|
@ -376,6 +411,11 @@ impl Iterator for Lexer {
|
||||||
return Some(Contextual(Node::Semicolon, Context(self.line)))
|
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() {
|
if let Some(token) = self.scan_string() {
|
||||||
self.cursor = token.position;
|
self.cursor = token.position;
|
||||||
return Some(Contextual(Node::String(token.value.clone()),
|
return Some(Contextual(Node::String(token.value.clone()),
|
||||||
|
@ -441,6 +481,17 @@ mod test {
|
||||||
|
|
||||||
Ok(result)
|
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]
|
#[test]
|
||||||
fn test_booleans() {
|
fn test_booleans() {
|
||||||
let mut lex = Lexer::new();
|
let mut lex = Lexer::new();
|
||||||
|
@ -590,4 +641,45 @@ mod test {
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::context::Contextual;
|
use crate::context::Contextual;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[derive(Clone)]
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
Module(String, Vec<Contextual<Node>>),
|
Module(String, Vec<Contextual<Node>>),
|
||||||
|
@ -9,8 +10,8 @@ pub enum Node {
|
||||||
Float(f64),
|
Float(f64),
|
||||||
String(String),
|
String(String),
|
||||||
Symbol(String),
|
Symbol(String),
|
||||||
OSquare,
|
OSquare, CSquare,
|
||||||
CSquare,
|
OPar, CPar,
|
||||||
Array(Vec<Contextual<Node>>),
|
Array(Vec<Contextual<Node>>),
|
||||||
Assign,
|
Assign,
|
||||||
AssignIf,
|
AssignIf,
|
||||||
|
@ -19,7 +20,18 @@ pub enum Node {
|
||||||
AssignIfInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
|
AssignIfInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
|
||||||
AssignAddInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
|
AssignAddInstr(Contextual<Box<Node>>, Vec<Contextual<Node>>),
|
||||||
Ident(String),
|
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>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,10 @@ impl Parser {
|
||||||
-> ParseResult {
|
-> ParseResult {
|
||||||
match self.lexer.next() {
|
match self.lexer.next() {
|
||||||
Some(Contextual(Node::Semicolon, _)) => result,
|
Some(Contextual(Node::Semicolon, _)) => result,
|
||||||
|
Some(Contextual(node, Context(line)))
|
||||||
|
=> Err(error::AstError {
|
||||||
|
msg: format!("unexpected node {:?}", node),
|
||||||
|
line}),
|
||||||
_ => Err(error::AstError {
|
_ => Err(error::AstError {
|
||||||
msg: String::from("missing semicolon"),
|
msg: String::from("missing semicolon"),
|
||||||
line: self.lexer.line()})
|
line: self.lexer.line()})
|
||||||
|
@ -58,6 +62,24 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_instr(&mut self) -> ParseResult {
|
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) {
|
match self.lexer.peek(1) {
|
||||||
Some(Contextual(Node::Assign, _)) => {
|
Some(Contextual(Node::Assign, _)) => {
|
||||||
let assign = self.parse_assign();
|
let assign = self.parse_assign();
|
||||||
|
@ -74,6 +96,7 @@ impl Parser {
|
||||||
self.ensure_semicolon(assign)
|
self.ensure_semicolon(assign)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
_ =>
|
_ =>
|
||||||
match self.parse_expr() {
|
match self.parse_expr() {
|
||||||
Ok(Some(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 {
|
fn parse_expr(&mut self) -> ParseResult {
|
||||||
self.parse_literal()
|
self.parse_literal()
|
||||||
}
|
}
|
||||||
|
@ -155,7 +307,16 @@ impl Parser {
|
||||||
self.lexer.next();
|
self.lexer.next();
|
||||||
Ok(Some(Contextual(Node::Ident(id), ctx)))
|
Ok(Some(Contextual(Node::Ident(id), ctx)))
|
||||||
},
|
},
|
||||||
|
|
||||||
Some(Contextual(Node::OSquare, _)) => self.parse_array(),
|
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(),
|
Some(_) => self.parse_builtin(),
|
||||||
None => { self.lexer.next(); Ok(None) }
|
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]
|
#[test]
|
||||||
fn premature_end() -> TestResult {
|
fn premature_end() -> TestResult {
|
||||||
let mut lex = lexer::Lexer::new();
|
let mut lex = lexer::Lexer::new();
|
||||||
|
@ -287,8 +456,9 @@ mod test {
|
||||||
fn symbols() -> TestResult {
|
fn symbols() -> TestResult {
|
||||||
test_parser(
|
test_parser(
|
||||||
Node::Module("mod".to_owned(), vec![
|
Node::Module("mod".to_owned(), vec![
|
||||||
Contextual(Node::Symbol(String::from("bonjour++")),
|
Contextual(Node::Symbol(
|
||||||
Context(0))
|
String::from("bonjour++")
|
||||||
|
), Context(0))
|
||||||
]),
|
]),
|
||||||
" 'bonjour++ ; "
|
" 'bonjour++ ; "
|
||||||
)?;
|
)?;
|
||||||
|
@ -373,4 +543,73 @@ mod test {
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
pub struct Context(pub i32);
|
pub struct Context(pub i32);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Contextual<T>(pub T, pub Context);
|
pub struct Contextual<T>(pub T, pub Context);
|
||||||
|
|
||||||
impl<T: PartialEq> PartialEq<Contextual<T>> for Contextual<T> {
|
impl<T: PartialEq> PartialEq<Contextual<T>> for Contextual<T> {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,30 +4,53 @@ use crate::{
|
||||||
eval::{
|
eval::{
|
||||||
value::Value,
|
value::Value,
|
||||||
error::EvalError,
|
error::EvalError,
|
||||||
symtable::SymTable
|
symtable::SymTable,
|
||||||
|
cmd::{
|
||||||
|
runner::CommandRunner,
|
||||||
|
builder::CmdBuilder
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::natives::cmd::*;
|
||||||
|
|
||||||
pub struct Evaluator {
|
pub struct Evaluator {
|
||||||
table: SymTable
|
table: SymTable,
|
||||||
|
runner: CommandRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
type EvalResult = Result<Option<Value>, EvalError>;
|
type EvalResult = Result<Option<Value>, EvalError>;
|
||||||
|
|
||||||
impl Evaluator {
|
impl Evaluator {
|
||||||
pub fn new() -> Self {
|
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 {
|
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 {
|
match &root.0 {
|
||||||
Node::Module(_, nodes) => {
|
Node::Module(_, nodes) => {
|
||||||
let mut res = None;
|
let mut res = None;
|
||||||
|
|
||||||
for node in nodes.iter() {
|
for node in nodes.iter() {
|
||||||
res = self.run(node)?
|
res = self.run(node, &args)?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
@ -63,7 +86,7 @@ impl Evaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
for node_val in values {
|
for node_val in values {
|
||||||
let val = self.run(&node_val)?;
|
let val = self.run(&node_val, &args)?;
|
||||||
if let Some(v) = val {
|
if let Some(v) = val {
|
||||||
val_array.push(v);
|
val_array.push(v);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +173,7 @@ impl Evaluator {
|
||||||
let mut values: Vec<Value> = vec![];
|
let mut values: Vec<Value> = vec![];
|
||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
match self.run(&node) {
|
match self.run(&node, &args) {
|
||||||
Ok(Some(value)) => {
|
Ok(Some(value)) => {
|
||||||
values.push(value);
|
values.push(value);
|
||||||
},
|
},
|
||||||
|
@ -168,15 +191,122 @@ impl Evaluator {
|
||||||
Ok(Some(Value::Array(values)))
|
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 {
|
fn nodes_to_value(&mut self, nodes: &Vec<Contextual<Node>>) -> EvalResult {
|
||||||
let mut val_array: Vec<Value> = vec![];
|
let mut val_array: Vec<Value> = vec![];
|
||||||
|
let args: Vec<Value> = vec![];
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
match self.run(&node) {
|
match self.run(&node, &args) {
|
||||||
Ok(Some(val)) => val_array.push(val),
|
Ok(Some(val)) => val_array.push(val),
|
||||||
Ok(None) => {},
|
Ok(None) => {},
|
||||||
Err(err) => { return Err(err); }
|
Err(err) => { return Err(err); }
|
||||||
|
@ -211,10 +341,11 @@ mod test {
|
||||||
let mut parser = Parser::new("mod", lex);
|
let mut parser = Parser::new("mod", lex);
|
||||||
let mut evaluator = Evaluator::new();
|
let mut evaluator = Evaluator::new();
|
||||||
let root = parser.run()?;
|
let root = parser.run()?;
|
||||||
|
let args: Vec<Value> = vec![];
|
||||||
|
|
||||||
match root {
|
match root {
|
||||||
Some(root) => {
|
Some(root) => {
|
||||||
if let Some(val) = evaluator.run(&root)? {
|
if let Some(val) = evaluator.run(&root, &args)? {
|
||||||
if val != value {
|
if val != value {
|
||||||
Err(Box::new(EvalError {
|
Err(Box::new(EvalError {
|
||||||
msg: format!("expected: {:?}, got: {:?}", value, val),
|
msg: format!("expected: {:?}, got: {:?}", value, val),
|
||||||
|
@ -244,8 +375,9 @@ mod test {
|
||||||
let root = parser.run()?.unwrap();
|
let root = parser.run()?.unwrap();
|
||||||
|
|
||||||
let mut evaluator = Evaluator::new();
|
let mut evaluator = Evaluator::new();
|
||||||
|
let args: Vec<Value> = vec![];
|
||||||
|
|
||||||
match evaluator.run(&root) {
|
match evaluator.run(&root, &args) {
|
||||||
Err(_) => Ok(()),
|
Err(_) => Ok(()),
|
||||||
_ => Err(Box::new(EvalError {
|
_ => Err(Box::new(EvalError {
|
||||||
msg: format!("\"{}\" should fail", source),
|
msg: format!("\"{}\" should fail", source),
|
||||||
|
|
|
@ -2,3 +2,4 @@ pub mod value;
|
||||||
pub mod evaluator;
|
pub mod evaluator;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod symtable;
|
pub mod symtable;
|
||||||
|
pub mod cmd;
|
||||||
|
|
|
@ -10,3 +10,25 @@ pub enum Value {
|
||||||
Array(Vec<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(" ")
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use crate::eval::value::Value;
|
||||||
mod ast;
|
mod ast;
|
||||||
mod eval;
|
mod eval;
|
||||||
mod context;
|
mod context;
|
||||||
|
mod natives;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args: Vec<String> = std::env::args().collect();
|
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();
|
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);
|
println!("{:?}", val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod cmd;
|
Reference in New Issue