Merge pull request #5 from jntrnr/string_interp

String interp
This commit is contained in:
JT 2021-07-30 15:34:00 +12:00 committed by GitHub
commit 7cac5bb633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 183 additions and 2 deletions

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Display};
use crate::{
parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId,
@ -20,6 +20,24 @@ pub enum Value {
Block(BlockId),
Unknown,
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Bool { val, .. } => {
write!(f, "{}", val)
}
Value::Int { val, .. } => {
write!(f, "{}", val)
}
Value::String { val, .. } => write!(f, "{}", val),
Value::List(..) => write!(f, "<list>"),
Value::Block(..) => write!(f, "<block>"),
Value::Unknown => write!(f, "<unknown>"),
}
}
}
impl Value {
pub fn add(&self, rhs: &Value) -> Result<Value, ShellError> {
match (self, rhs) {
@ -138,6 +156,18 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result<Value, She
}
_ => Err(ShellError::Mismatch("bool".into(), Span::unknown())),
}
} else if decl.signature.name == "build-string" {
let mut output = vec![];
for expr in &call.positional {
let val = eval_expression(state, stack, expr)?;
output.push(val.to_string());
}
Ok(Value::String {
val: output.join(""),
span: call.head,
})
} else {
Ok(Value::Unknown)
}

View File

@ -49,6 +49,9 @@ fn main() -> std::io::Result<()> {
);
working_set.add_decl(sig.into());
let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string");
working_set.add_decl(sig.into());
let sig = Signature::build("def")
.required("def_name", SyntaxShape::String, "definition name")
.required("params", SyntaxShape::Signature, "parameters")

View File

@ -894,7 +894,144 @@ impl<'a> ParserWorkingSet<'a> {
}
pub(crate) fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option<ParseError>) {
self.parse_variable_expr(span)
let contents = self.get_span_contents(span);
if contents.starts_with(b"$\"") {
self.parse_string_interpolation(span)
} else {
self.parse_variable_expr(span)
}
}
pub fn parse_string_interpolation(&mut self, span: Span) -> (Expression, Option<ParseError>) {
#[derive(PartialEq, Eq, Debug)]
enum InterpolationMode {
String,
Expression,
}
let mut error = None;
let contents = self.get_span_contents(span);
let start = if contents.starts_with(b"$\"") {
span.start + 2
} else {
span.start
};
let end = if contents.ends_with(b"\"") && contents.len() > 2 {
span.end - 1
} else {
span.end
};
let inner_span = Span { start, end };
let contents = self.get_span_contents(inner_span).to_vec();
let mut output = vec![];
let mut mode = InterpolationMode::String;
let mut token_start = start;
let mut depth = 0;
let mut b = start;
#[allow(clippy::needless_range_loop)]
while b != end {
if contents[b - start] == b'(' && mode == InterpolationMode::String {
depth = 1;
mode = InterpolationMode::Expression;
if token_start < b {
let span = Span {
start: token_start,
end: b,
};
let str_contents = self.get_span_contents(span);
output.push(Expression {
expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()),
span,
ty: Type::String,
});
}
token_start = b;
} else if contents[b - start] == b'(' && mode == InterpolationMode::Expression {
depth += 1;
} else if contents[b - start] == b')' && mode == InterpolationMode::Expression {
match depth {
0 => {}
1 => {
mode = InterpolationMode::String;
if token_start < b {
let span = Span {
start: token_start,
end: b + 1,
};
let (expr, err) = self.parse_full_column_path(span);
error = error.or(err);
output.push(expr);
}
token_start = b + 1;
}
_ => depth -= 1,
}
}
b += 1;
}
match mode {
InterpolationMode::String => {
if token_start < end {
let span = Span {
start: token_start,
end,
};
let str_contents = self.get_span_contents(span);
output.push(Expression {
expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()),
span,
ty: Type::String,
});
}
}
InterpolationMode::Expression => {
if token_start < end {
let span = Span {
start: token_start,
end,
};
let (expr, err) = self.parse_full_column_path(span);
error = error.or(err);
output.push(expr);
}
}
}
if let Some(decl_id) = self.find_decl(b"build-string") {
(
Expression {
expr: Expr::Call(Box::new(Call {
head: Span {
start: span.start,
end: span.start + 2,
},
named: vec![],
positional: output,
decl_id,
})),
span,
ty: Type::String,
},
error,
)
} else {
(
Expression::garbage(span),
Some(ParseError::UnknownCommand(span)),
)
}
}
pub fn parse_variable_expr(&mut self, span: Span) -> (Expression, Option<ParseError>) {

View File

@ -102,6 +102,17 @@ impl Signature {
self
}
pub fn rest(mut self, shape: impl Into<SyntaxShape>, desc: impl Into<String>) -> Signature {
self.rest_positional = Some(PositionalArg {
name: "rest".into(),
desc: desc.into(),
shape: shape.into(),
var_id: None,
});
self
}
/// Add an optional named flag argument to the signature
pub fn named(
mut self,