FEATURE: Add include as a string functionality.

Includes happy path tests for including a string.

fixes #15
This commit is contained in:
Jeremy Wall 2019-01-04 10:01:49 -06:00
parent 5daf266366
commit a830047784
7 changed files with 121 additions and 31 deletions

View File

@ -44,9 +44,10 @@ rbracket: "]" ;
lparen: "(" ;
rparen: ")" ;
bareword: ASCII_CHAR, { DIGIT | VISIBLE_CHAR | "_" } ;
let_keyword: "let" [WS] ;
import_keyword: "import" [WS];
as_keyword: "as" [WS];
let_keyword: "let" ;
import_keyword: "import" ;
include_keyword: "include" ;
as_keyword: "as" ;
macro_keyword: "macro" ;
module_keyword: "module" ;
mod_keyword: "mod" ;
@ -134,6 +135,12 @@ call_expression: bareword, lparen, [arglist], rparen ;
format_expr: str, percent, lparen, [arglist], rparen ;
```
#### Include Expression
```
include_expr: include_keyword, bareword, str ;
```
#### Non Operator Expression
```
@ -142,6 +149,7 @@ non_operator_expr: literal
| macrodef
| module_def
| format_expression
| include_expression
| copy_expression
| call_expression ;
```

View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
echo "included"

View File

@ -0,0 +1,5 @@
let script = include str "./include_example.sh";
assert |
script == "#!/usr/bin/env bash
echo \"included\"";
|;

View File

@ -457,15 +457,10 @@ impl MacroDef {
let mut syms_set = self.validate_value_symbols(&mut stack, val);
bad_symbols.extend(syms_set.drain());
}
&Expression::Macro(_) => {
// noop
continue;
}
&Expression::Module(_) => {
// noop
continue;
}
&Expression::ListOp(_) => {
&Expression::Macro(_)
| &Expression::Module(_)
| &Expression::ListOp(_)
| &Expression::Include(_) => {
// noop
continue;
}
@ -551,6 +546,14 @@ pub struct FormatDef {
pub pos: Position,
}
/// Encodes an import statement in the UCG AST.
#[derive(Debug, PartialEq, Clone)]
pub struct IncludeDef {
pub pos: Position,
pub path: Token,
pub typ: Token,
}
/// Encodes a list expression in the UCG AST.
#[derive(Debug, PartialEq, Clone)]
pub struct ListDef {
@ -625,6 +628,7 @@ pub enum Expression {
// TODO(jwall): This should really store it's position :-(
Grouped(Box<Expression>),
Format(FormatDef),
Include(IncludeDef),
Call(CallDef),
Macro(MacroDef),
Select(SelectDef),
@ -646,6 +650,7 @@ impl Expression {
&Expression::Module(ref def) => &def.pos,
&Expression::Select(ref def) => &def.pos,
&Expression::ListOp(ref def) => &def.pos,
&Expression::Include(ref def) => &def.pos,
}
}
}
@ -683,6 +688,9 @@ impl fmt::Display for Expression {
&Expression::Select(_) => {
write!(w, "<Select>")?;
}
&Expression::Include(_) => {
write!(w, "<Include>")?;
}
}
Ok(())
}

View File

@ -334,6 +334,33 @@ impl<'a> FileBuilder<'a> {
.is_some()
}
fn find_file<P: Into<PathBuf>>(
&mut self,
path: P,
use_import_path: bool,
) -> Result<PathBuf, Box<Error>> {
// Try a relative path first.
let path = path.into();
let mut normalized = self.file.parent().unwrap().to_path_buf();
if path.is_relative() {
normalized.push(&path);
// First see if the normalized file exists or not.
if !normalized.exists() && use_import_path {
// If it does not then look for it in the list of import_paths
for mut p in self.import_path.iter().cloned() {
p.push(&path);
if p.exists() {
normalized = p;
break;
}
}
}
} else {
normalized = path;
}
Ok(normalized.canonicalize()?)
}
fn eval_import(&mut self, def: &ImportDef) -> Result<Rc<Val>, Box<Error>> {
let sym = &def.name;
if Self::check_reserved_word(&sym.fragment) {
@ -347,25 +374,7 @@ impl<'a> FileBuilder<'a> {
)));
}
// Try a relative path first.
let mut normalized = self.file.parent().unwrap().to_path_buf();
let import_path = PathBuf::from(&def.path.fragment);
if import_path.is_relative() {
normalized.push(&import_path);
// First see if the normalized file exists or not.
if !normalized.exists() {
// If it does not then look for it in the list of import_paths
for mut p in self.import_path.iter().cloned() {
p.push(&import_path);
if p.exists() {
normalized = p;
break;
}
}
}
} else {
normalized = import_path;
}
normalized = normalized.canonicalize()?;
let normalized = self.find_file(&def.path.fragment, true)?;
if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) {
return Err(Box::new(error::BuildError::new(
format!(
@ -1167,6 +1176,42 @@ impl<'a> FileBuilder<'a> {
Ok(ok)
}
pub fn eval_include(&mut self, def: &IncludeDef) -> Result<Rc<Val>, Box<Error>> {
return if def.typ.fragment == "str" {
let normalized = match self.find_file(&def.path.fragment, false) {
Ok(p) => p,
Err(e) => {
return Err(Box::new(error::BuildError::new(
format!("Error finding file {} {}", def.path.fragment, e),
error::ErrorType::TypeFail,
def.typ.pos.clone(),
)))
}
};
let mut f = match File::open(&normalized) {
Ok(f) => f,
Err(e) => {
return Err(Box::new(error::BuildError::new(
format!("Error opening file {} {}", normalized.to_string_lossy(), e),
error::ErrorType::TypeFail,
def.typ.pos.clone(),
)))
}
};
let mut contents = String::new();
f.read_to_string(&mut contents)?;
Ok(Rc::new(Val::Str(contents)))
} else {
// TODO(jwall): Run the conversion on the contents of the file and return it as
// an Rc<Val>.
Err(Box::new(error::BuildError::new(
format!("Unknown include conversion type {}", def.typ.fragment),
error::ErrorType::Unsupported,
def.typ.pos.clone(),
)))
};
}
// Evals a single Expression in the context of a running Builder.
// It does not mutate the builders collected state at all.
pub fn eval_expr(&mut self, expr: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<Error>> {
@ -1181,6 +1226,7 @@ impl<'a> FileBuilder<'a> {
&Expression::Module(ref def) => self.eval_module_def(def, scope),
&Expression::Select(ref def) => self.eval_select(def, scope),
&Expression::ListOp(ref def) => self.eval_list_op(def, scope),
&Expression::Include(ref def) => self.eval_include(def),
}
}
}

View File

@ -509,6 +509,21 @@ make_fn!(
)
);
make_fn!(
include_expression<SliceIter<Token>, Expression>,
do_each!(
pos => pos,
_ => word!("include"),
typ => match_type!(BAREWORD),
path => match_type!(STR),
(Expression::Include(IncludeDef{
pos: pos,
typ: typ,
path: path,
}))
)
);
fn tuple_to_call<'a>(
input: SliceIter<'a, Token>,
val: Value,
@ -622,6 +637,7 @@ make_fn!(
trace_parse!(module_expression),
trace_parse!(select_expression),
trace_parse!(grouped_expression),
trace_parse!(include_expression),
trace_parse!(unprefixed_expression)
)
);

View File

@ -284,6 +284,10 @@ make_fn!(importtok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "import", WS)
);
make_fn!(includetok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "include", WS)
);
make_fn!(asserttok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "assert", WS)
);
@ -397,6 +401,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
macrotok,
moduletok,
importtok,
includetok,
astok,
maptok,
filtertok,