Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.
Merged
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ jobs:
run: |
cargo fmt --all -- --check
cargo clippy

- name: test
run: ./meta/test --all -- --skip third_party
29 changes: 19 additions & 10 deletions bin/snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use php_parser_rs::{Lexer, Parser};
use php_parser_rs::prelude::{Lexer, Parser};
use std::env;
use std::fs::read_dir;
use std::path::PathBuf;

static PARSER: Parser = Parser::new();
static LEXER: Lexer = Lexer::new();

fn main() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut entries = read_dir(manifest.join("tests"))
Expand All @@ -14,13 +17,10 @@ fn main() {

entries.sort();

let mut content = String::new();
content.push_str("/// this file is auto-generated by the build script.\n");
content.push_str("/// you should never manually change it.\n\n\n");

for entry in entries {
let code_filename = entry.join("code.php");
let ast_filename = entry.join("ast.txt");
let tokens_filename = entry.join("tokens.txt");
let lexer_error_filename = entry.join("lexer-error.txt");
let parser_error_filename = entry.join("parser-error.txt");

Expand All @@ -32,6 +32,10 @@ fn main() {
std::fs::remove_file(&ast_filename).unwrap();
}

if tokens_filename.exists() {
std::fs::remove_file(&tokens_filename).unwrap();
}

if lexer_error_filename.exists() {
std::fs::remove_file(&lexer_error_filename).unwrap();
}
Expand All @@ -41,13 +45,17 @@ fn main() {
}

let code = std::fs::read_to_string(&code_filename).unwrap();
let mut lexer = Lexer::new(None);
let tokens = lexer.tokenize(code.as_bytes());
let tokens = LEXER.tokenize(code.as_bytes());

match tokens {
Ok(tokens) => {
let mut parser = Parser::new(None);
let ast = parser.parse(tokens);
std::fs::write(tokens_filename, format!("{:#?}\n", tokens)).unwrap();
println!(
"✅ generated `tokens.txt` for `{}`",
entry.to_string_lossy()
);

let ast = PARSER.parse(tokens);
match ast {
Ok(ast) => {
std::fs::write(ast_filename, format!("{:#?}\n", ast)).unwrap();
Expand All @@ -67,7 +75,8 @@ fn main() {
}
}
Err(error) => {
std::fs::write(lexer_error_filename, format!("{:?}\n", error)).unwrap();
std::fs::write(lexer_error_filename, format!("{:?} -> {}\n", error, error))
.unwrap();
println!(
"✅ generated `lexer-error.txt` for `{}`",
entry.to_string_lossy()
Expand Down
76 changes: 49 additions & 27 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ use std::fs::read_dir;
use std::path::PathBuf;

fn main() {
println!("cargo:rerun-if-changed=tests");
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let tests = manifest.join("tests");
let snapshot = manifest.join("bin").join("snapshot.rs");

println!("cargo:rerun-if-changed={}", tests.to_string_lossy());
println!("cargo:rerun-if-changed={}", snapshot.to_string_lossy());
println!("cargo:rerun-if-env-changed=BUILD_INTEGRATION_TESTS");

if env::var("BUILD_INTEGRATION_TESTS").unwrap_or_else(|_| "0".to_string()) == "0" {
return;
}

let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut entries = read_dir(manifest.join("tests"))
let mut entries = read_dir(tests)
.unwrap()
.flatten()
.map(|entry| entry.path())
Expand All @@ -21,11 +26,16 @@ fn main() {

let mut content = String::new();
content.push_str("/// this file is auto-generated by the build script.\n");
content.push_str("/// you should never manually change it.\n\n\n");
content.push_str("/// you should never manually change it.\n\n");
content.push_str("use php_parser_rs::prelude::{Lexer, Parser};\n");
content.push_str("use pretty_assertions::assert_str_eq;\n\n");
content.push_str("static PARSER: Parser = Parser::new();\n");
content.push_str("static LEXER: Lexer = Lexer::new();\n\n");
Comment on lines +32 to +33
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can now be sent across threads safely, meaning we can have multiple threads parsing different files using the same parser, previously that was not possible.


for entry in entries {
let code_filename = entry.join("code.php");
let ast_filename = entry.join("ast.txt");
let tokens_filename = entry.join("tokens.txt");
let lexer_error_filename = entry.join("lexer-error.txt");
let parser_error_filename = entry.join("parser-error.txt");

Expand All @@ -45,7 +55,12 @@ fn main() {
entry.to_string_lossy()
);

content.push_str(&build_success_test(entry, code_filename, ast_filename))
content.push_str(&build_success_test(
entry,
code_filename,
ast_filename,
tokens_filename,
))
} else if lexer_error_filename.exists() {
assert!(
!parser_error_filename.exists(),
Expand All @@ -69,6 +84,7 @@ fn main() {
entry,
code_filename,
parser_error_filename,
tokens_filename,
))
}
}
Expand All @@ -77,31 +93,37 @@ fn main() {
std::fs::write(dest, content).expect("failed to write to file");
}

fn build_success_test(entry: PathBuf, code_filename: PathBuf, ast_filename: PathBuf) -> String {
fn build_success_test(
entry: PathBuf,
code_filename: PathBuf,
ast_filename: PathBuf,
tokens_filename: PathBuf,
) -> String {
format!(
r#"#[test]
fn test_success_{}() {{
use php_parser_rs::{{Lexer, Parser}};
use pretty_assertions::assert_str_eq;

let code_filename = "{}";
let ast_filename = "{}";
let tokens_filename = "{}";

let code = std::fs::read_to_string(&code_filename).unwrap();
let expected_ast = std::fs::read_to_string(&ast_filename).unwrap();
let expected_tokens = std::fs::read_to_string(&tokens_filename).unwrap();

let tokens = LEXER.tokenize(code.as_bytes()).unwrap();

assert_str_eq!(expected_tokens.trim(), format!("{{:#?}}", tokens));

let mut lexer = Lexer::new(None);
let tokens = lexer.tokenize(code.as_bytes()).unwrap();
let mut parser = Parser::new(None);
let ast = parser.parse(tokens).unwrap();
let ast = PARSER.parse(tokens).unwrap();

assert_str_eq!(expected_ast.trim(), format!("{{:#?}}", ast));
}}

"#,
entry.file_name().unwrap().to_string_lossy(),
code_filename.to_string_lossy(),
ast_filename.to_string_lossy()
ast_filename.to_string_lossy(),
tokens_filename.to_string_lossy(),
)
}

Expand All @@ -113,19 +135,18 @@ fn build_lexer_error_test(
format!(
r#"#[test]
fn test_lexer_error_{}() {{
use php_parser_rs::Lexer;
use pretty_assertions::assert_str_eq;

let code_filename = "{}";
let lexer_error_filename = "{}";

let code = std::fs::read_to_string(&code_filename).unwrap();
let expected_error = std::fs::read_to_string(&lexer_error_filename).unwrap();

let mut lexer = Lexer::new(None);
let error = lexer.tokenize(code.as_bytes()).err().unwrap();
let error = LEXER.tokenize(code.as_bytes()).err().unwrap();

assert_str_eq!(expected_error.trim(), format!("{{:?}}", error));
assert_str_eq!(
expected_error.trim(),
format!("{{:?}} -> {{}}", error, error.to_string())
);
}}

"#,
Expand All @@ -139,24 +160,24 @@ fn build_parser_error_test(
entry: PathBuf,
code_filename: PathBuf,
parser_error_filename: PathBuf,
tokens_filename: PathBuf,
) -> String {
format!(
r#"#[test]
fn test_paser_error_{}() {{
use php_parser_rs::{{Lexer, Parser}};
use pretty_assertions::assert_str_eq;

let code_filename = "{}";
let tokens_filename = "{}";
let parser_error_filename = "{}";

let code = std::fs::read_to_string(&code_filename).unwrap();
let expected_tokens = std::fs::read_to_string(&tokens_filename).unwrap();
let expected_error = std::fs::read_to_string(&parser_error_filename).unwrap();

let mut lexer = Lexer::new(None);
let tokens = lexer.tokenize(code.as_bytes()).unwrap();
let tokens = LEXER.tokenize(code.as_bytes()).unwrap();

assert_str_eq!(expected_tokens.trim(), format!("{{:#?}}", tokens));

let mut parser = Parser::new(None);
let error = parser.parse(tokens).err().unwrap();
let error = PARSER.parse(tokens).err().unwrap();

assert_str_eq!(
expected_error.trim(),
Expand All @@ -167,6 +188,7 @@ fn test_paser_error_{}() {{
"#,
entry.file_name().unwrap().to_string_lossy(),
code_filename.to_string_lossy(),
tokens_filename.to_string_lossy(),
parser_error_filename.to_string_lossy()
)
}
64 changes: 64 additions & 0 deletions src/lexer/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::fmt::Display;

use crate::lexer::token::Span;

pub type SyntaxResult<T> = Result<T, SyntaxError>;

#[derive(Debug, Eq, PartialEq)]
pub enum SyntaxError {
UnexpectedEndOfFile(Span),
UnexpectedError(Span),
UnexpectedCharacter(u8, Span),
InvalidHaltCompiler(Span),
InvalidOctalEscape(Span),
InvalidOctalLiteral(Span),
InvalidUnicodeEscape(Span),
UnpredictableState(Span),
}

impl Display for SyntaxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedEndOfFile(span) => write!(
f,
"Syntax Error: unexpected end of file on line {} column {}",
span.0, span.1
),
Self::UnexpectedError(span) => write!(
f,
"Syntax Error: unexpected error on line {} column {}",
span.0, span.1
),
Self::UnexpectedCharacter(char, span) => write!(
f,
"Syntax Error: unexpected character `{:?}` on line {} column {}",
*char as char, span.0, span.1
),
Self::InvalidHaltCompiler(span) => write!(
f,
"Syntax Error: invalid halt compiler on line {} column {}",
span.0, span.1
),
Self::InvalidOctalEscape(span) => write!(
f,
"Syntax Error: invalid octal escape on line {} column {}",
span.0, span.1
),
Self::InvalidOctalLiteral(span) => write!(
f,
"Syntax Error: invalid octal literal on line {} column {}",
span.0, span.1
),
Self::InvalidUnicodeEscape(span) => write!(
f,
"Syntax Error: invalid unicode escape on line {} column {}",
span.0, span.1
),
Self::UnpredictableState(span) => write!(
f,
"Syntax Error: Reached an unpredictable state on line {} column {}",
span.0, span.1
),
}
}
}
Loading