use super::*;
impl<'cst, 'ast> Printer<'cst, 'ast> {
pub(super) fn write_block(&mut self, block: Block) {
for &statement in block.as_slice() {
self.write_statement(statement);
}
}
pub(super) fn write_root_block(&mut self, block: Block) {
self.write_block(block);
self.advance(block.location().end);
}
pub(super) fn write_visualized_block(&mut self, block: Block) {
self.write_block(block);
self.advance(block.location().end);
}
pub(super) fn write_end(&mut self, location: Location) {
let mut end = location.end;
if end.column >= 3 {
end.column -= 3;
}
self.advance(end);
self.keyword("end");
}
pub(super) fn write_else_if_statement(
&mut self,
condition: Expression,
then_body: Block,
else_body: Option<Statement>,
then_location: Option<Location>,
else_location: Option<Location>,
) {
self.write_expression(condition);
if let Some(then_location) = then_location {
self.advance(then_location.begin);
}
self.keyword("then");
self.write_visualized_block(then_body);
if let Some(else_body) = else_body {
if let Some(else_if) = else_body.as_if() {
if let Some(else_location) = else_location {
self.advance(else_location.begin);
}
self.keyword("elseif");
self.write_else_if_statement(
else_if.condition,
else_if.then_body,
else_if.else_body,
else_if.then_location,
else_if.else_location,
);
} else {
if let Some(else_location) = else_location {
self.advance(else_location.begin);
}
self.keyword("else");
if let Some(block) = else_body.as_block() {
self.write_visualized_block(block);
} else {
self.write_statement(else_body);
self.advance(else_body.location().end);
}
self.keyword("end");
}
} else {
self.advance(then_body.location().end);
self.keyword("end");
}
}
pub(super) fn write_else_if_expression(
&mut self,
condition: Expression,
then_expression: Expression,
else_expression: Expression,
cst: Option<&CstExprIfElse>,
) {
let cst_nodes = self.cst_nodes;
self.write_expression(condition);
if let Some(cst) = cst {
self.maybe_advance_and_write(Some(cst.then_position), "then", false);
} else {
self.keyword("then");
}
self.write_expression(then_expression);
if let Some(cst) = cst {
self.advance(cst.else_position);
}
if let ExpressionKind::If {
condition: nested_condition,
then_expression: nested_then_expression,
else_expression: nested_else_expression,
..
} = else_expression.kind()
&& cst.is_none_or(|cst| cst.is_else_if)
{
self.keyword("elseif");
self.write_else_if_expression(
nested_condition,
nested_then_expression,
nested_else_expression,
expression_cst(cst_nodes, else_expression).and_then(|node| match node {
CstNode::ExprIfElse(cst) => Some(cst),
_ => None,
}),
);
} else {
self.keyword("else");
self.write_expression(else_expression);
}
}
pub(super) fn write_statement(&mut self, statement: Statement) {
self.advance(statement.location().begin);
let cst_nodes = self.cst_nodes;
match statement.tag {
StatementTag::Block => {
let block = statement.as_block_unchecked();
if let Some(CstNode::StatDo(CstStatDo { stats_start, end })) =
statement_cst(cst_nodes, statement)
{
self.keyword("do");
self.advance(*stats_start);
self.write_block(block);
self.maybe_advance_and_write(Some(*end), "end", false);
} else {
self.write_block(block);
self.advance(block.location().end);
self.write_end(statement.location());
}
}
StatementTag::Local => {
let local = statement.as_local_unchecked();
let local_cst =
if let Some(CstNode::StatLocal(cst)) = statement_cst(cst_nodes, statement) {
Some(cst)
} else {
None
};
if flags::LuauExportValueSyntax.get() && local.is_exported {
self.keyword("export");
if let Some(position) = local_cst
.as_ref()
.map(|cst| cst.declaration_keyword_position)
&& position.has_value()
{
self.advance(position);
}
}
self.keyword(if local.is_const { "const" } else { "local" });
for (index, binding) in local.bindings.iter().enumerate() {
if index > 0 {
if let Some(position) = local_cst
.as_ref()
.and_then(|cst| cst.variable_commas.get(index - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.write_local_with_colon(
binding,
local_cst
.as_ref()
.and_then(|cst| cst.variable_annotation_colons.get(index).copied()),
);
}
if !local.values.is_empty() || local.equals_sign_location.is_some() {
if let Some(location) = local.equals_sign_location {
self.advance(location.begin);
} else {
self.symbol(" ");
}
self.symbol("=");
for (index, value) in local.values.iter().enumerate() {
if index > 0 {
if let Some(position) = local_cst
.as_ref()
.and_then(|cst| cst.value_commas.get(index - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.write_expression(*value);
}
}
}
StatementTag::LocalFunction => {
let local_function = statement.as_local_function_unchecked();
let name = local_function.name;
let function = local_function.function;
let local_function_cst = statement_cst(cst_nodes, statement);
let attr_lists = match local_function_cst {
Some(CstNode::StatLocalFunction(cst)) => Some(cst.attr_lists.as_slice()),
_ => None,
};
self.write_attributes(function.attributes, attr_lists);
if let Some(CstNode::StatLocalFunction(CstStatLocalFunction {
attr_lists: _,
local_keyword,
function_keyword,
})) = local_function_cst
{
self.advance(*local_keyword);
if flags::LuauExportValueSyntax.get() && name.is_exported {
self.keyword("export");
} else if name.is_const {
self.keyword("const");
} else {
self.keyword("local");
}
self.advance(*function_keyword);
self.keyword("function");
} else if flags::LuauExportValueSyntax.get() && name.is_exported {
self.advance_before(name.location.begin, "export function ".len() as u32);
self.keyword("export");
self.symbol(" ");
self.keyword("function");
} else if name.is_const {
self.advance_before(name.location.begin, "const function ".len() as u32);
self.keyword("const");
self.symbol(" ");
self.keyword("function");
} else {
self.advance_before(name.location.begin, "local function ".len() as u32);
self.keyword("local");
self.symbol(" ");
self.keyword("function");
}
self.advance(name.location.begin);
self.identifier(name.name.bytes());
self.write_function_body(function);
}
StatementTag::FunctionDeclaration => {
let declaration = statement.as_function_declaration_unchecked();
let name = declaration.name;
let function = declaration.function;
let function_cst = statement_cst(cst_nodes, statement);
let attr_lists = match function_cst {
Some(CstNode::StatFunction(cst)) => Some(cst.attr_lists.as_slice()),
_ => None,
};
self.write_attributes(function.attributes, attr_lists);
if let Some(CstNode::StatFunction(CstStatFunction {
attr_lists: _,
function_keyword,
})) = function_cst
{
self.advance(*function_keyword);
} else {
self.advance_before(name.location().begin, "function ".len() as u32);
}
self.keyword("function");
self.write_expression(name);
self.write_function_body(function);
}
StatementTag::TypeAlias => {
let alias = statement.as_type_alias_unchecked();
if self.write_types {
if alias.exported {
self.keyword("export");
}
if let Some(CstNode::StatTypeAlias(CstStatTypeAlias { type_keyword, .. })) =
statement_cst(cst_nodes, statement)
{
self.advance(*type_keyword);
}
self.keyword("type");
self.advance(alias.name_location.begin);
self.identifier(alias.name.bytes());
let type_alias_cst = if let Some(CstNode::StatTypeAlias(cst)) =
statement_cst(cst_nodes, statement)
{
Some(cst)
} else {
None
};
self.write_generic_parameters(
alias.generics,
alias.generic_packs,
type_alias_cst.as_ref().map(|cst| {
(
cst.generics_open,
cst.generics_commas.as_slice(),
cst.generics_close,
)
}),
GenericPackEllipsisMode::PreserveMissingFromCst,
);
if let Some(cst) = type_alias_cst {
self.maybe_advance_and_write(Some(cst.equals), "=", false);
} else {
self.maybe_space(alias.ty.location.begin, 2);
self.symbol("=");
}
self.write_type(alias.ty);
}
}
StatementTag::TypeFunction => {
let type_function = statement.as_type_function_unchecked();
if self.write_types {
if type_function.exported {
self.keyword("export");
}
if let Some(CstNode::StatTypeFunction(CstStatTypeFunction {
type_keyword,
function_keyword,
})) = statement_cst(cst_nodes, statement)
{
self.advance(*type_keyword);
self.keyword("type");
self.advance(*function_keyword);
self.keyword("function");
} else {
self.keyword("type");
self.keyword("function");
}
self.advance(type_function.name_location.begin);
self.identifier(type_function.name.bytes());
self.write_function_body(type_function.body);
}
}
StatementTag::Expression => {
self.write_expression(statement.as_expression_unchecked().expr)
}
StatementTag::Return => {
let return_statement = statement.as_return_unchecked();
let return_cst =
if let Some(CstNode::StatReturn(CstStatReturn { comma_positions })) =
statement_cst(cst_nodes, statement)
{
Some(comma_positions)
} else {
None
};
self.keyword("return");
self.write_expression_list_with_commas(
return_statement.expressions,
return_cst.as_ref().map(|positions| positions.as_slice()),
);
}
StatementTag::NumericFor => {
let numeric_for = statement.as_numeric_for_unchecked();
let for_cst =
if let Some(CstNode::StatFor(cst)) = statement_cst(cst_nodes, statement) {
Some(cst)
} else {
None
};
self.keyword("for");
self.write_local_with_colon(
numeric_for.name,
for_cst.as_ref().map(|cst| cst.annotation_colon),
);
if let Some(cst) = for_cst.as_ref() {
self.advance(cst.equals);
}
self.symbol("=");
self.write_expression(numeric_for.start);
if let Some(cst) = for_cst.as_ref() {
self.maybe_advance_and_write(Some(cst.end_comma), ",", false);
} else {
self.symbol(",");
}
self.write_expression(numeric_for.limit);
if let Some(step) = numeric_for.step {
if let Some(cst) = for_cst.as_ref().and_then(|cst| cst.step_comma) {
self.advance(cst);
}
self.symbol(",");
self.write_expression(step);
}
self.advance(numeric_for.do_location.begin);
self.keyword("do");
self.write_visualized_block(numeric_for.body);
self.keyword("end");
}
StatementTag::GenericFor => {
let generic_for = statement.as_generic_for_unchecked();
let for_in_cst =
if let Some(CstNode::StatForIn(cst)) = statement_cst(cst_nodes, statement) {
Some(cst)
} else {
None
};
self.keyword("for");
for (index, name) in generic_for.names.iter().enumerate() {
if index > 0 {
if let Some(position) = for_in_cst
.as_ref()
.and_then(|cst| cst.variable_commas.get(index - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.write_local_with_colon(
name,
for_in_cst
.as_ref()
.and_then(|cst| cst.variable_annotation_colons.get(index).copied()),
);
}
self.advance(generic_for.in_location.begin);
self.keyword("in");
for (index, value) in generic_for.values.iter().enumerate() {
if index > 0 {
if let Some(position) = for_in_cst
.as_ref()
.and_then(|cst| cst.value_commas.get(index - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.write_expression(*value);
}
self.advance(generic_for.do_location.begin);
self.keyword("do");
self.write_visualized_block(generic_for.body);
self.keyword("end");
}
StatementTag::Assign => {
let assign = statement.as_assign_unchecked();
let assign_cst =
if let Some(CstNode::StatAssign(cst)) = statement_cst(cst_nodes, statement) {
Some(cst)
} else {
None
};
self.write_expression_list_with_commas(
assign.vars,
assign_cst.as_ref().map(|cst| cst.variable_commas),
);
if let Some(cst) = assign_cst.as_ref() {
self.maybe_advance_and_write(Some(cst.equals), "=", false);
} else {
self.symbol(" ");
self.symbol("=");
}
self.write_expression_list_with_commas(
assign.values,
assign_cst.as_ref().map(|cst| cst.value_commas),
);
}
StatementTag::CompoundAssign => {
let compound_assign = statement.as_compound_assign_unchecked();
self.write_expression(compound_assign.var);
if let Some(CstNode::StatCompoundAssign(CstStatCompoundAssign { op })) =
statement_cst(cst_nodes, statement)
{
self.advance(*op);
} else {
self.maybe_space(
compound_assign.value.location.begin,
compound_assign.op.symbol().len() as u32 + 1,
);
}
self.symbol(compound_assign.op.symbol());
self.symbol("=");
self.write_expression(compound_assign.value);
}
StatementTag::Break => self.keyword("break"),
StatementTag::Continue => self.keyword("continue"),
StatementTag::DeclareGlobal => {
let declare_global = statement.as_declare_global_unchecked();
self.keyword("declare");
self.identifier(declare_global.name.bytes());
self.symbol(":");
self.write_type(declare_global.ty);
}
StatementTag::Class => {
let class = statement.as_class_unchecked();
debug_assert!(flags::DebugLuauUserDefinedClasses.get());
self.keyword("class");
self.advance(class.name.location.begin);
self.identifier(class.name.name.bytes());
if let Some(super_class) = class.super_class {
self.keyword("extends");
self.write_expression(super_class);
}
for member in class.members {
self.write_class_member(member);
}
self.newline();
self.keyword("end");
self.newline();
}
StatementTag::Error => {
let error = statement.as_error_unchecked();
self.symbol("(error-stat");
for (index, expression) in error.expressions.iter().enumerate() {
self.symbol(if index == 0 { ": " } else { ", " });
self.write_expression(*expression);
}
for (index, statement) in error.statements.iter().enumerate() {
self.symbol(if index == 0 && error.expressions.is_empty() {
": "
} else {
", "
});
self.write_statement(*statement);
}
self.symbol(")");
}
StatementTag::If => {
let if_statement = statement.as_if_unchecked();
self.keyword("if");
self.write_else_if_statement(
if_statement.condition,
if_statement.then_body,
if_statement.else_body,
if_statement.then_location,
if_statement.else_location,
);
}
StatementTag::While => {
let while_statement = statement.as_while_unchecked();
self.keyword("while");
self.write_expression(while_statement.condition);
self.advance(while_statement.do_location.begin);
self.keyword("do");
self.write_visualized_block(while_statement.body);
self.keyword("end");
}
StatementTag::Repeat => {
let repeat = statement.as_repeat_unchecked();
self.keyword("repeat");
self.write_visualized_block(repeat.body);
if let Some(CstNode::StatRepeat(CstStatRepeat { until })) =
statement_cst(cst_nodes, statement)
{
self.maybe_advance_and_write(Some(*until), "until", false);
} else {
self.advance_before(repeat.condition.location.begin, "until ".len() as u32);
self.keyword("until");
}
self.write_expression(repeat.condition);
}
StatementTag::DeclareFunction | StatementTag::DeclareExternType => {}
}
if statement.has_semicolon() {
self.advance_before(statement.location().end, 1);
self.symbol(";");
}
}
pub(super) fn write_class_member(&mut self, member: &ClassMember) {
match member {
ClassMember::Property {
qualifier_location,
name,
name_location,
ty,
type_colon_location,
} => {
self.advance(qualifier_location.begin);
self.keyword("public");
self.advance(name_location.begin);
self.identifier(name.bytes());
if self.write_types
&& let Some(ty) = ty
{
if let Some(location) = type_colon_location {
self.advance(location.begin);
}
self.symbol(":");
self.write_type(*ty);
}
}
ClassMember::Method {
qualifier_location,
keyword_location,
function_name,
name_location,
function,
} => {
if let Some(qualifier_location) = qualifier_location {
self.advance(qualifier_location.begin);
self.keyword("public");
}
self.advance(keyword_location.begin);
self.keyword("function");
self.advance(name_location.begin);
self.identifier(function_name.bytes());
self.write_function_body(function);
}
}
}
pub(super) fn write_local_with_colon(
&mut self,
local: &Local,
annotation_colon: Option<Position>,
) {
self.advance(local.location.begin);
self.identifier(local.name.bytes());
if self.write_types
&& let Some(annotation) = local.annotation
{
self.maybe_advance_and_write(annotation_colon, ":", true);
self.write_type(annotation);
}
}
pub(super) fn write_function_body(&mut self, function: &Function) {
let cst_nodes = self.cst_nodes;
let function_cst =
if let Some(CstNode::ExprFunction(cst)) = function_cst(cst_nodes, function) {
Some(cst)
} else {
None
};
self.write_generic_parameters(
function.generics,
function.generic_packs,
function_cst
.as_ref()
.map(|cst| (cst.open_generics, cst.generics_commas, cst.close_generics)),
GenericPackEllipsisMode::AlwaysWrite,
);
if let Some(location) = function.arg_location {
self.advance(location.begin);
}
self.symbol("(");
for (index, arg) in function.args.iter().enumerate() {
if index > 0 {
if let Some(position) = function_cst
.as_ref()
.and_then(|cst| cst.argument_commas.get(index - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.write_local_with_colon(
arg,
function_cst
.as_ref()
.and_then(|cst| cst.argument_annotation_colons.get(index).copied()),
);
}
if function.vararg {
if !function.args.is_empty() {
if let Some(position) = function_cst
.as_ref()
.and_then(|cst| cst.argument_commas.get(function.args.len() - 1).copied())
{
self.advance(position);
}
self.symbol(",");
}
self.advance(function.vararg_location.begin);
self.symbol("...");
if let Some(annotation) = function.vararg_annotation {
self.maybe_advance_and_write(
function_cst.as_ref().map(|cst| cst.vararg_annotation_colon),
":",
true,
);
self.write_type_pack(annotation, true);
}
}
if let Some(location) = function.arg_location {
self.advance_before(location.end, 1);
}
self.symbol(")");
if self.write_types
&& let Some(annotation) = function.return_annotation
{
self.maybe_advance_and_write(
function_cst.as_ref().map(|cst| cst.return_specifier),
":",
true,
);
self.write_type_pack(annotation, false);
}
self.write_visualized_block(function.body);
self.keyword("end");
}
}