mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-21 18:10:42 -04:00
FEATURE: Add include as a string functionality.
Includes happy path tests for including a string. fixes #15
This commit is contained in:
parent
5daf266366
commit
a830047784
@ -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 ;
|
||||
```
|
||||
|
2
integration_tests/include_example.sh
Normal file
2
integration_tests/include_example.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "included"
|
5
integration_tests/include_test.ucg
Normal file
5
integration_tests/include_test.ucg
Normal file
@ -0,0 +1,5 @@
|
||||
let script = include str "./include_example.sh";
|
||||
assert |
|
||||
script == "#!/usr/bin/env bash
|
||||
echo \"included\"";
|
||||
|;
|
@ -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(())
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
)
|
||||
);
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user