Skip to main content

ocelot_parser/
parser.rs

1use crate::lexer::lex::lex;
2use crate::lexer::token::Token;
3use crate::lexer::token_type::TokenType;
4use ocelot_ast::expression::Expression;
5use ocelot_ast::expression_kind::ExpressionKind;
6use ocelot_ast::identifier_expression::IdentifierExpression;
7use ocelot_ast::item::Item;
8use ocelot_ast::item_kind::ItemKind;
9use ocelot_ast::println_statement::PrintlnStatement;
10use ocelot_ast::script::Script;
11use ocelot_ast::statement::Statement;
12use ocelot_ast::statement_kind::StatementKind;
13use ocelot_ast::string_literal_expression::StringLiteralExpression;
14use ocelot_ast::test_item::TestItem;
15use ocelot_base::compilation_context::CompilationContext;
16use ocelot_base::compilation_stage::CompilationStage;
17use ocelot_base::diagnostic_level::DiagnosticLevel;
18use ocelot_base::error::ErrorKind;
19use ocelot_base::error::OcelotError;
20use ocelot_base::result::OcelotResult;
21use ocelot_base::shared_string::SharedString;
22use ocelot_base::source_annotation::SourceAnnotation;
23use ocelot_base::source_diagnostic::SourceDiagnostic;
24use ocelot_base::source_excerpt::SourceExcerpt;
25use ocelot_base::source_file::SourceFile;
26use ocelot_base::span::Span;
27
28/// Stateful parser context for one source file.
29pub struct Parser<'a> {
30    source_file: &'a SourceFile,
31    compilation_context: &'a mut CompilationContext,
32    tokens: Vec<Token>,
33    position: usize,
34}
35
36impl<'a> Parser<'a> {
37    /// Creates a parser for one source file.
38    pub fn new(
39        source_file: &'a SourceFile,
40        compilation_context: &'a mut CompilationContext,
41    ) -> Self {
42        let tokens = lex(source_file, compilation_context);
43        Self {
44            source_file,
45            compilation_context,
46            tokens,
47            position: 0,
48        }
49    }
50
51    /// Parses the source file into a script AST.
52    pub fn parse_script(&mut self) -> OcelotResult<Option<Script>> {
53        if self.compilation_context.has_errors() {
54            return Ok(None);
55        }
56
57        let mut items = Vec::new();
58
59        while !self.at(TokenType::EndOfFile) {
60            match self.parse_item() {
61                Ok(item) => items.push(item),
62                Err(error) if is_parser_compilation_error(&error) => return Ok(None),
63                Err(error) => return Err(error),
64            }
65        }
66
67        Ok(Some(Script::new(
68            items,
69            Span::new(0, self.source_file.source().len()),
70        )))
71    }
72
73    fn parse_item(&mut self) -> OcelotResult<Item> {
74        match self.current().token_type {
75            TokenType::Test => self.parse_test_item(),
76            _ => Ok({
77                let statement = self.parse_statement()?;
78                let span = statement.span.clone();
79                Item::new(ItemKind::Statement(statement), span)
80            }),
81        }
82    }
83
84    fn parse_test_item(&mut self) -> OcelotResult<Item> {
85        let test_token = self.expect(TokenType::Test, "expected `test` item")?;
86        let name_token = self.expect(TokenType::String, "expected test name string")?;
87        let name_literal = self.source_text(&name_token.span);
88        let name = SharedString::from(&name_literal[1..name_literal.len() - 1]);
89        self.expect(TokenType::LeftBrace, "expected `{` after test name")?;
90
91        let mut body = Vec::new();
92        while !self.at(TokenType::RightBrace) {
93            if self.at(TokenType::EndOfFile) {
94                return self.emit_fatal_diagnostic(
95                    "expected `}` to close test body",
96                    self.current().span.clone(),
97                    "test body ends here",
98                );
99            }
100            body.push(self.parse_statement()?);
101        }
102
103        let right_brace = self.expect(TokenType::RightBrace, "expected `}` after test body")?;
104        let span = Span::new(test_token.span.start(), right_brace.span.end());
105        Ok(Item::new(
106            ItemKind::Test(TestItem::new(name, body, span.clone())),
107            span,
108        ))
109    }
110
111    fn parse_statement(&mut self) -> OcelotResult<Statement> {
112        let start = self.current().span.start();
113        let identifier = self.expect(TokenType::Identifier, "expected statement")?;
114        let name = self.source_text(&identifier.span);
115
116        if name != "println" {
117            return self.emit_fatal_diagnostic(
118                "expected `println` statement",
119                identifier.span,
120                "statement is not supported",
121            );
122        }
123
124        self.expect(TokenType::LeftParen, "expected `(` after `println`")?;
125        if self.at(TokenType::RightParen) {
126            return self.emit_fatal_diagnostic(
127                "type error: `println` expects exactly one argument",
128                self.current().span.clone(),
129                "missing argument",
130            );
131        }
132        let argument = self.parse_expression()?;
133        self.expect(TokenType::RightParen, "expected `)` after argument")?;
134        let semicolon = self.expect(TokenType::Semicolon, "expected `;` after statement")?;
135        let statement_span = Span::new(start, semicolon.span.end());
136
137        Ok(Statement::new(
138            StatementKind::Println(PrintlnStatement::new(argument)),
139            statement_span,
140        ))
141    }
142
143    fn parse_expression(&mut self) -> OcelotResult<Expression> {
144        let token = self.current().clone();
145
146        match token.token_type {
147            TokenType::String => {
148                self.position += 1;
149                let literal = self.source_text(&token.span);
150                let value = literal[1..literal.len() - 1].to_owned();
151                Ok(Expression::new(
152                    ExpressionKind::StringLiteral(StringLiteralExpression::new(value)),
153                    token.span,
154                ))
155            }
156            TokenType::Identifier => {
157                self.position += 1;
158                Ok(Expression::new(
159                    ExpressionKind::Identifier(IdentifierExpression::new(
160                        self.source_text(&token.span),
161                    )),
162                    token.span,
163                ))
164            }
165            TokenType::Unexpected => {
166                self.emit_fatal_diagnostic("unexpected token", token.span, "unexpected character")
167            }
168            _ => self.emit_fatal_diagnostic(
169                "expected expression",
170                token.span,
171                "expression expected here",
172            ),
173        }
174    }
175
176    fn expect(&mut self, token_type: TokenType, message: &str) -> OcelotResult<Token> {
177        let token = self.current().clone();
178
179        if token.token_type != token_type {
180            return self.emit_fatal_diagnostic(message, token.span, "found here");
181        }
182
183        self.position += 1;
184        Ok(token)
185    }
186
187    fn at(&self, token_type: TokenType) -> bool {
188        self.current().token_type == token_type
189    }
190
191    fn current(&self) -> &Token {
192        &self.tokens[self.position]
193    }
194
195    fn source_text(&self, span: &Span) -> &str {
196        &self.source_file.source()[span.start()..span.end()]
197    }
198
199    fn emit_fatal_diagnostic<T>(
200        &mut self,
201        message: impl Into<SharedString>,
202        span: Span,
203        annotation: impl Into<SharedString>,
204    ) -> OcelotResult<T> {
205        self.compilation_context
206            .add_diagnostic(self.source_diagnostic(message, span, annotation));
207        Err(OcelotError::compilation_error(CompilationStage::Parser))
208    }
209
210    fn source_diagnostic(
211        &self,
212        message: impl Into<SharedString>,
213        span: Span,
214        annotation: impl Into<SharedString>,
215    ) -> SourceDiagnostic {
216        let message = message.into();
217        let annotation = annotation.into();
218        let (line_number, line_start, line_end) = self.line_bounds(span.start());
219        let source_line = &self.source_file.source()[line_start..line_end];
220        let relative_start = span.start().saturating_sub(line_start);
221        let relative_end = span.end().saturating_sub(line_start);
222
223        SourceDiagnostic::new(DiagnosticLevel::Error, &self.source_file.path, message).with_excerpt(
224            SourceExcerpt::new(&self.source_file.path, line_number, source_line).with_annotation(
225                SourceAnnotation::new(Span::new(relative_start, relative_end), annotation),
226            ),
227        )
228    }
229
230    fn line_bounds(&self, index: usize) -> (usize, usize, usize) {
231        let source = self.source_file.source();
232        let line_start = source[..index].rfind('\n').map_or(0, |offset| offset + 1);
233        let line_end = source[index..]
234            .find('\n')
235            .map_or(source.len(), |offset| index + offset);
236        let line_number = source[..line_start]
237            .bytes()
238            .filter(|byte| *byte == b'\n')
239            .count()
240            + 1;
241
242        (line_number, line_start, line_end)
243    }
244}
245
246fn is_parser_compilation_error(error: &OcelotError) -> bool {
247    matches!(
248        error.kind(),
249        ErrorKind::CompilationError(CompilationStage::Parser)
250    )
251}