Skip to content

Commit

Permalink
Use statement parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
g-r-a-n-t committed Oct 1, 2021
1 parent e23abe3 commit 8f60f58
Show file tree
Hide file tree
Showing 10 changed files with 834 additions and 15 deletions.
26 changes: 26 additions & 0 deletions crates/parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,32 @@ pub enum Import {
},
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Path {
pub names: Vec<Node<String>>,
pub trailing_delim: bool,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Use {
pub tree: Node<UseTree>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub enum UseTree {
Glob {
prefix: Node<Path>,
},
Nested {
prefix: Node<Path>,
children: Vec<Node<UseTree>>,
},
Simple {
path: Node<Path>,
rename: Option<Node<String>>,
},
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct TypeAlias {
pub name: Node<String>,
Expand Down
139 changes: 138 additions & 1 deletion crates/parser/src/grammar/module.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::contracts::parse_contract_def;
use super::types::{parse_struct_def, parse_type_alias};
use crate::ast::{Import, Module, ModuleStmt, Pragma, SimpleImportName};
use crate::ast::{Import, Module, ModuleStmt, Path, Pragma, SimpleImportName, Use, UseTree};
use crate::node::{Node, Span};
use crate::{Label, ParseFailed, ParseResult, Parser, TokenKind};

Expand Down Expand Up @@ -116,6 +116,143 @@ pub fn parse_from_import(par: &mut Parser) -> ParseResult<Node<Import>> {
todo!("parse from .. import (not supported in rest of compiler yet)")
}

/// Parse a `::` delimited path.
pub fn parse_path(par: &mut Parser) -> ParseResult<Node<Path>> {
let mut names = vec![];

let name = par.expect_with_notes(TokenKind::Name, "failed to parse path", |_| {
vec![
"Note: paths must start with a name".into(),
"Example: `foo::bar`".into(),
]
})?;

names.push(Node::new(name.text.to_string(), name.span));

loop {
if par.peek() == Some(TokenKind::ColonColon) {
let delim_tok = par.next()?;

if par.peek() == Some(TokenKind::Name) {
let name = par.next()?;

names.push(Node::new(name.text.to_string(), name.span));
} else {
let span =
names.first().expect("`names` should not be empty").span + delim_tok.span;

return Ok(Node::new(
Path {
names,
trailing_delim: true,
},
span,
));
}
} else {
let span = names.first().expect("`names` should not be empty").span
+ names.last().expect("").span;

return Ok(Node::new(
Path {
names,
trailing_delim: false,
},
span,
));
}
}
}

/// Parse a `use` statement.
/// # Panics
/// Panics if the next token isn't `use`.
pub fn parse_use(par: &mut Parser) -> ParseResult<Node<Use>> {
let use_tok = par.assert(TokenKind::Use);

let tree = parse_use_tree(par)?;
let tree_span = tree.span;

Ok(Node::new(Use { tree }, use_tok.span + tree_span))
}

/// Parse a `use` tree.
pub fn parse_use_tree(par: &mut Parser) -> ParseResult<Node<UseTree>> {
let path = parse_path(par)?;
let path_span = path.span;

if path.kind.trailing_delim {
match par.peek() {
Some(TokenKind::BraceOpen) => {
par.next()?;

let mut children = vec![];
let close_brace_span;

loop {
children.push(parse_use_tree(par)?);

match par.peek() {
Some(TokenKind::Comma) => {
par.next()?;
continue;
}
Some(TokenKind::BraceClose) => {
let tok = par.next()?;
close_brace_span = tok.span;
break;
}
_ => {
let tok = par.next()?;
par.unexpected_token_error(
tok.span,
"failed to parse `use` tree",
vec!["Note: expected a `,` or `}` token".to_string()],
);
return Err(ParseFailed);
}
}
}

Ok(Node::new(
UseTree::Nested {
prefix: path,
children,
},
close_brace_span,
))
}
Some(TokenKind::Star) => {
par.next()?;
Ok(Node::new(UseTree::Glob { prefix: path }, path_span))
}
_ => {
let tok = par.next()?;
par.unexpected_token_error(
tok.span,
"failed to parse `use` tree",
vec!["Note: expected a `*`, `{` or name token".to_string()],
);
return Err(ParseFailed);
}
}
} else {
if par.peek() == Some(TokenKind::As) {
par.next()?;

let rename_tok = par.expect(TokenKind::Name, "failed to parse `use` tree")?;
let rename = Some(Node::new(rename_tok.text.to_string(), rename_tok.span));

Ok(Node::new(
UseTree::Simple { path, rename },
path_span + rename_tok.span,
))
} else {
Ok(Node::new(UseTree::Simple { path, rename: None }, path_span))
}
}
}

/// Parse a `pragma <version-requirement>` statement.
pub fn parse_pragma(par: &mut Parser) -> ParseResult<Node<Pragma>> {
let tok = par.assert(TokenKind::Pragma);
Expand Down
7 changes: 5 additions & 2 deletions crates/parser/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ pub enum TokenKind {
Not,
#[token("or")]
Or,
#[token("let")]
Let,
#[token("use")]
Use,
// Symbols
#[token("(")]
ParenOpen,
Expand Down Expand Up @@ -199,8 +203,6 @@ pub enum TokenKind {
GtGtEq,
#[token("->")]
Arrow,
#[token("let")]
Let,
}

impl TokenKind {
Expand Down Expand Up @@ -258,6 +260,7 @@ impl TokenKind {
In => "in",
Not => "not",
Or => "or",
Use => "use",
ParenOpen => "(",
ParenClose => ")",
BracketOpen => "[",
Expand Down
37 changes: 25 additions & 12 deletions crates/parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct Parser<'a> {
/// generic type parameter list (eg. `Map<u256, Map<u256, address>>`).
buffered: Vec<Token<'a>>,

paren_stack: Vec<Span>,
enclosure_stack: Vec<Span>,
indent_stack: Vec<BlockIndent<'a>>,
indent_style: Option<char>,

Expand All @@ -47,7 +47,7 @@ impl<'a> Parser<'a> {
Parser {
lexer: Lexer::new(content),
buffered: vec![],
paren_stack: vec![],
enclosure_stack: vec![],
indent_stack: vec![BlockIndent {
context_span: Span::zero(),
context_name: "module".into(),
Expand All @@ -70,13 +70,26 @@ impl<'a> Parser<'a> {
// TODO: allow newlines inside square brackets
// TODO: allow newlines inside angle brackets?
// eg `fn f(x: map\n <\n u8\n, ...`
if !self.paren_stack.is_empty() {
if !self.enclosure_stack.is_empty() {
self.eat_newlines();
}
if let Some(tok) = self.next_raw() {
if tok.kind == TokenKind::ParenOpen {
self.paren_stack.push(tok.span);
} else if tok.kind == TokenKind::ParenClose && self.paren_stack.pop().is_none() {
if [
TokenKind::ParenOpen,
TokenKind::BraceOpen,
TokenKind::BracketOpen,
]
.contains(&tok.kind)
{
self.enclosure_stack.push(tok.span);
} else if [
TokenKind::ParenClose,
TokenKind::BraceClose,
TokenKind::BracketClose,
]
.contains(&tok.kind)
&& self.enclosure_stack.pop().is_none()
{
self.error(tok.span, "Unmatched right parenthesis");
if self.peek_raw() == Some(TokenKind::ParenClose) {
// another unmatched closing paren; fail.
Expand All @@ -100,7 +113,7 @@ impl<'a> Parser<'a> {
/// Take a peek at the next token kind without consuming it, or return an
/// error if we've reached the end of the file.
pub fn peek_or_err(&mut self) -> ParseResult<TokenKind> {
if !self.paren_stack.is_empty() {
if !self.enclosure_stack.is_empty() {
self.eat_newlines();
}
if let Some(tk) = self.peek_raw() {
Expand All @@ -115,7 +128,7 @@ impl<'a> Parser<'a> {
/// Take a peek at the next token kind. Returns `None` if we've reached the
/// end of the file.
pub fn peek(&mut self) -> Option<TokenKind> {
if !self.paren_stack.is_empty() {
if !self.enclosure_stack.is_empty() {
self.eat_newlines();
}
self.peek_raw()
Expand Down Expand Up @@ -267,7 +280,7 @@ impl<'a> Parser<'a> {
/// # Panics
/// Panics if called while the parser is inside a set of parentheses.
pub fn enter_block(&mut self, context_span: Span, context_name: &str) -> ParseResult<()> {
assert!(self.paren_stack.is_empty());
assert!(self.enclosure_stack.is_empty());

let colon = if self.peek_raw() == Some(TokenKind::Colon) {
self.next_raw()
Expand Down Expand Up @@ -337,7 +350,7 @@ impl<'a> Parser<'a> {

fn handle_newline_indent(&mut self, context_name: &str) -> ParseResult<()> {
assert!(
self.paren_stack.is_empty(),
self.enclosure_stack.is_empty(),
"Parser::handle_newline_indent called within parens"
);

Expand Down Expand Up @@ -462,7 +475,7 @@ impl<'a, 'b> BTParser<'a, 'b> {
let parser = Parser {
lexer: snapshot.lexer.clone(),
buffered: snapshot.buffered.clone(),
paren_stack: snapshot.paren_stack.clone(),
enclosure_stack: snapshot.enclosure_stack.clone(),
indent_stack: snapshot.indent_stack.clone(),
indent_style: snapshot.indent_style,
diagnostics: Vec::new(),
Expand All @@ -473,7 +486,7 @@ impl<'a, 'b> BTParser<'a, 'b> {
pub fn accept(self) {
self.snapshot.lexer = self.parser.lexer;
self.snapshot.buffered = self.parser.buffered;
self.snapshot.paren_stack = self.parser.paren_stack;
self.snapshot.enclosure_stack = self.parser.enclosure_stack;
self.snapshot.indent_stack = self.parser.indent_stack;
self.snapshot.indent_style = self.parser.indent_style;
self.snapshot.diagnostics.extend(self.parser.diagnostics);
Expand Down
10 changes: 10 additions & 0 deletions crates/parser/tests/cases/parse_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ test_parse! { pragma2, module::parse_pragma, "pragma 0.1.0-alpha" }
test_parse! { pragma3, module::parse_pragma, "pragma >= 1.2, < 1.5" }

test_parse! { import_simple, module::parse_simple_import, "import foo as bar, baz, bing as bop" }
test_parse! { use_simple1, module::parse_use, "use foo::bar" }
test_parse! { use_simple2, module::parse_use, "use foo::bar as baz" }
test_parse! { use_glob, module::parse_use, "use foo::bar::*" }
test_parse! { use_nested1, module::parse_use, "use foo::bar::{bing::*, bang::big, bass as fish, bong::{hello as hi, goodbye}}" }
test_parse! { use_nested2, module::parse_use, r#"use std::bar::{
bing::*,
bad::{food as burger, barge::*, bill::bob::{jkl::*}},
evm as mve
}"#
}
test_parse! { struct_def, types::parse_struct_def, r#"struct S:
x: address
pub y: u8
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
source: crates/parser/tests/cases/parse_ast.rs
expression: "ast_string(stringify!(use_glob), module::parse_use, \"use foo::bar::*\")"

---
Node(
kind: Use(
tree: Node(
kind: Glob(
prefix: Node(
kind: Path(
names: [
Node(
kind: "foo",
span: Span(
start: 4,
end: 7,
),
),
Node(
kind: "bar",
span: Span(
start: 9,
end: 12,
),
),
],
trailing_delim: true,
),
span: Span(
start: 4,
end: 14,
),
),
),
span: Span(
start: 4,
end: 14,
),
),
),
span: Span(
start: 0,
end: 14,
),
)
Loading

0 comments on commit 8f60f58

Please sign in to comment.