ezno_parser/statements/
mod.rs

1mod for_statement;
2mod if_statement;
3mod switch_statement;
4mod try_catch_statement;
5mod while_statement;
6
7use crate::{
8	declarations::variable::{declarations_to_string, VariableDeclarationItem},
9	derive_ASTNode,
10	tokens::token_as_identifier,
11	ParseError, ParseErrors,
12};
13use derive_enum_from_into::{EnumFrom, EnumTryInto};
14use derive_partial_eq_extras::PartialEqExtras;
15use get_field_by_type::GetFieldByType;
16use std::fmt::Debug;
17
18use super::{
19	expressions::MultipleExpression, ASTNode, Block, Expression, ParseOptions, ParseResult, Span,
20	TSXKeyword, TSXToken, Token, TokenReader,
21};
22use crate::errors::parse_lexing_error;
23pub use for_statement::{ForLoopCondition, ForLoopStatement, ForLoopStatementInitialiser};
24pub use if_statement::*;
25pub use switch_statement::{SwitchBranch, SwitchStatement};
26pub use try_catch_statement::TryCatchStatement;
27use visitable_derive::Visitable;
28pub use while_statement::{DoWhileStatement, WhileStatement};
29
30/// A statement. See [Declaration]s and [StatementAndDeclaration] for more
31#[apply(derive_ASTNode)]
32#[derive(Debug, Clone, Visitable, EnumFrom, EnumTryInto, PartialEqExtras, GetFieldByType)]
33#[get_field_by_type_target(Span)]
34#[try_into_references(&, &mut)]
35#[partial_eq_ignore_types(Span)]
36pub enum Statement {
37	Expression(MultipleExpression),
38	/// { ... } statement
39	Block(Block),
40	Debugger(Span),
41	// Loops and "condition-aries"
42	If(IfStatement),
43	ForLoop(ForLoopStatement),
44	Switch(SwitchStatement),
45	WhileLoop(WhileStatement),
46	DoWhileLoop(DoWhileStatement),
47	TryCatch(TryCatchStatement),
48	// Control flow
49	Return(ReturnStatement),
50	// TODO maybe an actual label struct instead of `Option<String>`
51	Continue(Option<String>, Span),
52	// TODO maybe an actual label struct instead of `Option<String>`
53	Break(Option<String>, Span),
54	/// e.g `throw ...`
55	Throw(ThrowStatement),
56	// Comments
57	Comment(String, Span),
58	MultiLineComment(String, Span),
59	Labelled {
60		position: Span,
61		name: String,
62		statement: Box<Statement>,
63	},
64	VarVariable(VarVariableStatement),
65	Empty(Span),
66	/// Lol
67	AestheticSemiColon(Span),
68}
69
70#[apply(derive_ASTNode)]
71#[derive(Debug, Clone, Visitable, PartialEqExtras, GetFieldByType)]
72#[get_field_by_type_target(Span)]
73pub struct ReturnStatement(pub Option<MultipleExpression>, pub Span);
74
75#[apply(derive_ASTNode)]
76#[derive(Debug, Clone, Visitable, PartialEqExtras, GetFieldByType)]
77#[get_field_by_type_target(Span)]
78pub struct ThrowStatement(pub Box<MultipleExpression>, pub Span);
79
80impl Eq for Statement {}
81
82impl ASTNode for Statement {
83	fn get_position(&self) -> Span {
84		*get_field_by_type::GetFieldByType::get(self)
85	}
86
87	fn from_reader(
88		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
89		state: &mut crate::ParsingState,
90		options: &ParseOptions,
91	) -> ParseResult<Self> {
92		// Labeled statements
93		if let Some(Token(TSXToken::Colon, _)) = reader.peek_n(1) {
94			let (name, label_name_pos) = token_as_identifier(reader.next().unwrap(), "label name")?;
95			let _colon = reader.next().unwrap();
96			let statement = Statement::from_reader(reader, state, options).map(Box::new)?;
97			if statement.requires_semi_colon() {
98				let _ = crate::expect_semi_colon(
99					reader,
100					&state.line_starts,
101					statement.get_position().start,
102					options,
103				)?;
104			}
105			// TODO statement.can_be_labelled()
106			let position = label_name_pos.union(statement.get_position());
107			return Ok(Statement::Labelled { name, statement, position });
108		}
109
110		let Token(token, _s) = &reader.peek().ok_or_else(parse_lexing_error)?;
111
112		match token {
113			TSXToken::Keyword(TSXKeyword::Var) => {
114				let stmt = VarVariableStatement::from_reader(reader, state, options)?;
115				Ok(Statement::VarVariable(stmt))
116			}
117			TSXToken::Keyword(TSXKeyword::Throw) => {
118				let Token(_, start) = reader.next().unwrap();
119				let expression = MultipleExpression::from_reader(reader, state, options)?;
120				let position = start.union(expression.get_position());
121				Ok(Statement::Throw(ThrowStatement(Box::new(expression), position)))
122			}
123			TSXToken::Keyword(TSXKeyword::If) => {
124				IfStatement::from_reader(reader, state, options).map(Into::into)
125			}
126			TSXToken::Keyword(TSXKeyword::For) => {
127				ForLoopStatement::from_reader(reader, state, options).map(Into::into)
128			}
129			TSXToken::Keyword(TSXKeyword::Switch) => {
130				SwitchStatement::from_reader(reader, state, options).map(Into::into)
131			}
132			TSXToken::Keyword(TSXKeyword::While) => {
133				WhileStatement::from_reader(reader, state, options).map(Into::into)
134			}
135			TSXToken::Keyword(TSXKeyword::Do) => {
136				DoWhileStatement::from_reader(reader, state, options).map(Into::into)
137			}
138			TSXToken::Keyword(TSXKeyword::Try) => {
139				TryCatchStatement::from_reader(reader, state, options).map(Into::into)
140			}
141			TSXToken::OpenBrace => Block::from_reader(reader, state, options).map(Statement::Block),
142			TSXToken::Keyword(TSXKeyword::Debugger) => {
143				Ok(Statement::Debugger(reader.next().unwrap().get_span()))
144			}
145			TSXToken::Keyword(TSXKeyword::Return) => Ok({
146				let Token(_, start) = reader.next().unwrap();
147				state.append_keyword_at_pos(start.0, TSXKeyword::Return);
148				let next = reader.peek().ok_or_else(parse_lexing_error)?;
149				if on_different_lines_or_line_end(&state.line_starts, start, next) {
150					let position = start.with_length(TSXKeyword::Return.length() as usize);
151					Statement::Return(ReturnStatement(None, position))
152				} else {
153					let multiple_expression =
154						MultipleExpression::from_reader(reader, state, options)?;
155					let position = start.union(multiple_expression.get_position());
156					Statement::Return(ReturnStatement(Some(multiple_expression), position))
157				}
158			}),
159			TSXToken::Keyword(TSXKeyword::Break) => {
160				let Token(_break_token, start) = reader.next().unwrap();
161				state.append_keyword_at_pos(start.0, TSXKeyword::Break);
162				let next = reader.peek().ok_or_else(parse_lexing_error)?;
163				if on_different_lines_or_line_end(&state.line_starts, start, next) {
164					Ok(Statement::Break(
165						None,
166						start.with_length(TSXKeyword::Break.length() as usize),
167					))
168				} else {
169					let (label, position) =
170						token_as_identifier(reader.next().unwrap(), "break label")?;
171					Ok(Statement::Break(Some(label), start.union(position)))
172				}
173			}
174			TSXToken::Keyword(TSXKeyword::Continue) => {
175				let Token(_continue_token, start) = reader.next().unwrap();
176				state.append_keyword_at_pos(start.0, TSXKeyword::Continue);
177				let next = reader.peek().ok_or_else(parse_lexing_error)?;
178				if on_different_lines_or_line_end(&state.line_starts, start, next) {
179					Ok(Statement::Continue(
180						None,
181						start.with_length(TSXKeyword::Continue.length() as usize),
182					))
183				} else {
184					let (label, position) =
185						token_as_identifier(reader.next().unwrap(), "continue label")?;
186					Ok(Statement::Continue(Some(label), start.union(position)))
187				}
188			}
189			TSXToken::Comment(_) => {
190				if let Token(TSXToken::Comment(comment), start) = reader.next().unwrap() {
191					let position = start.with_length(comment.len() + 2);
192					Ok(Statement::Comment(comment, position))
193				} else {
194					unreachable!()
195				}
196			}
197			TSXToken::MultiLineComment(_) => {
198				if let Token(TSXToken::MultiLineComment(comment), start) = reader.next().unwrap() {
199					let position = start.with_length(comment.len() + 2);
200					Ok(Statement::MultiLineComment(comment, position))
201				} else {
202					unreachable!()
203				}
204			}
205			TSXToken::SemiColon => {
206				Ok(Statement::AestheticSemiColon(reader.next().unwrap().get_span()))
207			}
208			TSXToken::EOS => {
209				reader.next();
210				Ok(Statement::Empty(Span { start: 0, end: 0, source: () }))
211			}
212			// Finally ...!
213			_ => {
214				let expr = MultipleExpression::from_reader(reader, state, options)?;
215				if let (true, Expression::Marker { .. }) = (options.partial_syntax, expr.get_rhs())
216				{
217					Err(ParseError::new(
218						ParseErrors::ExpectedIdentifier,
219						reader.next().unwrap().get_span(),
220					))
221				} else {
222					Ok(Statement::Expression(expr))
223				}
224			}
225		}
226	}
227
228	fn to_string_from_buffer<T: source_map::ToString>(
229		&self,
230		buf: &mut T,
231		options: &crate::ToStringOptions,
232		local: crate::LocalToStringInformation,
233	) {
234		match self {
235			Statement::Empty(..) => {}
236			Statement::AestheticSemiColon(..) => buf.push(';'),
237			Statement::Return(ReturnStatement(expression, _)) => {
238				buf.push_str("return");
239				if let Some(expression) = expression {
240					buf.push(' ');
241					expression.to_string_from_buffer(buf, options, local);
242				}
243			}
244			Statement::If(is) => is.to_string_from_buffer(buf, options, local),
245			Statement::ForLoop(fl) => fl.to_string_from_buffer(buf, options, local),
246			Statement::Switch(ss) => ss.to_string_from_buffer(buf, options, local),
247			Statement::WhileLoop(ws) => ws.to_string_from_buffer(buf, options, local),
248			Statement::DoWhileLoop(dws) => dws.to_string_from_buffer(buf, options, local),
249			Statement::TryCatch(tcs) => tcs.to_string_from_buffer(buf, options, local),
250			Statement::Comment(comment, _) => {
251				if options.should_add_comment(comment.as_str()) {
252					buf.push_str("//");
253					buf.push_str_contains_new_line(comment.as_str().trim_end());
254				}
255			}
256			Statement::MultiLineComment(comment, _) => {
257				if options.should_add_comment(comment) {
258					buf.push_str("/*");
259					if options.pretty {
260						// Perform indent correction
261						for (idx, line) in comment.split('\n').enumerate() {
262							if idx > 0 {
263								buf.push_new_line();
264							}
265							options.add_indent(local.depth, buf);
266							buf.push_str(line.trim());
267						}
268					} else {
269						buf.push_str_contains_new_line(comment.as_str());
270					}
271					buf.push_str("*/");
272				}
273			}
274			Statement::Block(block) => {
275				block.to_string_from_buffer(buf, options, local.next_level());
276			}
277			Statement::Debugger(_) => buf.push_str("debugger"),
278			Statement::Continue(label, _) => {
279				buf.push_str("continue");
280				if let Some(label) = label {
281					buf.push(' ');
282					buf.push_str(label);
283				}
284			}
285			Statement::Break(label, _) => {
286				buf.push_str("break");
287				if let Some(label) = label {
288					buf.push(' ');
289					buf.push_str(label);
290				}
291			}
292			Statement::Expression(val) => {
293				val.to_string_on_left(buf, options, local);
294			}
295			Statement::Labelled { name, statement, .. } => {
296				buf.push_str(name);
297				buf.push_str(": ");
298
299				if let Statement::Empty(..) = &**statement {
300					buf.push(';');
301				} else {
302					// TODO new line?
303					statement.to_string_from_buffer(buf, options, local);
304					if statement.requires_semi_colon() {
305						buf.push(';');
306					}
307				}
308			}
309			Statement::Throw(ThrowStatement(thrown_expression, _)) => {
310				buf.push_str("throw ");
311				thrown_expression.to_string_from_buffer(buf, options, local);
312			}
313			Statement::VarVariable(var_stmt) => var_stmt.to_string_from_buffer(buf, options, local),
314		}
315	}
316}
317
318impl Statement {
319	/// Used for skipping in `to_string`
320	#[must_use]
321	pub fn is_comment(&self) -> bool {
322		matches!(self, Statement::Comment(..) | Statement::MultiLineComment(..))
323	}
324
325	pub(crate) fn requires_semi_colon(&self) -> bool {
326		matches!(
327			self,
328			Statement::VarVariable(_)
329				| Statement::Expression(_)
330				| Statement::DoWhileLoop(_)
331				| Statement::Continue(..)
332				| Statement::Break(..)
333				| Statement::Return(..)
334				| Statement::Throw(..)
335		)
336	}
337}
338
339#[apply(derive_ASTNode)]
340#[derive(Debug, PartialEq, Clone, Visitable, get_field_by_type::GetFieldByType)]
341#[get_field_by_type_target(Span)]
342pub struct VarVariableStatement {
343	pub declarations: Vec<VariableDeclarationItem<Option<Expression>>>,
344	pub position: Span,
345}
346
347impl ASTNode for VarVariableStatement {
348	fn get_position(&self) -> Span {
349		self.position
350	}
351
352	fn from_reader(
353		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
354		state: &mut crate::ParsingState,
355		options: &ParseOptions,
356	) -> ParseResult<Self> {
357		let Token(_, start) = reader.next().unwrap();
358		let mut declarations = Vec::new();
359		loop {
360			let value =
361				VariableDeclarationItem::<Option<Expression>>::from_reader(reader, state, options)?;
362			if value.expression.is_none()
363				&& !matches!(value.name.get_ast_ref(), crate::VariableField::Name(_))
364			{
365				return Err(crate::ParseError::new(
366					crate::ParseErrors::DestructuringRequiresValue,
367					value.name.get_ast_ref().get_position(),
368				));
369			}
370			declarations.push(value);
371			if let Some(Token(TSXToken::Comma, _)) = reader.peek() {
372				reader.next();
373			} else {
374				break;
375			}
376		}
377
378		let position = if let Some(last) = declarations.last() {
379			start.union(last.get_position())
380		} else {
381			let position = start.with_length(3);
382			if options.partial_syntax {
383				position
384			} else {
385				return Err(ParseError::new(ParseErrors::ExpectedDeclaration, position));
386			}
387		};
388
389		Ok(VarVariableStatement { declarations, position })
390	}
391
392	fn to_string_from_buffer<T: source_map::ToString>(
393		&self,
394		buf: &mut T,
395		options: &crate::ToStringOptions,
396		local: crate::LocalToStringInformation,
397	) {
398		buf.push_str("var ");
399		declarations_to_string(&self.declarations, buf, options, local, false);
400	}
401}
402
403fn on_different_lines_or_line_end(
404	line_starts: &source_map::LineStarts,
405	keyword_position: crate::TokenStart,
406	Token(kind, next): &Token<TSXToken, crate::TokenStart>,
407) -> bool {
408	matches!(kind, TSXToken::SemiColon | TSXToken::CloseBrace | TSXToken::EOS)
409		|| line_starts.byte_indexes_on_different_lines(keyword_position.0 as usize, next.0 as usize)
410}