diff --git a/crates/oxide_parser/src/error.rs b/crates/oxide_parser/src/error.rs new file mode 100644 index 00000000..52acb081 --- /dev/null +++ b/crates/oxide_parser/src/error.rs @@ -0,0 +1,30 @@ +use std::fmt; + +#[derive(Debug, Clone)] +pub struct OxideError { + pub message: String, + pub span: (usize, usize), +} + +impl fmt::Display for OxideError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} at {:?}", self.message, self.span) + } +} + +impl std::error::Error for OxideError {} + +impl From for OxideError { + fn from(diag: oxc_diagnostics::OxcDiagnostic) -> Self { + let span = diag + .labels + .clone() + .and_then(|labels| labels.first().map(|l| (l.offset() as usize, (l.offset() + l.len()) as usize))) + .unwrap_or((0, 0)); + + OxideError { + message: diag.message.to_string(), + span, + } + } +} diff --git a/crates/oxide_parser/src/lib.rs b/crates/oxide_parser/src/lib.rs index 8ece1ad3..89f347c3 100644 --- a/crates/oxide_parser/src/lib.rs +++ b/crates/oxide_parser/src/lib.rs @@ -1 +1,29 @@ #![doc = "OxideJS - JavaScript parser (oxc_parser re-export)"] + +mod error; + +pub use error::OxideError; +pub use oxc_allocator::Allocator; +pub use oxc_ast::ast::*; +pub use oxc_span::Span; + +pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result, Vec> { + use oxc_parser::Parser; + use oxc_span::SourceType; + + let source_type = SourceType::default(); + let ret = Parser::new(allocator, source, source_type).parse(); + + if ret.panicked { + return Err(vec![OxideError { + message: "Parser panicked: unrecoverable syntax error".to_string(), + span: (0, 0), + }]); + } + + if !ret.errors.is_empty() { + return Err(ret.errors.into_iter().map(OxideError::from).collect()); + } + + Ok(ret.program) +} diff --git a/crates/oxide_parser/tests/parse_smoke.rs b/crates/oxide_parser/tests/parse_smoke.rs new file mode 100644 index 00000000..47cc7006 --- /dev/null +++ b/crates/oxide_parser/tests/parse_smoke.rs @@ -0,0 +1,54 @@ +use oxide_parser::Allocator; + +#[test] +fn parse_simple_expression() { + let allocator = Allocator::default(); + let result = oxide_parser::parse(&allocator, "1 + 2"); + assert!(result.is_ok(), "1+2 should parse successfully"); +} + +#[test] +fn parse_empty_string() { + let allocator = Allocator::default(); + let result = oxide_parser::parse(&allocator, ""); + assert!(result.is_ok(), "empty string should parse"); + let program = result.unwrap(); + assert!(program.body.is_empty(), "empty program should have empty body"); +} + +#[test] +fn parse_function_declaration() { + let allocator = Allocator::default(); + let result = oxide_parser::parse(&allocator, "function foo() { return 42; }"); + assert!(result.is_ok(), "function declaration should parse"); + let program = result.unwrap(); + assert!(!program.body.is_empty(), "program with function should not be empty"); +} + +#[test] +fn parse_syntax_error() { + let allocator = Allocator::default(); + let result = oxide_parser::parse(&allocator, "function("); + assert!(result.is_err(), "syntax error should return Err"); + let errors = result.unwrap_err(); + assert!(!errors.is_empty(), "syntax error should produce at least one error"); +} + +#[test] +fn parse_variable_declaration() { + let allocator = Allocator::default(); + let result = oxide_parser::parse(&allocator, "var x = 42;"); + assert!(result.is_ok(), "variable declaration should parse"); +} + +#[test] +fn parse_multiple_statements() { + let allocator = Allocator::default(); + let result = oxide_parser::parse( + &allocator, + "var a = 1; var b = 2; var c = a + b;", + ); + assert!(result.is_ok(), "multiple statements should parse"); + let program = result.unwrap(); + assert_eq!(program.body.len(), 3, "should have 3 statements"); +}