Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support paths with dynamic interpolation from Nix 2.4+ #52

Merged
merged 1 commit into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/kinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub enum SyntaxKind {
NODE_UNARY_OP,
NODE_LITERAL,
NODE_WITH,
NODE_PATH_WITH_INTERPOL,

#[doc(hidden)]
__LAST,
Expand Down
42 changes: 38 additions & 4 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,20 +201,26 @@ where
self.get_text_position()
}
fn bump(&mut self) {
let next = self.buffer.pop_front().or_else(|| self.iter.next());
let next = self.try_next();
match next {
Some((token, s)) => {
if token.is_trivia() {
self.trivia_buffer.push((token, s))
} else {
self.drain_trivia_buffer();
self.consumed += TextSize::of(s.as_str());
self.builder.token(NixLanguage::kind_to_raw(token), s.as_str())
self.manual_bump(s, token);
}
}
None => self.errors.push(ParseError::UnexpectedEOF),
}
}
fn try_next(&mut self) -> Option<(SyntaxKind, SmolStr)> {
self.buffer.pop_front().or_else(|| self.iter.next())
}
fn manual_bump(&mut self, s: SmolStr, token: SyntaxKind) {
self.drain_trivia_buffer();
self.consumed += TextSize::of(s.as_str());
self.builder.token(NixLanguage::kind_to_raw(token), s.as_str())
}
fn peek_data(&mut self) -> Option<&(SyntaxKind, SmolStr)> {
while self.peek_raw().map(|&(t, _)| t.is_trivia()).unwrap_or(false) {
self.bump();
Expand Down Expand Up @@ -507,6 +513,34 @@ where
}
TOKEN_DYNAMIC_START => self.parse_dynamic(),
TOKEN_STRING_START => self.parse_string(),
TOKEN_PATH => {
let next = self.try_next();
if let Some((token, s)) = next {
let is_complex_path = self
.peek()
.map_or(false, |t| t == TOKEN_INTERPOL_START);
self.start_node(if is_complex_path { NODE_PATH_WITH_INTERPOL } else { NODE_LITERAL });
self.manual_bump(s, token);
if is_complex_path {
loop {
match self.peek_raw().map(|(t, _)| t) {
Some(TOKEN_PATH) => self.bump(),
Some(TOKEN_INTERPOL_START) => {
self.start_node(NODE_STRING_INTERPOL);
self.bump();
self.parse_expr();
self.expect(TOKEN_INTERPOL_END);
self.finish_node();
},
_ => break
}
}
}
self.finish_node();
} else {
self.errors.push(ParseError::UnexpectedEOF);
}
},
t if t.is_literal() => {
self.start_node(NODE_LITERAL);
self.bump();
Expand Down
101 changes: 96 additions & 5 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ struct Interpol {
brackets: u32,
string: bool,
multiline: bool,
path_interpol: bool,
}
#[derive(Clone, Copy)]
enum Todo {
StringBody { multiline: bool },
StringEnd,
InterpolStart,
Path,
}
#[derive(Clone, Copy, Default)]
struct Context {
Expand Down Expand Up @@ -140,7 +142,7 @@ impl<'a> Tokenizer<'a> {
Some('{') => {
self.state = start;
self.ctx.push(Context {
interpol: Some(Interpol { brackets: 0, string: true, multiline }),
interpol: Some(Interpol { brackets: 0, string: true, multiline, path_interpol: false }),
todo: Some(Todo::InterpolStart),
..Default::default()
});
Expand Down Expand Up @@ -168,6 +170,21 @@ impl<'a> Iterator for Tokenizer<'a> {
return Some((TOKEN_INTERPOL_START, self.string_since(start)));
}
}
Some(Todo::Path) => {
*todo = Some(Todo::Path);
if self.starts_with_bump("${") {
self.ctx.push(Context {
interpol: Some(Interpol {
brackets: 0,
string: false,
multiline: false,
path_interpol: true,
}),
todo: None,
});
return Some((TOKEN_INTERPOL_START, self.string_since(start)));
}
}
Some(Todo::StringBody { multiline }) => {
*todo = Some(Todo::StringEnd);
let token = self.next_string(multiline);
Expand Down Expand Up @@ -206,6 +223,10 @@ impl<'a> Iterator for Tokenizer<'a> {
}

if self.consume(char::is_whitespace) > 0 {
let ctx = self.ctx.last_mut().unwrap();
if matches!(ctx.todo, Some(Todo::Path)) {
ctx.todo = None;
}
return Some((TOKEN_WHITESPACE, self.string_since(start)));
}

Expand Down Expand Up @@ -259,7 +280,9 @@ impl<'a> Iterator for Tokenizer<'a> {
}
self.consume(is_valid_path_char);
let ident = self.string_since(start);
if ident.ends_with('/') {
if self.remaining().starts_with("${") {
self.ctx.last_mut().unwrap().todo = Some(Todo::Path);
} else if ident.ends_with('/') {
return Some((TOKEN_ERROR, ident));
}
return Some((TOKEN_PATH, ident));
Expand All @@ -284,7 +307,7 @@ impl<'a> Iterator for Tokenizer<'a> {
Some((TOKEN_CURLY_B_OPEN, self.string_since(start)))
}
'}' => {
if let Some(Interpol { ref mut brackets, string, multiline }) =
if let Some(Interpol { ref mut brackets, string, multiline, path_interpol }) =
self.ctx.last_mut().unwrap().interpol
{
match brackets.checked_sub(1) {
Expand All @@ -296,6 +319,9 @@ impl<'a> Iterator for Tokenizer<'a> {
self.ctx.last_mut().unwrap().todo =
Some(Todo::StringBody { multiline });
return Some((TOKEN_INTERPOL_END, self.string_since(start)));
} else if path_interpol {
self.ctx.last_mut().unwrap().todo = Some(Todo::Path);
return Some((TOKEN_INTERPOL_END, self.string_since(start)));
} else {
return Some((TOKEN_DYNAMIC_END, self.string_since(start)));
}
Expand Down Expand Up @@ -359,7 +385,7 @@ impl<'a> Iterator for Tokenizer<'a> {
'$' if self.peek() == Some('{') => {
self.next().unwrap();
self.ctx.push(Context {
interpol: Some(Interpol { brackets: 0, string: false, multiline: false }),
interpol: Some(Interpol { brackets: 0, string: false, multiline: false, path_interpol: false, }),
..Default::default()
});
Some((TOKEN_DYNAMIC_START, self.string_since(start)))
Expand Down Expand Up @@ -388,7 +414,13 @@ impl<'a> Iterator for Tokenizer<'a> {
"rec" => TOKEN_REC,
"then" => TOKEN_THEN,
"with" => TOKEN_WITH,
_ => TOKEN_IDENT,
_ => {
if matches!(self.ctx.last_mut().unwrap().todo, Some(Todo::Path)) {
TOKEN_PATH
} else {
TOKEN_IDENT
}
},
},
IdentType::Path | IdentType::Store => TOKEN_PATH,
IdentType::Uri => TOKEN_URI,
Expand Down Expand Up @@ -841,6 +873,65 @@ mod tests {
assert_eq!(tokenize("<hello/world>"), path("<hello/world>"));
}
#[test]
fn test_path_interpol() {
assert_eq!(
tokenize("./${foo}"),
tokens![
(TOKEN_PATH, "./"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "foo"),
(TOKEN_INTERPOL_END, "}")
]
);
assert_eq!(
tokenize("./${foo} bar"),
tokens![
(TOKEN_PATH, "./"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "foo"),
(TOKEN_INTERPOL_END, "}"),
(TOKEN_WHITESPACE, " "),
(TOKEN_IDENT, "bar"),
]
);
assert_eq!(
tokenize("./${foo}${bar}"),
tokens![
(TOKEN_PATH, "./"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "foo"),
(TOKEN_INTERPOL_END, "}"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "bar"),
(TOKEN_INTERPOL_END, "}"),
]
);
assert_eq!(
tokenize("./${foo}a${bar}"),
tokens![
(TOKEN_PATH, "./"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "foo"),
(TOKEN_INTERPOL_END, "}"),
(TOKEN_PATH, "a"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "bar"),
(TOKEN_INTERPOL_END, "}"),
]
);
assert_eq!(
tokenize("\"./${foo}\""),
tokens![
(TOKEN_STRING_START, "\""),
(TOKEN_STRING_CONTENT, "./"),
(TOKEN_INTERPOL_START, "${"),
(TOKEN_IDENT, "foo"),
(TOKEN_INTERPOL_END, "}"),
(TOKEN_STRING_END, "\""),
]
);
}
#[test]
fn uri() {
assert_eq!(
tokenize("https://google.com/?q=Hello+World"),
Expand Down
6 changes: 5 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ pub enum ParsedType {
UnaryOp(UnaryOp),
Value(Value),
With(With),
PathWithInterpol(PathWithInterpol),
}

impl TryFrom<SyntaxNode> for ParsedType {
Expand Down Expand Up @@ -285,6 +286,7 @@ impl TryFrom<SyntaxNode> for ParsedType {
NODE_UNARY_OP => Ok(ParsedType::UnaryOp(UnaryOp::cast(node).unwrap())),
NODE_LITERAL => Ok(ParsedType::Value(Value::cast(node).unwrap())),
NODE_WITH => Ok(ParsedType::With(With::cast(node).unwrap())),
NODE_PATH_WITH_INTERPOL => Ok(ParsedType::PathWithInterpol(PathWithInterpol::cast(node).unwrap())),
other => Err(ParsedTypeError(other)),
}
}
Expand Down Expand Up @@ -320,6 +322,7 @@ impl TypedNode for ParsedType {
ParsedType::UnaryOp(n) => n.node(),
ParsedType::Value(n) => n.node(),
ParsedType::With(n) => n.node(),
ParsedType::PathWithInterpol(n) => n.node(),
}
}

Expand Down Expand Up @@ -539,5 +542,6 @@ typed! [
pub fn body(&self) -> Option<SyntaxNode> {
nth!(self; 1)
}
}
},
NODE_PATH_WITH_INTERPOL => PathWithInterpol: Wrapper
];
89 changes: 89 additions & 0 deletions test_data/parser/paths/2.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
NODE_ROOT 0..72 {
NODE_LET_IN 0..72 {
TOKEN_LET("let") 0..3
TOKEN_WHITESPACE("\n ") 3..6
NODE_KEY_VALUE 6..29 {
NODE_KEY 6..7 {
NODE_IDENT 6..7 {
TOKEN_IDENT("a") 6..7
}
}
TOKEN_WHITESPACE(" ") 7..8
TOKEN_ASSIGN("=") 8..9
TOKEN_WHITESPACE(" ") 9..10
NODE_LAMBDA 10..28 {
NODE_IDENT 10..11 {
TOKEN_IDENT("f") 10..11
}
TOKEN_COLON(":") 11..12
TOKEN_WHITESPACE(" ") 12..13
NODE_PATH_WITH_INTERPOL 13..28 {
TOKEN_PATH("./foo") 13..18
NODE_STRING_INTERPOL 18..24 {
TOKEN_INTERPOL_START("${") 18..20
NODE_IDENT 20..23 {
TOKEN_IDENT("bar") 20..23
}
TOKEN_INTERPOL_END("}") 23..24
}
TOKEN_PATH("/baz") 24..28
}
}
TOKEN_SEMICOLON(";") 28..29
}
TOKEN_WHITESPACE("\n ") 29..32
NODE_KEY_VALUE 32..67 {
NODE_KEY 32..33 {
NODE_IDENT 32..33 {
TOKEN_IDENT("b") 32..33
}
}
TOKEN_WHITESPACE(" ") 33..34
TOKEN_ASSIGN("=") 34..35
TOKEN_WHITESPACE(" ") 35..36
NODE_APPLY 36..66 {
NODE_APPLY 36..53 {
NODE_APPLY 36..44 {
NODE_IDENT 36..37 {
TOKEN_IDENT("a") 36..37
}
TOKEN_WHITESPACE(" ") 37..38
TOKEN_WHITESPACE(" ") 38..39
NODE_LITERAL 39..44 {
TOKEN_PATH("./bar") 39..44
}
}
NODE_PATH_WITH_INTERPOL 44..53 {
TOKEN_PATH("./baz") 44..49
NODE_STRING_INTERPOL 49..53 {
TOKEN_INTERPOL_START("${") 49..51
NODE_IDENT 51..52 {
TOKEN_IDENT("x") 51..52
}
TOKEN_INTERPOL_END("}") 52..53
}
}
}
TOKEN_WHITESPACE(" ") 53..54
NODE_PATH_WITH_INTERPOL 54..66 {
TOKEN_PATH("./snens") 54..61
NODE_STRING_INTERPOL 61..65 {
TOKEN_INTERPOL_START("${") 61..63
NODE_IDENT 63..64 {
TOKEN_IDENT("x") 63..64
}
TOKEN_INTERPOL_END("}") 64..65
}
TOKEN_PATH("y") 65..66
}
}
TOKEN_SEMICOLON(";") 66..67
}
TOKEN_WHITESPACE("\n") 67..68
TOKEN_IN("in") 68..70
TOKEN_WHITESPACE(" ") 70..71
NODE_IDENT 71..72 {
TOKEN_IDENT("b") 71..72
}
}
}
4 changes: 4 additions & 0 deletions test_data/parser/paths/2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let
a = f: ./foo${bar}/baz;
b = a ./bar ./baz${x} ./snens${x}y;
in b
12 changes: 12 additions & 0 deletions test_data/parser/paths/3.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
NODE_ROOT 0..6 {
NODE_PATH_WITH_INTERPOL 0..6 {
TOKEN_PATH("a/") 0..2
NODE_STRING_INTERPOL 2..6 {
TOKEN_INTERPOL_START("${") 2..4
NODE_IDENT 4..5 {
TOKEN_IDENT("b") 4..5
}
TOKEN_INTERPOL_END("}") 5..6
}
}
}
1 change: 1 addition & 0 deletions test_data/parser/paths/3.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a/${b}
Loading