1use crate::{kind::HtmlSyntaxKind, language::HtmlLanguage, lexer::HtmlLexer};
2use oak_core::{
3 GreenNode, OakError,
4 parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
5 source::{Source, TextEdit},
6};
7
8pub(crate) type State<'a, S> = ParserState<'a, HtmlLanguage, S>;
9
10pub struct HtmlParser {
11 pub(crate) _config: HtmlLanguage,
12}
13
14impl HtmlParser {
15 pub fn new(config: HtmlLanguage) -> Self {
16 Self { _config: config }
17 }
18
19 pub(crate) fn parse_root_internal<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<&'a GreenNode<'a, HtmlLanguage>, OakError> {
20 let checkpoint = state.checkpoint();
21
22 while state.not_at_end() {
23 match state.peek_kind() {
24 Some(HtmlSyntaxKind::TagOpen) => self.parse_tag(state)?,
25 Some(HtmlSyntaxKind::Doctype) => {
26 state.bump();
27 }
28 Some(HtmlSyntaxKind::Comment) => {
29 state.bump();
30 }
31 _ => {
32 state.bump();
33 }
34 }
35 }
36
37 Ok(state.finish_at(checkpoint, HtmlSyntaxKind::Document.into()))
38 }
39
40 fn parse_tag<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
41 use crate::kind::HtmlSyntaxKind::*;
42 let cp = state.checkpoint();
43 state.expect(TagOpen).ok();
44 state.expect(TagName).ok();
45
46 while state.not_at_end() && !matches!(state.peek_kind(), Some(TagClose) | Some(TagSelfClose)) {
47 if state.at(AttributeName) {
48 let _attr_cp = state.checkpoint();
49 state.bump(); if state.eat(Equal) {
51 state.eat(Quote);
52 state.eat(AttributeValue);
53 state.eat(Quote);
54 }
55 }
57 else {
58 state.advance();
59 }
60 }
61
62 if state.eat(TagSelfClose) {
63 }
65 else if state.eat(TagClose) {
66 while state.not_at_end() && !state.at(TagSlashOpen) {
69 if state.at(TagOpen) {
70 self.parse_tag(state)?;
71 }
72 else {
73 state.advance();
74 }
75 }
76 if state.eat(TagSlashOpen) {
77 state.eat(TagName);
78 state.expect(TagClose).ok();
79 }
80 }
81
82 state.finish_at(cp, HtmlSyntaxKind::Element.into());
83 Ok(())
84 }
85}
86
87impl Parser<HtmlLanguage> for HtmlParser {
88 fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<HtmlLanguage>) -> ParseOutput<'a, HtmlLanguage> {
89 let lexer = HtmlLexer::new(&self._config);
90 parse_with_lexer(&lexer, text, edits, cache, |state| self.parse_root_internal(state))
91 }
92}