mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -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: "(" ;
|
lparen: "(" ;
|
||||||
rparen: ")" ;
|
rparen: ")" ;
|
||||||
bareword: ASCII_CHAR, { DIGIT | VISIBLE_CHAR | "_" } ;
|
bareword: ASCII_CHAR, { DIGIT | VISIBLE_CHAR | "_" } ;
|
||||||
let_keyword: "let" [WS] ;
|
let_keyword: "let" ;
|
||||||
import_keyword: "import" [WS];
|
import_keyword: "import" ;
|
||||||
as_keyword: "as" [WS];
|
include_keyword: "include" ;
|
||||||
|
as_keyword: "as" ;
|
||||||
macro_keyword: "macro" ;
|
macro_keyword: "macro" ;
|
||||||
module_keyword: "module" ;
|
module_keyword: "module" ;
|
||||||
mod_keyword: "mod" ;
|
mod_keyword: "mod" ;
|
||||||
@ -134,6 +135,12 @@ call_expression: bareword, lparen, [arglist], rparen ;
|
|||||||
format_expr: str, percent, lparen, [arglist], rparen ;
|
format_expr: str, percent, lparen, [arglist], rparen ;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Include Expression
|
||||||
|
|
||||||
|
```
|
||||||
|
include_expr: include_keyword, bareword, str ;
|
||||||
|
```
|
||||||
|
|
||||||
#### Non Operator Expression
|
#### Non Operator Expression
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -142,6 +149,7 @@ non_operator_expr: literal
|
|||||||
| macrodef
|
| macrodef
|
||||||
| module_def
|
| module_def
|
||||||
| format_expression
|
| format_expression
|
||||||
|
| include_expression
|
||||||
| copy_expression
|
| copy_expression
|
||||||
| call_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);
|
let mut syms_set = self.validate_value_symbols(&mut stack, val);
|
||||||
bad_symbols.extend(syms_set.drain());
|
bad_symbols.extend(syms_set.drain());
|
||||||
}
|
}
|
||||||
&Expression::Macro(_) => {
|
&Expression::Macro(_)
|
||||||
// noop
|
| &Expression::Module(_)
|
||||||
continue;
|
| &Expression::ListOp(_)
|
||||||
}
|
| &Expression::Include(_) => {
|
||||||
&Expression::Module(_) => {
|
|
||||||
// noop
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
&Expression::ListOp(_) => {
|
|
||||||
// noop
|
// noop
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -551,6 +546,14 @@ pub struct FormatDef {
|
|||||||
pub pos: Position,
|
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.
|
/// Encodes a list expression in the UCG AST.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ListDef {
|
pub struct ListDef {
|
||||||
@ -625,6 +628,7 @@ pub enum Expression {
|
|||||||
// TODO(jwall): This should really store it's position :-(
|
// TODO(jwall): This should really store it's position :-(
|
||||||
Grouped(Box<Expression>),
|
Grouped(Box<Expression>),
|
||||||
Format(FormatDef),
|
Format(FormatDef),
|
||||||
|
Include(IncludeDef),
|
||||||
Call(CallDef),
|
Call(CallDef),
|
||||||
Macro(MacroDef),
|
Macro(MacroDef),
|
||||||
Select(SelectDef),
|
Select(SelectDef),
|
||||||
@ -646,6 +650,7 @@ impl Expression {
|
|||||||
&Expression::Module(ref def) => &def.pos,
|
&Expression::Module(ref def) => &def.pos,
|
||||||
&Expression::Select(ref def) => &def.pos,
|
&Expression::Select(ref def) => &def.pos,
|
||||||
&Expression::ListOp(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(_) => {
|
&Expression::Select(_) => {
|
||||||
write!(w, "<Select>")?;
|
write!(w, "<Select>")?;
|
||||||
}
|
}
|
||||||
|
&Expression::Include(_) => {
|
||||||
|
write!(w, "<Include>")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -334,6 +334,33 @@ impl<'a> FileBuilder<'a> {
|
|||||||
.is_some()
|
.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>> {
|
fn eval_import(&mut self, def: &ImportDef) -> Result<Rc<Val>, Box<Error>> {
|
||||||
let sym = &def.name;
|
let sym = &def.name;
|
||||||
if Self::check_reserved_word(&sym.fragment) {
|
if Self::check_reserved_word(&sym.fragment) {
|
||||||
@ -347,25 +374,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
// Try a relative path first.
|
// Try a relative path first.
|
||||||
let mut normalized = self.file.parent().unwrap().to_path_buf();
|
let normalized = self.find_file(&def.path.fragment, true)?;
|
||||||
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()?;
|
|
||||||
if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) {
|
if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) {
|
||||||
return Err(Box::new(error::BuildError::new(
|
return Err(Box::new(error::BuildError::new(
|
||||||
format!(
|
format!(
|
||||||
@ -1167,6 +1176,42 @@ impl<'a> FileBuilder<'a> {
|
|||||||
Ok(ok)
|
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.
|
// Evals a single Expression in the context of a running Builder.
|
||||||
// It does not mutate the builders collected state at all.
|
// 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>> {
|
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::Module(ref def) => self.eval_module_def(def, scope),
|
||||||
&Expression::Select(ref def) => self.eval_select(def, scope),
|
&Expression::Select(ref def) => self.eval_select(def, scope),
|
||||||
&Expression::ListOp(ref def) => self.eval_list_op(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>(
|
fn tuple_to_call<'a>(
|
||||||
input: SliceIter<'a, Token>,
|
input: SliceIter<'a, Token>,
|
||||||
val: Value,
|
val: Value,
|
||||||
@ -622,6 +637,7 @@ make_fn!(
|
|||||||
trace_parse!(module_expression),
|
trace_parse!(module_expression),
|
||||||
trace_parse!(select_expression),
|
trace_parse!(select_expression),
|
||||||
trace_parse!(grouped_expression),
|
trace_parse!(grouped_expression),
|
||||||
|
trace_parse!(include_expression),
|
||||||
trace_parse!(unprefixed_expression)
|
trace_parse!(unprefixed_expression)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -284,6 +284,10 @@ make_fn!(importtok<OffsetStrIter, Token>,
|
|||||||
do_text_token_tok!(TokenType::BAREWORD, "import", WS)
|
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>,
|
make_fn!(asserttok<OffsetStrIter, Token>,
|
||||||
do_text_token_tok!(TokenType::BAREWORD, "assert", WS)
|
do_text_token_tok!(TokenType::BAREWORD, "assert", WS)
|
||||||
);
|
);
|
||||||
@ -397,6 +401,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
|||||||
macrotok,
|
macrotok,
|
||||||
moduletok,
|
moduletok,
|
||||||
importtok,
|
importtok,
|
||||||
|
includetok,
|
||||||
astok,
|
astok,
|
||||||
maptok,
|
maptok,
|
||||||
filtertok,
|
filtertok,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user