diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 493a30b..7d552b8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -16,6 +16,7 @@ use either::Either; use pest::Span; mod parser; +mod tests; pub trait Node<'i> { fn as_node(&self) -> &dyn Node<'i>; @@ -34,6 +35,10 @@ pub trait Node<'i> { None } + fn as_error(&self) -> Option<&ErrorStatement<'i>> { + None + } + fn identifiers<'n>(&'n self) -> FilterWalk<'i, 'n, Identifier<'i>> { FilterWalk::new(self.as_node(), |node| node.as_identifier()) } @@ -41,6 +46,10 @@ pub trait Node<'i> { fn strings<'n>(&'n self) -> FilterWalk<'i, 'n, StringLiteral<'i>> { FilterWalk::new(self.as_node(), |node| node.as_string()) } + + fn errors<'n>(&'n self) -> FilterWalk<'i, 'n, ErrorStatement<'i>> { + FilterWalk::new(self.as_node(), |node| node.as_error()) + } } pub struct Walk<'i, 'n> { @@ -594,6 +603,10 @@ impl<'i> Node<'i> for ErrorStatement<'i> { self } + fn as_error(&self) -> Option<&ErrorStatement<'i>> { + Some(self) + } + fn children(&self) -> Vec<&dyn Node<'i>> { match self { ErrorStatement::UnknownStatement(unknown) => vec![unknown.as_node()], diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 2255563..a8ae307 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -352,77 +352,3 @@ pub fn parse(input: &str) -> Block { .unwrap(); convert_file(file_pair) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn smoke() { - // Empty - parse(""); - parse(" \r\n\t"); - - // Assignment - parse("a = 1"); - parse("a += 1"); - parse("a -= 1"); - parse("a[1] = 1"); - parse("a.b = 1"); - - // Conditional - parse("if (true) {}"); - parse("if (true) { a = 1 }"); - parse("if (true) {} else {}"); - parse("if (true) { a = 1 } else { a = 2 }"); - parse("if (true) {} else if (true) {}"); - parse("if (true) { a = 1 } else if (true) { a = 2 }"); - parse("if (true) {} else if (true) {} else {}"); - parse("if (true) { a = 1 } else if (true) { a = 2 } else { a = 3 }"); - - // Call - parse("assert(true)"); - parse("declare_args() {}"); - parse("declare_args() { a = 1 }"); - parse("template(\"foo\") {}"); - parse("template(\"foo\") { bar(target_name) }"); - - // Expressions - parse("a = 1"); - parse("a = b[1]"); - parse("a = b.c"); - parse("a = 1 + 2 * 3"); - parse("a = 1 == 2"); - parse("a = 1 <= 2"); - parse("a = true && false || !false"); - parse(r#"a = "foo\"bar\\baz""#); - parse("a = b(c)"); - parse("a = b.c"); - parse("a = {}"); - parse("a = { b = 1 }"); - parse("a = (((1)))"); - parse("a = []"); - parse("a = [1]"); - parse("a = [1, ]"); - parse("a = [1, 2]"); - parse("a = [1, 2, ]"); - - // TODO: Add more tests. - } - - #[test] - fn comments() { - parse("# comment"); - parse("# comment\n # comment\n"); - parse("a = 1 # comment"); - } - - #[test] - fn error_recovery() { - parse("a = 1 2 3"); - parse("a = \"foo\nb = 1"); - parse("declare_args() {}}"); - - // TODO: Add more tests. - } -} diff --git a/src/ast/tests.rs b/src/ast/tests.rs new file mode 100644 index 0000000..c3887fe --- /dev/null +++ b/src/ast/tests.rs @@ -0,0 +1,97 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +use super::{parse, Node}; + +fn parse_no_errors(input: &str) { + let block = parse(input); + let errors: Vec<_> = block.errors().collect(); + assert!( + errors.is_empty(), + "parse failed!\n\tinput = {:?}\n\terrors = {:?}", + input, + errors + ); +} + +#[test] +fn smoke() { + // Empty + parse_no_errors(""); + parse_no_errors(" \r\n\t"); + + // Assignment + parse_no_errors("a = 1"); + parse_no_errors("a += 1"); + parse_no_errors("a -= 1"); + parse_no_errors("a[1] = 1"); + parse_no_errors("a.b = 1"); + + // Conditional + parse_no_errors("if (true) {}"); + parse_no_errors("if (true) { a = 1 }"); + parse_no_errors("if (true) {} else {}"); + parse_no_errors("if (true) { a = 1 } else { a = 2 }"); + parse_no_errors("if (true) {} else if (true) {}"); + parse_no_errors("if (true) { a = 1 } else if (true) { a = 2 }"); + parse_no_errors("if (true) {} else if (true) {} else {}"); + parse_no_errors("if (true) { a = 1 } else if (true) { a = 2 } else { a = 3 }"); + + // Call + parse_no_errors("assert(true)"); + parse_no_errors("declare_args() {}"); + parse_no_errors("declare_args() { a = 1 }"); + parse_no_errors("template(\"foo\") {}"); + parse_no_errors("template(\"foo\") { bar(target_name) }"); + + // Expressions + parse_no_errors("a = 1"); + parse_no_errors("a = b[1]"); + parse_no_errors("a = b.c"); + parse_no_errors("a = 1 + 2 - 3"); + parse_no_errors("a = 1 == 2"); + parse_no_errors("a = 1 <= 2"); + parse_no_errors("a = true && false || !false"); + parse_no_errors(r#"a = "foo\"bar\\baz""#); + parse_no_errors("a = b(c)"); + parse_no_errors("a = b.c"); + parse_no_errors("a = {}"); + parse_no_errors("a = { b = 1 }"); + parse_no_errors("a = (((1)))"); + parse_no_errors("a = []"); + parse_no_errors("a = [1]"); + parse_no_errors("a = [1, ]"); + parse_no_errors("a = [1, 2]"); + parse_no_errors("a = [1, 2, ]"); + + // TODO: Add more tests. +} + +#[test] +fn comments() { + parse_no_errors("# comment"); + parse_no_errors("# comment\n # comment\n"); + parse_no_errors("a = 1 # comment"); +} + +#[test] +fn error_recovery() { + parse("a = 1 2 3"); + parse("a = \"foo\nb = 1"); + parse("declare_args() {}}"); + + // TODO: Add more tests. +}