Skip to content

Commit

Permalink
Use AST for all code generation, style changes (#76)
Browse files Browse the repository at this point in the history
Refactors code generation code so both flat and nested codegen use an
AST.

Also includes some style changes for consistency and formatting reasons:
- Adds a newline to the end of the file
- Typescript declarations have a semicolon at the end of each entry
- Uses tabs instead of spaces for indentation

With these style changes, it results in code that conforms with StyLua
and Biome's default rules.

Closes #70
  • Loading branch information
paradoxuum authored Jul 30, 2024
1 parent c74e170 commit 85e083e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ pub(crate) enum AstTarget {
}

pub(crate) struct AstStream<'a, 'b> {
number_of_spaces: usize,
indents: usize,
is_start_of_line: bool,
writer: &'a mut (dyn Write),
Expand All @@ -37,7 +36,6 @@ pub(crate) struct AstStream<'a, 'b> {
impl<'a, 'b> AstStream<'a, 'b> {
pub fn new(writer: &'a mut (dyn fmt::Write + 'a), target: &'b AstTarget) -> Self {
Self {
number_of_spaces: 4,
indents: 0,
is_start_of_line: true,
writer,
Expand Down Expand Up @@ -75,11 +73,8 @@ impl Write for AstStream<'_, '_> {
if !line.is_empty() {
if self.is_start_of_line {
self.is_start_of_line = false;
self.writer.write_str(&format!(
"{: >1$}",
"",
self.number_of_spaces * self.indents
))?;
self.writer
.write_str(&format!("{:\t>1$}", "", self.indents))?;
}

self.writer.write_str(line)?;
Expand Down Expand Up @@ -108,7 +103,9 @@ impl AstFormat for ReturnStatement {

let result = self.0.fmt_ast(output);
if let AstTarget::Typescript { output_dir } = output.target {
write!(output, "\nexport = {output_dir}")?
write!(output, "\nexport = {output_dir};\n")?
} else {
writeln!(output)?;
}
result
}
Expand Down Expand Up @@ -149,9 +146,11 @@ pub(crate) struct Table {

impl AstFormat for Table {
fn fmt_ast(&self, output: &mut AstStream<'_, '_>) -> fmt::Result {
let assignment = match output.target {
AstTarget::Luau => " = ",
AstTarget::Typescript { .. } => ": ",
let typescript = matches!(output.target, AstTarget::Typescript { .. });
let (assignment, ending) = if typescript {
(": ", ";")
} else {
(" = ", ",")
};

writeln!(output, "{{")?;
Expand All @@ -161,11 +160,25 @@ impl AstFormat for Table {
key.fmt_key(output)?;
write!(output, "{assignment}")?;
value.fmt_ast(output)?;
writeln!(output, ",")?;

// If the value is a table and the target is TypeScript, we don't need the ending
// as it will be added after - this avoids double semi-colons for nested tables.
if let Expression::Table(_) = value {
if typescript {
writeln!(output)?;
continue;
}
}

writeln!(output, "{ending}")?;
}

output.unindent();
write!(output, "}}")
if typescript {
write!(output, "}};")
} else {
write!(output, "}}")
}
}
}

Expand Down
55 changes: 32 additions & 23 deletions src/commands/sync/codegen/flat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ use std::{
path::{Path, PathBuf},
};

use super::{
ast::{AstTarget, Expression},
generate_code,
};

fn asset_path(file_path: &str, strip_dir: &str, strip_extension: bool) -> anyhow::Result<String> {
if strip_extension {
Path::new(file_path).with_extension("")
Expand All @@ -17,21 +22,30 @@ fn asset_path(file_path: &str, strip_dir: &str, strip_extension: bool) -> anyhow
.map(|s| s.to_string())
}

fn generate_table(
assets: &BTreeMap<String, String>,
strip_dir: &str,
strip_extension: bool,
) -> anyhow::Result<Expression> {
let mut expressions: Vec<(Expression, Expression)> = Vec::new();
for (file_path, asset_id) in assets.iter() {
let file_stem = asset_path(file_path, strip_dir, strip_extension)?;
expressions.push((
Expression::String(file_stem),
Expression::String(asset_id.clone()),
));
}
Ok(Expression::table(expressions))
}

pub fn generate_luau(
assets: &BTreeMap<String, String>,
strip_dir: &str,
strip_extension: bool,
) -> anyhow::Result<String> {
let table = assets
.iter()
.map(|(file_path, asset_id)| {
let file_stem = asset_path(file_path, strip_dir, strip_extension)?;
Ok(format!("\t[\"{}\"] = \"{}\"", file_stem, asset_id))
})
.collect::<Result<Vec<String>, anyhow::Error>>()?
.join(",\n");

Ok(format!("return {{\n{}\n}}", table))
let table =
generate_table(assets, strip_dir, strip_extension).context("Failed to generate table")?;
generate_code(table, AstTarget::Luau)
}

pub fn generate_ts(
Expand All @@ -40,17 +54,12 @@ pub fn generate_ts(
output_dir: &str,
strip_extension: bool,
) -> anyhow::Result<String> {
let interface = assets
.keys()
.map(|file_path| {
let file_stem = asset_path(file_path, strip_dir, strip_extension)?;
Ok(format!("\t\"{}\": string", file_stem))
})
.collect::<Result<Vec<String>, anyhow::Error>>()?
.join(",\n");

Ok(format!(
"declare const {}: {{\n{}\n}}\nexport = {}",
output_dir, interface, output_dir
))
let table =
generate_table(assets, strip_dir, strip_extension).context("Failed to generate table")?;
generate_code(
table,
AstTarget::Typescript {
output_dir: output_dir.to_owned(),
},
)
}
31 changes: 23 additions & 8 deletions src/commands/sync/codegen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::collections::BTreeMap;
use std::fmt::Write;

use ast::{AstTarget, Expression, ReturnStatement};

use crate::commands::sync::config::CodegenStyle;

mod ast;
mod flat;
mod nested;

Expand Down Expand Up @@ -29,6 +34,12 @@ pub fn generate_ts(
}
}

fn generate_code(expression: Expression, target: AstTarget) -> anyhow::Result<String> {
let mut buffer = String::new();
write!(buffer, "{}", ReturnStatement(expression, target))?;
Ok(buffer)
}

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
Expand All @@ -48,12 +59,12 @@ mod tests {
let lockfile = test_assets();

let lua = super::flat::generate_luau(&lockfile, "assets", false).unwrap();
assert_eq!(lua, "return {\n\t[\"/bar/baz.png\"] = \"rbxasset://.asphalt/bar/baz.png\",\n\t[\"/foo.png\"] = \"rbxassetid://1\"\n}");
assert_eq!(lua, "return {\n\t[\"/bar/baz.png\"] = \"rbxasset://.asphalt/bar/baz.png\",\n\t[\"/foo.png\"] = \"rbxassetid://1\",\n}\n");

let lua = super::flat::generate_luau(&lockfile, "assets", true).unwrap();
assert_eq!(
lua,
"return {\n\t[\"/bar/baz\"] = \"rbxasset://.asphalt/bar/baz.png\",\n\t[\"/foo\"] = \"rbxassetid://1\"\n}"
"return {\n\t[\"/bar/baz\"] = \"rbxasset://.asphalt/bar/baz.png\",\n\t[\"/foo\"] = \"rbxassetid://1\",\n}\n"
);
}

Expand All @@ -62,10 +73,10 @@ mod tests {
let lockfile = test_assets();

let ts = super::flat::generate_ts(&lockfile, "assets", "assets", false).unwrap();
assert_eq!(ts, "declare const assets: {\n\t\"/bar/baz.png\": string,\n\t\"/foo.png\": string\n}\nexport = assets");
assert_eq!(ts, "declare const assets: {\n\t\"/bar/baz.png\": \"rbxasset://.asphalt/bar/baz.png\";\n\t\"/foo.png\": \"rbxassetid://1\";\n};\nexport = assets;\n");

let ts = super::flat::generate_ts(&lockfile, "assets", "assets", true).unwrap();
assert_eq!(ts, "declare const assets: {\n\t\"/bar/baz\": string,\n\t\"/foo\": string\n}\nexport = assets");
assert_eq!(ts, "declare const assets: {\n\t\"/bar/baz\": \"rbxasset://.asphalt/bar/baz.png\";\n\t\"/foo\": \"rbxassetid://1\";\n};\nexport = assets;\n");
}

#[test]
Expand All @@ -75,12 +86,14 @@ mod tests {
let lua = super::nested::generate_luau(&lockfile, "assets", false).unwrap();
assert_eq!(
lua,
"return {\n bar = {\n [\"baz.png\"] = \"rbxasset://.asphalt/bar/baz.png\",\n },\n [\"foo.png\"] = \"rbxassetid://1\",\n}");
"return {\n\tbar = {\n\t\t[\"baz.png\"] = \"rbxasset://.asphalt/bar/baz.png\",\n\t},\n\t[\"foo.png\"] = \"rbxassetid://1\",\n}\n"
);

let lua = super::nested::generate_luau(&lockfile, "assets", true).unwrap();
assert_eq!(
lua,
"return {\n bar = {\n baz = \"rbxasset://.asphalt/bar/baz.png\",\n },\n foo = \"rbxassetid://1\",\n}");
"return {\n\tbar = {\n\t\tbaz = \"rbxasset://.asphalt/bar/baz.png\",\n\t},\n\tfoo = \"rbxassetid://1\",\n}\n"
);
}

#[test]
Expand All @@ -90,11 +103,13 @@ mod tests {
let ts = super::nested::generate_ts(&lockfile, "assets", "assets", false).unwrap();
assert_eq!(
ts,
"declare const assets: {\n bar: {\n \"baz.png\": \"rbxasset://.asphalt/bar/baz.png\",\n },\n \"foo.png\": \"rbxassetid://1\",\n}\nexport = assets");
"declare const assets: {\n\tbar: {\n\t\t\"baz.png\": \"rbxasset://.asphalt/bar/baz.png\";\n\t};\n\t\"foo.png\": \"rbxassetid://1\";\n};\nexport = assets;\n"
);

let ts = super::nested::generate_ts(&lockfile, "assets", "assets", true).unwrap();
assert_eq!(
ts,
"declare const assets: {\n bar: {\n baz: \"rbxasset://.asphalt/bar/baz.png\",\n },\n foo: \"rbxassetid://1\",\n}\nexport = assets");
"declare const assets: {\n\tbar: {\n\t\tbaz: \"rbxasset://.asphalt/bar/baz.png\";\n\t};\n\tfoo: \"rbxassetid://1\";\n};\nexport = assets;\n"
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use self::types::NestedTable;
use super::ast::{AstTarget, Expression};
use super::generate_code;
use anyhow::{bail, Context};
use ast::{AstTarget, Expression, ReturnStatement};
use std::collections::BTreeMap;
use std::fmt::Write;
use std::path::PathBuf;
use std::{path::Component as PathComponent, path::Path};

mod ast;

pub(crate) mod types {
use std::collections::BTreeMap;

Expand Down Expand Up @@ -99,7 +97,7 @@ pub fn generate_luau(
) -> anyhow::Result<String> {
generate_code(
generate_expressions(assets, strip_dir, strip_extension)
.context("Failed to create nested expressions")?,
.context("Failed to generate nested table")?,
AstTarget::Luau,
)
}
Expand All @@ -112,15 +110,9 @@ pub fn generate_ts(
) -> anyhow::Result<String> {
generate_code(
generate_expressions(assets, strip_dir, strip_extension)
.context("Failed to create nested expressions")?,
.context("Failed to generate nested table")?,
AstTarget::Typescript {
output_dir: output_dir.to_owned(),
},
)
}

fn generate_code(expression: Expression, target: AstTarget) -> anyhow::Result<String> {
let mut buffer = String::new();
write!(buffer, "{}", ReturnStatement(expression, target))?;
Ok(buffer)
}

0 comments on commit 85e083e

Please sign in to comment.