diff --git a/.gitignore b/.gitignore
index e1fbe55c..0a019ac3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
-/target
+target
# Zap Input & Output
-/network
+network
net.zap
# Docs
@@ -14,3 +14,6 @@ zap/package
# Mac Moment
.DS_Store
+
+# Editor Stuff
+.vscode/
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 5ff3de3e..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "rust-analyzer.showUnlinkedFileNotification": false,
- "luau-lsp.sourcemap.enabled": false,
-}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 829027d5..d2efb05f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,9 +165,20 @@ version = "0.2.2"
dependencies = [
"anyhow",
"clap",
+ "codespan-reporting",
"zap",
]
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
[[package]]
name = "colorchoice"
version = "1.0.0"
@@ -606,6 +617,15 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "termcolor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "thiserror"
version = "1.0.51"
@@ -641,6 +661,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
[[package]]
name = "unicode-xid"
version = "0.2.4"
@@ -729,6 +755,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -871,9 +906,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
name = "zap"
version = "0.2.2"
dependencies = [
+ "codespan-reporting",
"lalrpop",
"lalrpop-util",
"num-traits",
- "thiserror",
"wasm-bindgen",
]
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 96966582..96084627 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -10,6 +10,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
clap = { version = "4.4.11", features = ["derive"] }
+codespan-reporting = "0.11.0"
zap = { path = "../zap" }
[[bin]]
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 70621711..67cb6eb4 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -2,6 +2,13 @@ use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
+use codespan_reporting::{
+ files::SimpleFile,
+ term::{
+ self,
+ termcolor::{ColorChoice, StandardStream},
+ },
+};
use zap::run;
#[derive(Parser, Debug)]
@@ -16,26 +23,49 @@ fn main() -> Result<()> {
let config_path = args.config.unwrap();
- let config = std::fs::read_to_string(config_path)?;
+ let config = std::fs::read_to_string(&config_path)?;
- let code = run(config.as_str())?;
+ let ret = run(config.as_str());
- if let Some(definitions) = code.server.definitions {
- let mut path = code.server.path.clone();
- path.set_extension("d.ts");
+ let code = ret.code;
+ let diagnostics = ret.diagnostics;
- std::fs::write(path, definitions)?
- }
+ if let Some(code) = code {
+ let server_path = code.server.path.unwrap_or(PathBuf::from("network/server.lua"));
+ let client_path = code.client.path.unwrap_or(PathBuf::from("network/client.lua"));
+
+ if let Some(parent) = server_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+
+ if let Some(parent) = client_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
- if let Some(definitions) = code.client.definitions {
- let mut path = code.client.path.clone();
- path.set_extension("d.ts");
+ std::fs::write(server_path.clone(), code.server.code)?;
+ std::fs::write(client_path.clone(), code.client.code)?;
- std::fs::write(path, definitions)?
+ if let Some(defs) = code.server.defs {
+ std::fs::write(server_path.with_extension("d.ts"), defs)?;
+ }
+
+ if let Some(defs) = code.client.defs {
+ std::fs::write(client_path.with_extension("d.ts"), defs)?;
+ }
}
- std::fs::write(code.server.path, code.server.contents)?;
- std::fs::write(code.client.path, code.client.contents)?;
+ if diagnostics.is_empty() {
+ return Ok(());
+ }
+
+ let file = SimpleFile::new(config_path.to_str().unwrap(), config);
+
+ let writer = StandardStream::stderr(ColorChoice::Auto);
+ let config_term = codespan_reporting::term::Config::default();
+
+ for diagnostic in diagnostics {
+ term::emit(&mut writer.lock(), &config_term, &file, &diagnostic)?;
+ }
Ok(())
}
diff --git a/config.ebnf b/config.ebnf
new file mode 100644
index 00000000..e8c12e90
--- /dev/null
+++ b/config.ebnf
@@ -0,0 +1,75 @@
+(* This file is exists only for the purpose of documentation *)
+(* and reference. It is not used anywhere within zap. *)
+
+(* Zap Rules *)
+(* this section has whitespace *)
+
+zap = {opt}, {evdecl | tydecl};
+
+opt = "opt", (
+ "write_checks", ':', ("true" | "false")
+ | "server_output", ':', string
+ | "client_output", ':', string
+ | "typescript", ':', ("true" | "false")
+), [';'];
+
+evdecl = "event", ident, '=', '{',
+ "from", ':', ("Server" | "Client"), ',',
+ "type", ':', ("Reliable" | "Unreliable"), ',',
+ "call", ':', ("SingleSync" | "SingleAsync" | "ManySync" | "ManyAsync"), ',',
+ "data", ':', ty, [','],
+'}', [';'];
+
+tydecl = "type", ident, '=', ty, [';'];
+
+ty = ty-num | ty-str | ty-arr | ty-map | ty-opt | ty-ref | ty-enum | ty-struct | ty-instance;
+
+ty-num = ("f32" | "f64"), ['(', range-num,')']
+ | ("u8" | "u16" | "u32" | "i8" | "i16" | "i32"), ['(', range-int,')'];
+
+ty-str = "string", ['(', range-int,')'];
+ty-arr = ty, '[', range-num, ']';
+ty-map = "map", '[', ty, ']', ':', ty;
+ty-opt = ty, '?';
+ty-ref = ident;
+
+ty-struct = "struct", struct;
+
+ty-enum = ty-enum-unit | ty-enum-tagged;
+ty-enum-unit = "enum", '{', ident, {',', ident}, [','], '}';
+ty-enum-tagged = "enum", string, '{', ident, struct, {',', ident, ty-struct}, [','], '}';
+
+ty-instance = "Instance", ['(', ident, ')'];
+
+struct-field = ident, ':', ty, [',', struct-field];
+struct = '{', struct-field, [","], '}';
+
+range-num = ""
+ | num, "..", num
+ | num, ".."
+ | "..", num
+ | ".."
+ | num;
+
+range-int = ""
+ | int, "..", int
+ | int, ".."
+ | "..", int
+ | ".."
+ | int;
+
+
+(* Base Rules *)
+(* this section has no whitespace *)
+
+ident = alpha, {alphanum | '_'};
+
+string = '"', {alphanum}, '"';
+
+num = int, ['.', int];
+int = digit, {digit};
+
+alphanum = alpha | digit;
+
+alpha = 'a'..'z' | 'A'..'Z';
+digit = '0'..'9';
diff --git a/docs/.vitepress/components/Editor.vue b/docs/.vitepress/components/Editor.vue
index cf53143a..80d3f607 100644
--- a/docs/.vitepress/components/Editor.vue
+++ b/docs/.vitepress/components/Editor.vue
@@ -84,16 +84,20 @@ const beforeMount = (monaco: Monaco) => {
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
+ { open: '"', close: '"' },
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
+ { open: '"', close: '"' },
],
});
const Keywords = ["event", "opt", "type"] as const;
+ const TypeKeywords = ["enum", "struct", "map"] as const;
+
const Operators = ["true", "false"] as const;
const Locations = ["Server", "Client"] as const;
@@ -102,7 +106,7 @@ const beforeMount = (monaco: Monaco) => {
const Calls = ["SingleSync", "SingleAsync", "ManySync", "ManyAsync"] as const;
- const Options = ["typescript", "write_checks", "casing", "output_server", "output_client"] as const;
+ const Options = ["typescript", "write_checks", "casing", "server_output", "client_output"] as const;
const Casing = ["PascalCase", "camelCase", "snake_case"] as const;
@@ -119,10 +123,10 @@ const beforeMount = (monaco: Monaco) => {
"i64",
"f32",
"f64",
- "bool",
+ "boolean",
"string",
"Instance",
- "Vector3"
+ "Vector3",
] as const;
const EventParamToArray = {
@@ -148,7 +152,7 @@ const beforeMount = (monaco: Monaco) => {
create: () => ({
defaultToken: "",
- keywords: [...Keywords, ...Operators],
+ keywords: [...Keywords, ...TypeKeywords, ...Operators],
brackets: [
{ token: "delimiter.bracket", open: "{", close: "}" },
@@ -192,6 +196,9 @@ const beforeMount = (monaco: Monaco) => {
},
],
+ // "str" identifiers
+ [/\"\w+\"/, "regexp"],
+
// identifiers and keywords
[/(\w+):/, "identifier"],
[
@@ -312,6 +319,33 @@ const beforeMount = (monaco: Monaco) => {
range,
}));
+ if (wordBefore && !WordToArray[wordBefore.word]) {
+ identifiers.push(
+ {
+ label: "enum",
+ kind: monaco.languages.CompletionItemKind.Variable,
+ insertText: "enum ",
+ range: range,
+ },
+ {
+ label: "map",
+ kind: monaco.languages.CompletionItemKind.Snippet,
+ insertText: "map { [${1}]: ${2} }\n",
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+ documentation: "Map",
+ range: range,
+ },
+ {
+ label: "struct",
+ kind: monaco.languages.CompletionItemKind.Snippet,
+ insertText: ["struct {", "\t${1}: ${2},", "}"].join("\n"),
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+ documentation: "Struct",
+ range: range,
+ }
+ );
+ }
+
return { suggestions: identifiers };
}
},
@@ -325,4 +359,3 @@ const beforeMount = (monaco: Monaco) => {
width: 100%;
}
-
diff --git a/docs/config/options.md b/docs/config/options.md
index 4137308e..e0b7d18c 100644
--- a/docs/config/options.md
+++ b/docs/config/options.md
@@ -4,7 +4,7 @@ Zap has a number of options marked with `opt`, that must be placed at the start
## Example Options
-
+
## Output
@@ -12,11 +12,11 @@ The output options allow you to define where zap will output its generated code.
### Default
-
+
### Example
-
+
## Casing
@@ -52,3 +52,20 @@ The writechecks option determines if conditional constraints should be valided o
### Example
+
+## Typescript
+
+The typescript option determines if Zap should output typescript definitions for the generated code.
+
+### Default
+
+`false`
+
+### Options
+
+- `true`
+- `false`
+
+### Example
+
+
diff --git a/docs/playground.md b/docs/playground.md
index 701596b5..41a9a363 100644
--- a/docs/playground.md
+++ b/docs/playground.md
@@ -15,37 +15,37 @@
**Output:**
-
+
-
+
-
+
-
+
-
+
@@ -59,11 +59,8 @@ import MonacoEditor from "@guolao/vue-monaco-editor";
import type { Monaco } from "@monaco-editor/loader";
import { useData, useRouter } from "vitepress";
import { ref, watch, onMounted } from "vue";
-import { run, Code } from "../zap/package"
-
-type PlaygroundCode = Code | {
- error?: string
-}
+import { run } from "../zap/package";
+import type { Return as PlaygroundCode } from "../zap/package";
const { isDark } = useData();
const { go } = useRouter();
@@ -77,14 +74,7 @@ const code = ref("");
const isTypeScript = ref(false)
const free = () => {};
const compiledResult = ref({
- client: {
- contents: "-- Write some code to see output here!\n",
- free,
- },
- server: {
- contents: "-- Write some code to see output here!\n",
- free,
- },
+ diagnostics: "Write some code to see output here!\n",
free,
})
@@ -115,26 +105,15 @@ watch(code, (newCode) => {
try {
compiledResult.value = run(newCode);
- if (!compiledResult.value.client.contents && !compiledResult.value.server.contents) compiledResult.value = {
- client: {
- contents: "-- Add an event to see output here!\n",
- free,
- },
- server: {
- contents: "-- Add an event to see output here!\n",
- free,
- },
- free,
- }
-
- if (compiledResult.value.client.definitions && compiledResult.value.server.definitions) {
+ if (compiledResult.value.code?.client.defs && compiledResult.value.code?.server.defs) {
isTypeScript.value = true
} else {
isTypeScript.value = false
}
} catch (err) {
compiledResult.value = {
- error: `--[[\n${err.message}\n]]`
+ diagnostics: `Unable to compile code: ${err.message}`,
+ free
}
isTypeScript.value = false
diff --git a/zap/Cargo.toml b/zap/Cargo.toml
index 28d53e80..52e10927 100644
--- a/zap/Cargo.toml
+++ b/zap/Cargo.toml
@@ -14,7 +14,7 @@ crate-type = ["lib", "cdylib"]
lalrpop = "0.20.0"
[dependencies]
-lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] }
+codespan-reporting = "0.11.0"
+lalrpop-util = "0.20.0"
num-traits = "0.2.17"
-thiserror = "1.0"
wasm-bindgen = "0.2"
diff --git a/zap/src/config.rs b/zap/src/config.rs
new file mode 100644
index 00000000..703c6378
--- /dev/null
+++ b/zap/src/config.rs
@@ -0,0 +1,283 @@
+use std::{collections::HashSet, fmt::Display};
+
+#[derive(Debug, Clone)]
+pub struct Config<'src> {
+ pub tydecls: Vec>,
+ pub evdecls: Vec>,
+
+ pub write_checks: bool,
+ pub typescript: bool,
+
+ pub server_output: Option<&'src str>,
+ pub client_output: Option<&'src str>,
+
+ pub casing: Casing,
+}
+
+impl<'src> Config<'src> {
+ pub fn event_id_ty(&self) -> NumTy {
+ NumTy::from_f64(1.0, self.evdecls.len() as f64)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Casing {
+ Pascal,
+ Camel,
+ Snake,
+}
+
+impl Casing {
+ pub fn with(&self, pascal: &'static str, camel: &'static str, snake: &'static str) -> &'static str {
+ match self {
+ Self::Pascal => pascal,
+ Self::Camel => camel,
+ Self::Snake => snake,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct EvDecl<'src> {
+ pub name: &'src str,
+ pub from: EvSource,
+ pub evty: EvType,
+ pub call: EvCall,
+ pub data: Ty<'src>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum EvSource {
+ Server,
+ Client,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum EvType {
+ Reliable,
+ Unreliable,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum EvCall {
+ SingleSync,
+ SingleAsync,
+ ManySync,
+ ManyAsync,
+}
+
+#[derive(Debug, Clone)]
+pub struct TyDecl<'src> {
+ pub name: &'src str,
+ pub ty: Ty<'src>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Ty<'src> {
+ Num(NumTy, Range),
+ Str(Range),
+ Arr(Box>, Range),
+ Map(Box>, Box>),
+ Opt(Box>),
+ Ref(&'src str),
+
+ Enum(Enum<'src>),
+ Struct(Struct<'src>),
+ Instance(Option<&'src str>),
+
+ Vector3,
+ Boolean,
+}
+
+impl<'src> Ty<'src> {
+ pub fn max_size(&self, config: &Config<'src>, recursed: &mut HashSet<&'src str>) -> Option {
+ match self {
+ Self::Num(numty, _) => Some(numty.size()),
+ Self::Vector3 => Some(NumTy::F32.size() * 3),
+ Self::Boolean => Some(1),
+ Self::Opt(ty) => ty.max_size(config, recursed).map(|size| size + 1),
+ Self::Str(len) => len.max().map(|len| len as usize),
+ Self::Arr(ty, range) => range
+ .max()
+ .and_then(|len| ty.max_size(config, recursed).map(|size| size * len as usize)),
+ Self::Map(..) => None,
+ Self::Enum(enum_ty) => enum_ty.max_size(config, recursed),
+ Self::Struct(struct_ty) => struct_ty.max_size(config, recursed),
+ Self::Instance(_) => Some(2),
+ Self::Ref(name) => {
+ if recursed.contains(name) {
+ None
+ } else {
+ recursed.insert(name);
+ config
+ .tydecls
+ .iter()
+ .find(|tydecl| tydecl.name == *name)
+ .and_then(|tydecl| tydecl.ty.max_size(config, recursed))
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Enum<'src> {
+ Unit(Vec<&'src str>),
+
+ Tagged {
+ tag: &'src str,
+ variants: Vec<(&'src str, Struct<'src>)>,
+ },
+}
+
+impl<'src> Enum<'src> {
+ pub fn max_size(&self, config: &Config<'src>, recursed: &mut HashSet<&'src str>) -> Option {
+ match self {
+ Self::Unit(vec) => Some(NumTy::from_f64(0.0, vec.len() as f64).size()),
+
+ Self::Tagged { variants, .. } => {
+ let mut size = NumTy::from_f64(0.0, variants.len() as f64).size();
+
+ for (_, ty) in variants {
+ if let Some(ty_size) = ty.max_size(config, recursed) {
+ size += ty_size;
+ } else {
+ return None;
+ }
+ }
+
+ Some(size)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Struct<'src> {
+ pub fields: Vec<(&'src str, Ty<'src>)>,
+}
+
+impl<'src> Struct<'src> {
+ pub fn max_size(&self, config: &Config<'src>, recursed: &mut HashSet<&'src str>) -> Option {
+ let mut size = 0;
+
+ for (_, ty) in &self.fields {
+ if let Some(ty_size) = ty.max_size(config, recursed) {
+ size += ty_size;
+ } else {
+ return None;
+ }
+ }
+
+ Some(size)
+ }
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct Range {
+ min: Option,
+ max: Option,
+}
+
+impl Range {
+ pub fn new(min: Option, max: Option) -> Self {
+ Self { min, max }
+ }
+
+ pub fn min(&self) -> Option {
+ self.min
+ }
+
+ pub fn max(&self) -> Option {
+ self.max
+ }
+
+ pub fn exact(&self) -> Option {
+ if self.min.is_some() && self.min == self.max {
+ Some(self.min.unwrap())
+ } else {
+ None
+ }
+ }
+}
+
+impl Display for Range {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match (self.min, self.max) {
+ (Some(min), Some(max)) => write!(f, "{}..{}", min, max),
+ (Some(min), None) => write!(f, "{}..", min),
+ (None, Some(max)) => write!(f, "..{}", max),
+ (None, None) => write!(f, ".."),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum NumTy {
+ F32,
+ F64,
+
+ U8,
+ U16,
+ U32,
+
+ I8,
+ I16,
+ I32,
+}
+
+impl NumTy {
+ pub fn from_f64(min: f64, max: f64) -> NumTy {
+ if min < 0.0 {
+ if max < 0.0 {
+ NumTy::I32
+ } else if max <= u8::MAX as f64 {
+ NumTy::I8
+ } else if max <= u16::MAX as f64 {
+ NumTy::I16
+ } else {
+ NumTy::I32
+ }
+ } else if max <= u8::MAX as f64 {
+ NumTy::U8
+ } else if max <= u16::MAX as f64 {
+ NumTy::U16
+ } else if max <= u32::MAX as f64 {
+ NumTy::U32
+ } else {
+ NumTy::F64
+ }
+ }
+
+ pub fn size(&self) -> usize {
+ match self {
+ NumTy::F32 => 4,
+ NumTy::F64 => 8,
+
+ NumTy::U8 => 1,
+ NumTy::U16 => 2,
+ NumTy::U32 => 4,
+
+ NumTy::I8 => 1,
+ NumTy::I16 => 2,
+ NumTy::I32 => 4,
+ }
+ }
+}
+
+impl Display for NumTy {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ NumTy::F32 => write!(f, "f32"),
+ NumTy::F64 => write!(f, "f64"),
+
+ NumTy::U8 => write!(f, "u8"),
+ NumTy::U16 => write!(f, "u16"),
+ NumTy::U32 => write!(f, "u32"),
+
+ NumTy::I8 => write!(f, "i8"),
+ NumTy::I16 => write!(f, "i16"),
+ NumTy::I32 => write!(f, "i32"),
+ }
+ }
+}
diff --git a/zap/src/irgen/des.rs b/zap/src/irgen/des.rs
new file mode 100644
index 00000000..5187354c
--- /dev/null
+++ b/zap/src/irgen/des.rs
@@ -0,0 +1,233 @@
+use crate::config::{Enum, NumTy, Struct, Ty};
+
+use super::{Expr, Gen, Stmt, Var};
+
+struct Des {
+ checks: bool,
+ buf: Vec,
+}
+
+impl Gen for Des {
+ fn push_stmt(&mut self, stmt: Stmt) {
+ self.buf.push(stmt);
+ }
+
+ fn gen(mut self, var: Var, ty: &Ty) -> Vec {
+ self.push_ty(ty, var);
+ self.buf
+ }
+}
+
+impl Des {
+ fn push_struct(&mut self, struct_ty: &Struct, into: Var) {
+ for (name, ty) in struct_ty.fields.iter() {
+ self.push_ty(ty, into.clone().nindex(*name))
+ }
+ }
+
+ fn push_enum(&mut self, enum_ty: &Enum, into: Var) {
+ match enum_ty {
+ Enum::Unit(enumerators) => {
+ let numty = NumTy::from_f64(0.0, enumerators.len() as f64 - 1.0);
+
+ self.push_local("enum_value", Some(self.readnumty(numty)));
+
+ for (i, enumerator) in enumerators.iter().enumerate() {
+ if i == 0 {
+ self.push_stmt(Stmt::If(Expr::from("enum_value").eq((i as f64).into())));
+ } else {
+ self.push_stmt(Stmt::ElseIf(Expr::from("enum_value").eq((i as f64).into())));
+ }
+
+ self.push_assign(into.clone(), Expr::Str(enumerator.to_string()));
+ }
+
+ self.push_stmt(Stmt::Else);
+ self.push_stmt(Stmt::Error("Invalid enumerator".into()));
+ self.push_stmt(Stmt::End);
+ }
+
+ Enum::Tagged { tag, variants } => {
+ let numty = NumTy::from_f64(0.0, variants.len() as f64 - 1.0);
+
+ self.push_local("enum_value", Some(self.readnumty(numty)));
+
+ for (i, (name, struct_ty)) in variants.iter().enumerate() {
+ if i == 0 {
+ self.push_stmt(Stmt::If(Expr::from("enum_value").eq((i as f64).into())));
+ } else {
+ self.push_stmt(Stmt::ElseIf(Expr::from("enum_value").eq((i as f64).into())));
+ }
+
+ self.push_assign(into.clone().nindex(*tag), Expr::Str(name.to_string()));
+ self.push_struct(struct_ty, into.clone());
+ }
+
+ self.push_stmt(Stmt::Else);
+ self.push_stmt(Stmt::Error("Invalid variant".into()));
+ self.push_stmt(Stmt::End);
+ }
+ }
+ }
+
+ fn push_ty(&mut self, ty: &Ty, into: Var) {
+ let into_expr = Expr::from(into.clone());
+
+ match ty {
+ Ty::Num(numty, range) => {
+ self.push_assign(into, self.readnumty(*numty));
+
+ if self.checks {
+ self.push_range_check(into_expr, *range);
+ }
+ }
+
+ Ty::Str(range) => {
+ if let Some(len) = range.exact() {
+ self.push_assign(into, self.readstring(len.into()));
+ } else {
+ self.push_local("len", Some(self.readnumty(NumTy::U16)));
+
+ if self.checks {
+ self.push_range_check(Expr::from("len"), *range);
+ }
+
+ self.push_assign(into, self.readstring(Expr::from("len")));
+ }
+ }
+
+ Ty::Arr(ty, range) => {
+ self.push_assign(into.clone(), Expr::EmptyTable);
+
+ if let Some(len) = range.exact() {
+ self.push_stmt(Stmt::NumFor {
+ var: "i",
+ from: 1.0.into(),
+ to: len.into(),
+ });
+
+ self.push_ty(ty, into.clone().eindex("i".into()));
+ self.push_stmt(Stmt::End);
+ } else {
+ self.push_local("len", Some(self.readnumty(NumTy::U16)));
+
+ if self.checks {
+ self.push_range_check(Expr::from("len"), *range);
+ }
+
+ self.push_stmt(Stmt::NumFor {
+ var: "i",
+ from: 1.0.into(),
+ to: "len".into(),
+ });
+
+ self.push_ty(ty, into.clone().eindex("i".into()));
+ self.push_stmt(Stmt::End);
+ }
+ }
+
+ Ty::Map(key, val) => {
+ self.push_assign(into.clone(), Expr::EmptyTable);
+
+ self.push_stmt(Stmt::NumFor {
+ var: "_",
+ from: 1.0.into(),
+ to: self.readu16(),
+ });
+
+ self.push_local("key", None);
+ self.push_local("val", None);
+
+ self.push_ty(key, "key".into());
+ self.push_ty(val, "val".into());
+
+ self.push_assign(into.clone().eindex("key".into()), "val".into());
+
+ self.push_stmt(Stmt::End);
+ }
+
+ Ty::Opt(ty) => {
+ self.push_stmt(Stmt::If(self.readu8().eq(1.0.into())));
+
+ match **ty {
+ Ty::Instance(class) => {
+ self.push_assign(into.clone(), Var::from("incoming_inst").eindex(self.readu16()).into());
+
+ if self.checks && class.is_some() {
+ self.push_assert(
+ into_expr.clone().eq(Expr::Nil).or(Expr::Call(
+ Box::new(into.clone()),
+ Some("IsA".into()),
+ vec![class.unwrap().into()],
+ )),
+ None,
+ )
+ }
+ }
+
+ _ => self.push_ty(ty, into.clone()),
+ }
+
+ self.push_stmt(Stmt::Else);
+ self.push_assign(into, Expr::Nil);
+ self.push_stmt(Stmt::End);
+ }
+
+ Ty::Ref(name) => {
+ self.push_assign(
+ into,
+ Expr::Call(
+ Box::new(Var::from("types").nindex(format!("read_{name}"))),
+ None,
+ vec![],
+ ),
+ );
+ }
+
+ Ty::Enum(enum_ty) => {
+ self.push_assign(into.clone(), Expr::EmptyTable);
+ self.push_enum(enum_ty, into)
+ }
+
+ Ty::Struct(struct_ty) => {
+ self.push_assign(into.clone(), Expr::EmptyTable);
+ self.push_struct(struct_ty, into)
+ }
+
+ Ty::Instance(class) => {
+ self.push_assign(into.clone(), Var::from("incoming_inst").eindex(self.readu16()).into());
+
+ // always assert non-optional instances as roblox
+ // will sometimes vaporize them
+ self.push_assert(into_expr.clone().neq(Expr::Nil), None);
+
+ if self.checks && class.is_some() {
+ self.push_assert(
+ into_expr.clone().eq(Expr::Nil).or(Expr::Call(
+ Box::new(into),
+ Some("IsA".into()),
+ vec![class.unwrap().into()],
+ )),
+ None,
+ )
+ }
+ }
+
+ Ty::Boolean => self.push_assign(into, self.readu8().eq(1.0.into())),
+ Ty::Vector3 => {
+ self.push_local("x", Some(self.readnumty(NumTy::F32)));
+ self.push_local("y", Some(self.readnumty(NumTy::F32)));
+ self.push_local("z", Some(self.readnumty(NumTy::F32)));
+
+ self.push_assign(
+ into,
+ Expr::Vector3(Box::new("x".into()), Box::new("y".into()), Box::new("z".into())),
+ );
+ }
+ }
+ }
+}
+
+pub fn gen(ty: &Ty, var: &str, checks: bool) -> Vec {
+ Des { checks, buf: vec![] }.gen(var.into(), ty)
+}
diff --git a/zap/src/irgen/gen.rs b/zap/src/irgen/gen.rs
deleted file mode 100644
index b6008215..00000000
--- a/zap/src/irgen/gen.rs
+++ /dev/null
@@ -1,598 +0,0 @@
-use super::{Expr, Stmt, Var};
-use crate::{
- parser::Ty,
- util::{NumTy, Range},
-};
-
-fn local(stmts: &mut Vec, name: &'static str, expr: Option) {
- // local =
- stmts.push(Stmt::Local(name, expr));
-}
-
-fn assign(stmts: &mut Vec, var: Var, expr: Expr) {
- // =
- stmts.push(Stmt::Assign(var, expr));
-}
-
-fn assert(stmts: &mut Vec, cond: Expr, msg: Option) {
- // assert(, )
- stmts.push(Stmt::Assert(cond, msg));
-}
-
-fn buffer_writef32(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(4)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![4.0.into()])));
-
- // buffer.writef32(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writef32"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writef64(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(8)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![8.0.into()])));
-
- // buffer.writef64(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writef64"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writeu8(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(1)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![1.0.into()])));
-
- // buffer.writeu8(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writeu8"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writeu16(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(2)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![2.0.into()])));
-
- // buffer.writeu16(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writeu16"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writeu32(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(4)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![4.0.into()])));
-
- // buffer.writeu32(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writeu32"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writei8(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(1)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![1.0.into()])));
-
- // buffer.writei8(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writei8"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writei16(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(2)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![2.0.into()])));
-
- // buffer.writei16(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writei16"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writei32(stmts: &mut Vec, val: Expr) {
- // local pos = alloc(4)
- local(stmts, "pos", Some(Var::from("alloc").call(vec![4.0.into()])));
-
- // buffer.writei32(outgoing_buff, pos, )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writei32"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val],
- ));
-}
-
-fn buffer_writenumty(stmts: &mut Vec, val: Expr, numty: NumTy) {
- match numty {
- NumTy::F32 => buffer_writef32(stmts, val),
- NumTy::F64 => buffer_writef64(stmts, val),
-
- NumTy::U8 => buffer_writeu8(stmts, val),
- NumTy::U16 => buffer_writeu16(stmts, val),
- NumTy::U32 => buffer_writeu32(stmts, val),
-
- NumTy::I8 => buffer_writei8(stmts, val),
- NumTy::I16 => buffer_writei16(stmts, val),
- NumTy::I32 => buffer_writei32(stmts, val),
- }
-}
-
-fn buffer_writestring(stmts: &mut Vec, val: Expr, count: Expr) {
- // local pos = alloc()
- local(stmts, "pos", Some(Var::from("alloc").call(vec![count.clone()])));
-
- // buffer.writestring(outgoing_buff, pos, , )
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writestring"),
- None,
- vec!["outgoing_buff".into(), "pos".into(), val, count],
- ));
-}
-
-fn buffer_readf32() -> Expr {
- // buffer.readf32(incoming_buff, read(4))
- Var::from("buffer")
- .nindex("readf32")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
-}
-
-fn buffer_readf64() -> Expr {
- // buffer.readf64(incoming_buff, read(8))
- Var::from("buffer")
- .nindex("readf64")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![8.0.into()])])
-}
-
-fn buffer_readu8() -> Expr {
- // buffer.readu8(incoming_buff, read(1))
- Var::from("buffer")
- .nindex("readu8")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![1.0.into()])])
-}
-
-fn buffer_readu16() -> Expr {
- // buffer.readu16(incoming_buff, read(2))
- Var::from("buffer")
- .nindex("readu16")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![2.0.into()])])
-}
-
-fn buffer_readu32() -> Expr {
- // buffer.readu32(incoming_buff, read(4))
- Var::from("buffer")
- .nindex("readu32")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
-}
-
-fn buffer_readi8() -> Expr {
- // buffer.readi8(incoming_buff, read(1))
- Var::from("buffer")
- .nindex("readi8")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![1.0.into()])])
-}
-
-fn buffer_readi16() -> Expr {
- // buffer.readi16(incoming_buff, read(2))
- Var::from("buffer")
- .nindex("readi16")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![2.0.into()])])
-}
-
-fn buffer_readi32() -> Expr {
- // buffer.readi32(incoming_buff, read(4))
- Var::from("buffer")
- .nindex("readi32")
- .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
-}
-
-fn buffer_readnumty(numty: NumTy) -> Expr {
- match numty {
- NumTy::F32 => buffer_readf32(),
- NumTy::F64 => buffer_readf64(),
-
- NumTy::U8 => buffer_readu8(),
- NumTy::U16 => buffer_readu16(),
- NumTy::U32 => buffer_readu32(),
-
- NumTy::I8 => buffer_readi8(),
- NumTy::I16 => buffer_readi16(),
- NumTy::I32 => buffer_readi32(),
- }
-}
-
-fn buffer_readstring(count: Expr) -> Expr {
- // buffer.readstring(incoming_buff, read(), )
- Var::from("buffer").nindex("readstring").call(vec![
- "incoming_buff".into(),
- Var::from("read").call(vec![count.clone()]),
- count,
- ])
-}
-
-fn range_check(stmts: &mut Vec, val: Expr, range: Range) {
- if let Some(min) = range.min() {
- assert(stmts, val.clone().gte(min.into()), None);
- }
-
- if let Some(max) = range.max() {
- assert(stmts, val.lte(max.into()), None);
- }
-}
-
-pub fn gen_ser(ty: &Ty, from: Var, gen_checks: bool) -> Vec {
- let mut stmts = Vec::new();
- let from_expr = Expr::Var(Box::new(from.clone()));
-
- if gen_checks {
- match ty {
- Ty::F32(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
- Ty::F64(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
-
- Ty::U8(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
- Ty::U16(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
- Ty::U32(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
-
- Ty::I8(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
- Ty::I16(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
- Ty::I32(range) => range_check(&mut stmts, from_expr.clone(), range.cast()),
-
- _ => {}
- };
- }
-
- match ty {
- Ty::Bool => buffer_writeu8(&mut stmts, from_expr.and(1.0.into()).or(0.0.into())),
-
- Ty::F32(..) => buffer_writef32(&mut stmts, from_expr),
- Ty::F64(..) => buffer_writef64(&mut stmts, from_expr),
-
- Ty::U8(..) => buffer_writeu8(&mut stmts, from_expr),
- Ty::U16(..) => buffer_writeu16(&mut stmts, from_expr),
- Ty::U32(..) => buffer_writeu32(&mut stmts, from_expr),
-
- Ty::I8(..) => buffer_writei8(&mut stmts, from_expr),
- Ty::I16(..) => buffer_writei16(&mut stmts, from_expr),
- Ty::I32(..) => buffer_writei32(&mut stmts, from_expr),
-
- Ty::Str { len } => {
- if let Some(len) = len.exact_f64() {
- if gen_checks {
- assert(&mut stmts, from_expr.clone().len().eq(len.into()), None);
- }
-
- buffer_writestring(&mut stmts, from_expr, len.into())
- } else {
- local(&mut stmts, "len", Some(from_expr.clone().len()));
-
- if gen_checks {
- range_check(&mut stmts, "len".into(), len.cast());
- }
-
- buffer_writeu16(&mut stmts, "len".into());
- buffer_writestring(&mut stmts, from_expr, "len".into())
- }
- }
-
- Ty::Arr { ty, len } => {
- if let Some(len) = len.exact_f64() {
- if gen_checks {
- assert(&mut stmts, from_expr.clone().len().eq(len.into()), None);
- }
-
- stmts.push(Stmt::NumFor {
- var: "i",
- from: 1.0.into(),
- to: len.into(),
- });
-
- stmts.extend(gen_ser(ty, from.eindex("i".into()), gen_checks));
- stmts.push(Stmt::End);
- } else {
- local(&mut stmts, "len", Some(from_expr.clone().len()));
-
- if gen_checks {
- range_check(&mut stmts, "len".into(), len.cast());
- }
-
- buffer_writeu16(&mut stmts, "len".into());
-
- stmts.push(Stmt::NumFor {
- var: "i",
- from: 1.0.into(),
- to: "len".into(),
- });
-
- stmts.extend(gen_ser(ty, from.eindex("i".into()), gen_checks));
- stmts.push(Stmt::End);
- }
- }
-
- Ty::Map { key, val } => {
- local(&mut stmts, "len_pos", Some(Var::from("alloc").call(vec![2.0.into()])));
- local(&mut stmts, "len", Some(0.0.into()));
-
- stmts.push(Stmt::GenFor {
- key: "k",
- val: "v",
- obj: from_expr,
- });
-
- assign(&mut stmts, "len".into(), Expr::from("len").add(1.0.into()));
- stmts.extend(gen_ser(key, "k".into(), gen_checks));
- stmts.extend(gen_ser(val, "v".into(), gen_checks));
-
- stmts.push(Stmt::End);
-
- stmts.push(Stmt::Call(
- Var::from("buffer").nindex("writeu16"),
- None,
- vec!["outgoing_buff".into(), "len_pos".into(), "len".into()],
- ));
- }
-
- Ty::Struct { fields } => {
- for (name, ty) in fields {
- stmts.extend(gen_ser(ty, from.clone().nindex(name), gen_checks));
- }
- }
-
- Ty::Enum { variants } => {
- if !variants.is_empty() {
- let numty = NumTy::from_f64(0.0, variants.len() as f64 - 1.0);
-
- for (i, name) in variants.iter().enumerate() {
- if i == 0 {
- stmts.push(Stmt::If(from_expr.clone().eq(name.clone().into())));
- } else {
- stmts.push(Stmt::ElseIf(from_expr.clone().eq(name.clone().into())));
- }
-
- buffer_writenumty(&mut stmts, (i as f64).into(), numty);
- }
-
- stmts.push(Stmt::Else);
- stmts.push(Stmt::Error("invalid enum variant!".to_string()));
- stmts.push(Stmt::End);
- }
- }
-
- Ty::Instance(_, class) => {
- if gen_checks && class.is_some() {
- assert(
- &mut stmts,
- Expr::Call(Box::new(from), Some("IsA".into()), vec![class.clone().unwrap().into()]),
- None,
- );
- }
-
- buffer_writeu16(&mut stmts, Var::from("alloc_inst").call(vec![from_expr]))
- }
-
- Ty::Vector3 => {
- buffer_writef32(&mut stmts, from.clone().nindex("X").into());
- buffer_writef32(&mut stmts, from.clone().nindex("Y").into());
- buffer_writef32(&mut stmts, from.clone().nindex("Z").into());
- }
-
- Ty::Ref(name) => stmts.push(Stmt::Call(
- Var::from("types").nindex(format!("write_{name}")),
- None,
- vec![from_expr],
- )),
-
- Ty::Optional(ty) => {
- stmts.push(Stmt::If(from_expr.clone().eq(Expr::Nil)));
-
- buffer_writeu8(&mut stmts, 0.0.into());
-
- stmts.push(Stmt::Else);
-
- buffer_writeu8(&mut stmts, 1.0.into());
- stmts.extend(gen_ser(ty, from, gen_checks));
-
- stmts.push(Stmt::End);
- }
- }
-
- stmts
-}
-
-pub fn gen_des(ty: &Ty, to: Var, gen_checks: bool) -> Vec {
- let mut stmts = Vec::new();
-
- match ty {
- Ty::Bool => assign(&mut stmts, to.clone(), buffer_readu8().neq(0.0.into())),
-
- Ty::F32(..) => assign(&mut stmts, to.clone(), buffer_readf32()),
- Ty::F64(..) => assign(&mut stmts, to.clone(), buffer_readf64()),
-
- Ty::U8(..) => assign(&mut stmts, to.clone(), buffer_readu8()),
- Ty::U16(..) => assign(&mut stmts, to.clone(), buffer_readu16()),
- Ty::U32(..) => assign(&mut stmts, to.clone(), buffer_readu32()),
-
- Ty::I8(..) => assign(&mut stmts, to.clone(), buffer_readi8()),
- Ty::I16(..) => assign(&mut stmts, to.clone(), buffer_readi16()),
- Ty::I32(..) => assign(&mut stmts, to.clone(), buffer_readi32()),
-
- Ty::Str { len } => {
- if let Some(len) = len.exact_f64() {
- assign(&mut stmts, to.clone(), buffer_readstring(len.into()));
- } else {
- local(&mut stmts, "len", Some(buffer_readu16()));
-
- if gen_checks {
- range_check(&mut stmts, "len".into(), len.cast());
- }
-
- assign(&mut stmts, to.clone(), buffer_readstring("len".into()));
- }
- }
-
- Ty::Arr { len, ty } => {
- assign(&mut stmts, to.clone(), Expr::EmptyTab);
-
- if let Some(len) = len.exact_f64() {
- stmts.push(Stmt::NumFor {
- var: "i",
- from: 1.0.into(),
- to: len.into(),
- });
-
- stmts.extend(gen_des(ty, to.clone().eindex("i".into()), gen_checks));
- stmts.push(Stmt::End);
- } else {
- local(&mut stmts, "len", Some(buffer_readu16()));
-
- if gen_checks {
- range_check(&mut stmts, "len".into(), len.cast());
- }
-
- stmts.push(Stmt::NumFor {
- var: "i",
- from: 1.0.into(),
- to: "len".into(),
- });
-
- stmts.extend(gen_des(ty, to.clone().eindex("i".into()), gen_checks));
- stmts.push(Stmt::End);
- }
- }
-
- Ty::Map { key, val } => {
- assign(&mut stmts, to.clone(), Expr::EmptyTab);
-
- stmts.push(Stmt::NumFor {
- var: "_",
- from: 0.0.into(),
- to: buffer_readu16(),
- });
-
- local(&mut stmts, "k", None);
- local(&mut stmts, "v", None);
-
- stmts.extend(gen_des(key, "k".into(), gen_checks));
- stmts.extend(gen_des(val, "v".into(), gen_checks));
-
- assign(&mut stmts, to.clone().eindex("k".into()), "v".into());
-
- stmts.push(Stmt::End);
- }
-
- Ty::Struct { fields } => {
- assign(&mut stmts, to.clone(), Expr::EmptyTab);
-
- for (name, ty) in fields {
- stmts.extend(gen_des(ty, to.clone().nindex(name), gen_checks));
- }
- }
-
- Ty::Enum { variants } => {
- if !variants.is_empty() {
- let numty = NumTy::from_f64(0.0, variants.len() as f64 - 1.0);
-
- assign(&mut stmts, to.clone(), buffer_readnumty(numty));
-
- for (i, name) in variants.iter().enumerate() {
- if i == 0 {
- stmts.push(Stmt::If(Expr::from(to.clone()).eq((i as f64).into())));
- } else {
- stmts.push(Stmt::ElseIf(Expr::from(to.clone()).eq((i as f64).into())));
- }
-
- assign(&mut stmts, to.clone(), name.clone().into());
- }
-
- stmts.push(Stmt::Else);
- stmts.push(Stmt::Error("invalid enum variant!".to_string()));
- stmts.push(Stmt::End);
- }
- }
-
- Ty::Instance(strict, class) => {
- assign(
- &mut stmts,
- to.clone(),
- Var::from("incoming_inst").eindex(buffer_readu16()).into(),
- );
-
- // Assert that the instance is not nil even if we don't want checks
- // because roblox cannot ensure the instance's existance at the destination.
- // Only do this for strict instances, optional instances can be nil.
- if *strict {
- assert(&mut stmts, Expr::from(to.clone()).neq(Expr::Nil), None);
- }
-
- if gen_checks && class.is_some() {
- assert(
- &mut stmts,
- Expr::Call(
- to.clone().into(),
- Some("IsA".into()),
- vec![class.clone().unwrap().into()],
- ),
- None,
- );
- }
- }
-
- Ty::Vector3 => {
- local(&mut stmts, "X", Some(buffer_readf32()));
- local(&mut stmts, "Y", Some(buffer_readf32()));
- local(&mut stmts, "Z", Some(buffer_readf32()));
-
- assign(
- &mut stmts,
- to.clone(),
- Expr::Vector3(Box::new("X".into()), Box::new("Y".into()), Box::new("Z".into())),
- );
- }
-
- Ty::Ref(name) => assign(
- &mut stmts,
- to.clone(),
- Var::from("types").nindex(format!("read_{name}")).call(vec![]),
- ),
-
- Ty::Optional(ty) => {
- stmts.push(Stmt::If(buffer_readu8().neq(0.0.into())));
-
- stmts.extend(gen_des(ty, to.clone(), gen_checks));
-
- stmts.push(Stmt::Else);
- assign(&mut stmts, to.clone(), Expr::Nil);
- stmts.push(Stmt::End);
- }
- }
-
- if gen_checks {
- match ty {
- Ty::F32(range) => range_check(&mut stmts, to.into(), range.cast()),
- Ty::F64(range) => range_check(&mut stmts, to.into(), range.cast()),
-
- Ty::U8(range) => range_check(&mut stmts, to.into(), range.cast()),
- Ty::U16(range) => range_check(&mut stmts, to.into(), range.cast()),
- Ty::U32(range) => range_check(&mut stmts, to.into(), range.cast()),
-
- Ty::I8(range) => range_check(&mut stmts, to.into(), range.cast()),
- Ty::I16(range) => range_check(&mut stmts, to.into(), range.cast()),
- Ty::I32(range) => range_check(&mut stmts, to.into(), range.cast()),
-
- _ => {}
- };
- }
-
- stmts
-}
diff --git a/zap/src/irgen/mod.rs b/zap/src/irgen/mod.rs
index 30167fd0..3cb6dfa7 100644
--- a/zap/src/irgen/mod.rs
+++ b/zap/src/irgen/mod.rs
@@ -1,9 +1,209 @@
#![allow(dead_code)]
-use std::fmt::Display;
+use std::{fmt::Display, vec};
-mod gen;
+use crate::config::{NumTy, Range, Ty};
-pub use gen::{gen_des, gen_ser};
+pub mod des;
+pub mod ser;
+
+pub trait Gen {
+ fn push_stmt(&mut self, stmt: Stmt);
+ fn gen(self, var: Var, ty: &Ty<'_>) -> Vec;
+
+ fn push_local(&mut self, name: &'static str, expr: Option) {
+ self.push_stmt(Stmt::Local(name, expr))
+ }
+
+ fn push_assign(&mut self, var: Var, expr: Expr) {
+ self.push_stmt(Stmt::Assign(var, expr))
+ }
+
+ fn push_assert(&mut self, expr: Expr, msg: Option) {
+ self.push_stmt(Stmt::Assert(expr, msg))
+ }
+
+ fn push_writef32(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![4.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writef32"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writef64(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![8.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writef64"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writeu8(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![1.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writeu8"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writeu16(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![2.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writeu16"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writeu32(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![4.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writeu32"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writei8(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![1.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writei8"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writei16(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![2.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writei16"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writei32(&mut self, expr: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![4.0.into()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writei32"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr],
+ ));
+ }
+
+ fn push_writenumty(&mut self, expr: Expr, numty: NumTy) {
+ match numty {
+ NumTy::F32 => self.push_writef32(expr),
+ NumTy::F64 => self.push_writef64(expr),
+ NumTy::U8 => self.push_writeu8(expr),
+ NumTy::U16 => self.push_writeu16(expr),
+ NumTy::U32 => self.push_writeu32(expr),
+ NumTy::I8 => self.push_writei8(expr),
+ NumTy::I16 => self.push_writei16(expr),
+ NumTy::I32 => self.push_writei32(expr),
+ }
+ }
+
+ fn push_writestring(&mut self, expr: Expr, count: Expr) {
+ self.push_local("pos", Some(Var::from("alloc").call(vec![count.clone()])));
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writestring"),
+ None,
+ vec!["outgoing_buff".into(), "pos".into(), expr, count],
+ ));
+ }
+
+ fn readf32(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readf32")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
+ }
+
+ fn readf64(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readf64")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![8.0.into()])])
+ }
+
+ fn readu8(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readu8")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![1.0.into()])])
+ }
+
+ fn readu16(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readu16")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![2.0.into()])])
+ }
+
+ fn readu32(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readu32")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
+ }
+
+ fn readi8(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readi8")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![1.0.into()])])
+ }
+
+ fn readi16(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readi16")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![2.0.into()])])
+ }
+
+ fn readi32(&self) -> Expr {
+ Var::from("buffer")
+ .nindex("readi32")
+ .call(vec!["incoming_buff".into(), Var::from("read").call(vec![4.0.into()])])
+ }
+
+ fn readnumty(&self, numty: NumTy) -> Expr {
+ match numty {
+ NumTy::F32 => self.readf32(),
+ NumTy::F64 => self.readf64(),
+ NumTy::U8 => self.readu8(),
+ NumTy::U16 => self.readu16(),
+ NumTy::U32 => self.readu32(),
+ NumTy::I8 => self.readi8(),
+ NumTy::I16 => self.readi16(),
+ NumTy::I32 => self.readi32(),
+ }
+ }
+
+ fn readstring(&self, count: Expr) -> Expr {
+ Var::from("buffer").nindex("readstring").call(vec![
+ "incoming_buff".into(),
+ Var::from("read").call(vec![count.clone()]),
+ count,
+ ])
+ }
+
+ fn push_range_check(&mut self, expr: Expr, range: Range) {
+ if let Some(min) = range.min() {
+ self.push_assert(expr.clone().gte(min.into()), None)
+ }
+
+ if let Some(max) = range.max() {
+ self.push_assert(expr.clone().lte(max.into()), None)
+ }
+ }
+}
#[derive(Debug, Clone)]
pub enum Stmt {
@@ -85,7 +285,7 @@ pub enum Expr {
Call(Box, Option, Vec),
// Table
- EmptyTab,
+ EmptyTable,
// Vector3
Vector3(Box, Box, Box),
@@ -208,7 +408,7 @@ impl Display for Expr {
),
},
- Self::EmptyTab => write!(f, "{{}}"),
+ Self::EmptyTable => write!(f, "{{}}"),
Self::Vector3(x, y, z) => write!(f, "Vector3.new({}, {}, {})", x, y, z),
diff --git a/zap/src/irgen/ser.rs b/zap/src/irgen/ser.rs
new file mode 100644
index 00000000..826078d7
--- /dev/null
+++ b/zap/src/irgen/ser.rs
@@ -0,0 +1,208 @@
+use crate::config::{Enum, NumTy, Struct, Ty};
+
+use super::{Expr, Gen, Stmt, Var};
+
+struct Ser {
+ checks: bool,
+ buf: Vec,
+}
+
+impl Gen for Ser {
+ fn push_stmt(&mut self, stmt: Stmt) {
+ self.buf.push(stmt);
+ }
+
+ fn gen(mut self, var: Var, ty: &Ty) -> Vec {
+ self.push_ty(ty, var);
+ self.buf
+ }
+}
+
+impl Ser {
+ fn push_struct(&mut self, struct_ty: &Struct, from: Var) {
+ for (name, ty) in struct_ty.fields.iter() {
+ self.push_ty(ty, from.clone().nindex(*name));
+ }
+ }
+
+ fn push_enum(&mut self, enum_ty: &Enum, from: Var) {
+ match enum_ty {
+ Enum::Unit(enumerators) => {
+ let from_expr = Expr::from(from.clone());
+ let numty = NumTy::from_f64(0.0, enumerators.len() as f64 - 1.0);
+
+ for (i, enumerator) in enumerators.iter().enumerate() {
+ if i == 0 {
+ self.push_stmt(Stmt::If(from_expr.clone().eq(Expr::Str(enumerator.to_string()))));
+ } else {
+ self.push_stmt(Stmt::ElseIf(from_expr.clone().eq(Expr::Str(enumerator.to_string()))));
+ }
+
+ self.push_writenumty((i as f64).into(), numty);
+ }
+
+ self.push_stmt(Stmt::Else);
+ self.push_stmt(Stmt::Error("Invalid enumerator".into()));
+ self.push_stmt(Stmt::End);
+ }
+
+ Enum::Tagged { tag, variants } => {
+ let tag_expr = Expr::from(from.clone().nindex(*tag));
+
+ for (i, variant) in variants.iter().enumerate() {
+ if i == 0 {
+ self.push_stmt(Stmt::If(tag_expr.clone().eq(Expr::Str(variant.0.to_string()))));
+ } else {
+ self.push_stmt(Stmt::ElseIf(tag_expr.clone().eq(Expr::Str(variant.0.to_string()))));
+ }
+
+ self.push_writeu8((i as f64).into());
+ self.push_struct(&variant.1, from.clone());
+ }
+
+ self.push_stmt(Stmt::Else);
+ self.push_stmt(Stmt::Error("Invalid variant".into()));
+ self.push_stmt(Stmt::End);
+ }
+ }
+ }
+
+ fn push_ty(&mut self, ty: &Ty, from: Var) {
+ let from_expr = Expr::from(from.clone());
+
+ match ty {
+ Ty::Num(numty, range) => {
+ if self.checks {
+ self.push_range_check(from_expr.clone(), *range);
+ }
+
+ self.push_writenumty(from_expr, *numty)
+ }
+
+ Ty::Str(range) => {
+ if let Some(len) = range.exact() {
+ if self.checks {
+ self.push_assert(from_expr.clone().len().eq(len.into()), None);
+ }
+
+ self.push_writestring(from_expr, len.into());
+ } else {
+ self.push_local("len", Some(from_expr.clone().len()));
+
+ if self.checks {
+ self.push_range_check("len".into(), *range);
+ }
+
+ self.push_writeu16("len".into());
+ self.push_writestring(from_expr, "len".into());
+ }
+ }
+
+ Ty::Arr(ty, range) => {
+ if let Some(len) = range.exact() {
+ if self.checks {
+ self.push_assert(from_expr.clone().len().eq(len.into()), None);
+ }
+
+ self.push_stmt(Stmt::NumFor {
+ var: "i",
+ from: 1.0.into(),
+ to: len.into(),
+ });
+
+ self.push_ty(ty, from.clone().eindex("i".into()));
+ self.push_stmt(Stmt::End);
+ } else {
+ self.push_local("len", Some(from_expr.clone().len()));
+
+ if self.checks {
+ self.push_range_check("len".into(), *range);
+ }
+
+ self.push_writeu16("len".into());
+
+ self.push_stmt(Stmt::NumFor {
+ var: "i",
+ from: 1.0.into(),
+ to: "len".into(),
+ });
+
+ self.push_ty(ty, from.clone().eindex("i".into()));
+ self.push_stmt(Stmt::End);
+ }
+ }
+
+ Ty::Map(key, val) => {
+ self.push_local("len_pos", Some(Var::from("alloc").call(vec![2.0.into()])));
+ self.push_local("len", Some(0.0.into()));
+
+ self.push_stmt(Stmt::GenFor {
+ key: "k",
+ val: "v",
+ obj: from_expr,
+ });
+
+ self.push_assign("len".into(), Expr::from("len").add(1.0.into()));
+ self.push_ty(key, "k".into());
+ self.push_ty(val, "v".into());
+
+ self.push_stmt(Stmt::End);
+
+ self.push_stmt(Stmt::Call(
+ Var::from("buffer").nindex("writeu16"),
+ None,
+ vec!["outgoing_buff".into(), "len_pos".into(), "len".into()],
+ ));
+ }
+
+ Ty::Opt(ty) => {
+ self.push_stmt(Stmt::If(from_expr.clone().eq(Expr::Nil)));
+
+ self.push_writeu8(0.0.into());
+
+ self.push_stmt(Stmt::Else);
+
+ self.push_writeu8(1.0.into());
+ self.push_ty(ty, from);
+
+ self.push_stmt(Stmt::End);
+ }
+
+ Ty::Ref(name) => self.push_stmt(Stmt::Call(
+ Var::from("types").nindex(format!("write_{name}")),
+ None,
+ vec![from_expr],
+ )),
+
+ Ty::Enum(enum_ty) => self.push_enum(enum_ty, from),
+ Ty::Struct(struct_ty) => self.push_struct(struct_ty, from),
+
+ Ty::Instance(class) => {
+ if self.checks && class.is_some() {
+ self.push_assert(
+ Expr::Call(
+ Box::new(from),
+ Some("IsA".into()),
+ vec![Expr::Str(class.unwrap().into())],
+ ),
+ None,
+ );
+ }
+
+ self.push_writeu16(Var::from("alloc_inst").call(vec![from_expr]))
+ }
+
+ Ty::Vector3 => {
+ self.push_writef32(from.clone().nindex("X").into());
+ self.push_writef32(from.clone().nindex("Y").into());
+ self.push_writef32(from.clone().nindex("Z").into());
+ }
+
+ Ty::Boolean => self.push_writeu8(from_expr.and(1.0.into()).or(0.0.into())),
+ }
+ }
+}
+
+pub fn gen(ty: &Ty, var: &str, checks: bool) -> Vec {
+ Ser { checks, buf: vec![] }.gen(var.into(), ty)
+}
diff --git a/zap/src/lib.rs b/zap/src/lib.rs
index 9603f749..d6671665 100644
--- a/zap/src/lib.rs
+++ b/zap/src/lib.rs
@@ -1,96 +1,122 @@
+mod config;
mod irgen;
mod output;
mod parser;
-mod util;
-use thiserror::Error;
+#[cfg(target_arch = "wasm32")]
+use wasm_bindgen::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
-use std::path::PathBuf;
-
+use codespan_reporting::diagnostic::Diagnostic;
#[cfg(target_arch = "wasm32")]
-use wasm_bindgen::prelude::*;
+use codespan_reporting::{
+ files::SimpleFile,
+ term::{self, termcolor::NoColor},
+};
-#[derive(Error, Debug)]
-pub enum Error {
- #[error("Unable to parse config file: {0}")]
- ParseError(String),
- #[error("File System error: {0}")]
- FSError(#[from] std::io::Error),
- #[error("Unknown type referenced: `{0}`")]
- UnknownTypeRef(String),
- #[error("Duplicate type declared: `{0}`")]
- DuplicateType(String),
- #[error("Duplicate event declared: `{0}`")]
- DuplicateEvent(String),
-}
+#[cfg(not(target_arch = "wasm32"))]
+use std::path::PathBuf;
#[derive(Debug)]
#[cfg(not(target_arch = "wasm32"))]
pub struct Output {
- pub path: PathBuf,
- pub contents: String,
- pub definitions: Option,
+ pub path: Option,
+ pub code: String,
+ pub defs: Option,
}
-#[derive(Debug, Clone)]
#[cfg(target_arch = "wasm32")]
+#[derive(Debug, Clone)]
#[wasm_bindgen(getter_with_clone)]
pub struct Output {
- pub contents: String,
- pub definitions: Option,
+ pub code: String,
+ pub defs: Option,
}
#[derive(Debug)]
-#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
+#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone), derive(Clone))]
pub struct Code {
pub server: Output,
pub client: Output,
}
+#[derive(Debug)]
#[cfg(not(target_arch = "wasm32"))]
-pub fn run(config: &str) -> Result {
- let file = parser::parse(config)?;
-
- let server_contents = output::luau::server::code(&file);
- let server_definitions = output::typescript::server::code(&file);
-
- let client_contents = output::luau::client::code(&file);
- let client_definitions = output::typescript::client::code(&file);
-
- Ok(Code {
- server: Output {
- path: file.server_output,
- contents: server_contents,
- definitions: server_definitions,
- },
- client: Output {
- path: file.client_output,
- contents: client_contents,
- definitions: client_definitions,
- },
- })
+pub struct Return {
+ pub code: Option,
+ pub diagnostics: Vec>,
+}
+
+#[cfg(target_arch = "wasm32")]
+#[derive(Debug)]
+#[wasm_bindgen(getter_with_clone)]
+pub struct Return {
+ pub code: Option,
+ pub diagnostics: String,
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+pub fn run(input: &str) -> Return {
+ let (config, reports) = parser::parse(input);
+
+ if let Some(config) = config {
+ Return {
+ code: Some(Code {
+ server: Output {
+ path: config.server_output.map(|p| p.into()),
+ code: output::luau::server::code(&config),
+ defs: output::typescript::server::code(&config),
+ },
+ client: Output {
+ path: config.client_output.map(|p| p.into()),
+ code: output::luau::client::code(&config),
+ defs: output::typescript::client::code(&config),
+ },
+ }),
+ diagnostics: reports.into_iter().map(|report| report.into()).collect(),
+ }
+ } else {
+ Return {
+ code: None,
+ diagnostics: reports.into_iter().map(|report| report.into()).collect(),
+ }
+ }
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
-pub fn run(config: &str) -> Result {
- let file = parser::parse(config)?;
-
- let server_contents = output::luau::server::code(&file);
- let server_definitions = output::typescript::server::code(&file);
-
- let client_contents = output::luau::client::code(&file);
- let client_definitions = output::typescript::client::code(&file);
-
- Ok(Code {
- server: Output {
- contents: server_contents,
- definitions: server_definitions,
- },
- client: Output {
- contents: client_contents,
- definitions: client_definitions,
- },
- })
+pub fn run(input: &str) -> Return {
+ let (config, reports) = parser::parse(input);
+
+ let mut writer = NoColor::new(Vec::new());
+
+ let file = SimpleFile::new("input.zap", input);
+ let term_config = term::Config::default();
+
+ for report in reports {
+ term::emit(&mut writer, &term_config, &file, &report.into()).unwrap();
+ }
+
+ let diagnostics = String::from_utf8(writer.into_inner()).unwrap();
+
+ if let Some(config) = config {
+ Return {
+ code: Some(Code {
+ server: Output {
+ code: output::luau::server::code(&config),
+ defs: output::typescript::server::code(&config),
+ },
+ client: Output {
+ code: output::luau::client::code(&config),
+ defs: output::typescript::client::code(&config),
+ },
+ }),
+ diagnostics,
+ }
+ } else {
+ Return {
+ code: None,
+ diagnostics,
+ }
+ }
}
diff --git a/zap/src/output/luau/base.luau b/zap/src/output/luau/base.luau
index f05aebb3..b1ef1b2d 100644
--- a/zap/src/output/luau/base.luau
+++ b/zap/src/output/luau/base.luau
@@ -66,4 +66,6 @@ local function load_empty()
outgoing_inst = {}
end
+load_empty()
+
local types = {}
diff --git a/zap/src/output/luau/client.rs b/zap/src/output/luau/client.rs
index 1e334127..20ae02eb 100644
--- a/zap/src/output/luau/client.rs
+++ b/zap/src/output/luau/client.rs
@@ -1,20 +1,19 @@
use crate::{
- irgen::{gen_des, gen_ser},
- parser::{EvCall, EvDecl, EvSource, EvType, File, TyDecl},
- util::casing,
+ config::{Config, EvCall, EvDecl, EvSource, EvType, TyDecl},
+ irgen::{des, ser},
};
use super::Output;
-struct ClientOutput<'a> {
- file: &'a File,
- buff: String,
+struct ClientOutput<'src> {
+ config: &'src Config<'src>,
tabs: u32,
+ buf: String,
}
impl<'a> Output for ClientOutput<'a> {
fn push(&mut self, s: &str) {
- self.buff.push_str(s);
+ self.buf.push_str(s);
}
fn indent(&mut self) {
@@ -32,12 +31,12 @@ impl<'a> Output for ClientOutput<'a> {
}
}
-impl<'a> ClientOutput<'a> {
- pub fn new(file: &'a File) -> Self {
+impl<'src> ClientOutput<'src> {
+ pub fn new(config: &'src Config<'src>) -> Self {
Self {
- file,
- buff: String::new(),
+ config,
tabs: 0,
+ buf: String::new(),
}
}
@@ -52,21 +51,21 @@ impl<'a> ClientOutput<'a> {
self.push_line(&format!("function types.write_{name}(value: {name})"));
self.indent();
- self.push_stmts(&gen_ser(ty, "value".into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, "value", self.config.write_checks));
self.dedent();
self.push_line("end");
self.push_line(&format!("function types.read_{name}()"));
self.indent();
self.push_line("local value;");
- self.push_stmts(&gen_des(ty, "value".into(), false));
+ self.push_stmts(&des::gen(ty, "value", false));
self.push_line("return value");
self.dedent();
self.push_line("end");
}
fn push_tydecls(&mut self) {
- for tydecl in self.file.ty_decls.iter() {
+ for tydecl in &self.config.tydecls {
self.push_tydecl(tydecl);
}
}
@@ -85,8 +84,8 @@ impl<'a> ClientOutput<'a> {
self.push_line(&format!(
"local id = buffer.read{}(buff, read({}))",
- self.file.event_id_ty(),
- self.file.event_id_ty().size()
+ self.config.event_id_ty(),
+ self.config.event_id_ty().size()
));
}
fn push_reliable_callback(&mut self, first: bool, ev: &EvDecl, id: usize) {
@@ -107,7 +106,7 @@ impl<'a> ClientOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&gen_des(&ev.data, "value".into(), false));
+ self.push_stmts(&des::gen(&ev.data, "value", true));
match ev.call {
EvCall::SingleSync => self.push_line(&format!("if events[{id}] then events[{id}](value) end")),
@@ -137,8 +136,8 @@ impl<'a> ClientOutput<'a> {
let mut first = true;
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server && ev_decl.evty == EvType::Reliable)
@@ -161,8 +160,8 @@ impl<'a> ClientOutput<'a> {
self.push_line(&format!(
"local id = buffer.read{}(buff, read({}))",
- self.file.event_id_ty(),
- self.file.event_id_ty().size()
+ self.config.event_id_ty(),
+ self.config.event_id_ty().size()
));
}
@@ -184,7 +183,7 @@ impl<'a> ClientOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&gen_des(&ev.data, "value".into(), true));
+ self.push_stmts(&des::gen(&ev.data, "value", self.config.write_checks));
match ev.call {
EvCall::SingleSync => self.push_line(&format!("if events[{id}] then events[{id}](value) end")),
@@ -212,8 +211,8 @@ impl<'a> ClientOutput<'a> {
let mut first = true;
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server && ev_decl.evty == EvType::Unreliable)
@@ -228,9 +227,9 @@ impl<'a> ClientOutput<'a> {
}
fn push_callback_lists(&mut self) {
- self.push_line(&format!("local events = table.create({})", self.file.ev_decls.len()));
+ self.push_line(&format!("local events = table.create({})", self.config.evdecls.len()));
- for (i, _) in self.file.ev_decls.iter().enumerate().filter(|(_, ev_decl)| {
+ for (i, _) in self.config.evdecls.iter().enumerate().filter(|(_, ev_decl)| {
ev_decl.from == EvSource::Server && matches!(ev_decl.call, EvCall::ManyAsync | EvCall::ManySync)
}) {
let id = i + 1;
@@ -240,18 +239,18 @@ impl<'a> ClientOutput<'a> {
}
fn push_write_event_id(&mut self, id: usize) {
- self.push_line(&format!("local pos = alloc({})", self.file.event_id_ty().size()));
+ self.push_line(&format!("local pos = alloc({})", self.config.event_id_ty().size()));
self.push_line(&format!(
"buffer.write{}(outgoing_buff, pos, {id})",
- self.file.event_id_ty()
+ self.config.event_id_ty()
));
}
fn push_return_fire(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let fire = casing(self.file.casing, "Fire", "fire", "fire");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire = self.config.casing.with("Fire", "fire", "fire");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire} = function({value}: "));
@@ -266,7 +265,7 @@ impl<'a> ClientOutput<'a> {
self.push_write_event_id(id);
- self.push_stmts(&gen_ser(ty, value.into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
if ev.evty == EvType::Unreliable {
self.push_line("local buff = buffer.create(outgoing_used)");
@@ -281,8 +280,8 @@ impl<'a> ClientOutput<'a> {
fn push_return_outgoing(&mut self) {
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
@@ -302,8 +301,8 @@ impl<'a> ClientOutput<'a> {
fn push_return_setcallback(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let set_callback = casing(self.file.casing, "SetCallback", "setCallback", "set_callback");
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
+ let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{set_callback} = function({callback}: ("));
@@ -320,8 +319,8 @@ impl<'a> ClientOutput<'a> {
fn push_return_on(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let on = casing(self.file.casing, "On", "on", "on");
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
+ let on = self.config.casing.with("On", "on", "on");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{on} = function({callback}: ("));
@@ -337,8 +336,8 @@ impl<'a> ClientOutput<'a> {
pub fn push_return_listen(&mut self) {
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
@@ -370,12 +369,13 @@ impl<'a> ClientOutput<'a> {
}
pub fn output(mut self) -> String {
- if self.file.ev_decls.is_empty() {
- return self.buff;
- };
-
self.push_file_header("Client");
+ if self.config.evdecls.is_empty() {
+ return self.buf;
+ };
+
+ self.push(include_str!("base.luau"));
self.push(include_str!("client.luau"));
self.push_tydecls();
@@ -383,8 +383,8 @@ impl<'a> ClientOutput<'a> {
self.push_callback_lists();
if self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Server)
{
@@ -392,8 +392,8 @@ impl<'a> ClientOutput<'a> {
}
if self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.any(|ev| ev.evty == EvType::Unreliable && ev.from == EvSource::Server)
{
@@ -402,10 +402,10 @@ impl<'a> ClientOutput<'a> {
self.push_return();
- self.buff
+ self.buf
}
}
-pub fn code(file: &File) -> String {
- ClientOutput::new(file).output()
+pub fn code(config: &Config) -> String {
+ ClientOutput::new(config).output()
}
diff --git a/zap/src/output/luau/mod.rs b/zap/src/output/luau/mod.rs
index c907a0de..574b4505 100644
--- a/zap/src/output/luau/mod.rs
+++ b/zap/src/output/luau/mod.rs
@@ -1,4 +1,7 @@
-use crate::{irgen::Stmt, parser::Ty};
+use crate::{
+ config::{Enum, Ty},
+ irgen::Stmt,
+};
pub mod client;
pub mod server;
@@ -72,29 +75,20 @@ pub trait Output {
}
fn push_ty(&mut self, ty: &Ty) {
- match ty {
- Ty::Bool => self.push("boolean"),
-
- Ty::F32(_) => self.push("number"),
- Ty::F64(_) => self.push("number"),
+ self.push("(");
- Ty::U8(_) => self.push("number"),
- Ty::U16(_) => self.push("number"),
- Ty::U32(_) => self.push("number"),
-
- Ty::I8(_) => self.push("number"),
- Ty::I16(_) => self.push("number"),
- Ty::I32(_) => self.push("number"),
+ match ty {
+ Ty::Num(..) => self.push("number"),
- Ty::Str { .. } => self.push("string"),
+ Ty::Str(..) => self.push("string"),
- Ty::Arr { ty, .. } => {
+ Ty::Arr(ty, ..) => {
self.push("{ ");
self.push_ty(ty);
self.push(" }");
}
- Ty::Map { key, val } => {
+ Ty::Map(key, val) => {
self.push("{ [");
self.push_ty(key);
self.push("]: ");
@@ -102,11 +96,56 @@ pub trait Output {
self.push(" }");
}
- Ty::Struct { fields } => {
+ Ty::Opt(ty) => {
+ self.push_ty(ty);
+ self.push("?");
+ }
+
+ Ty::Ref(name) => self.push(name),
+
+ Ty::Enum(enum_ty) => match enum_ty {
+ Enum::Unit(enumerators) => self.push(
+ &enumerators
+ .iter()
+ .map(|v| format!("\"{}\"", v))
+ .collect::>()
+ .join(" | ")
+ .to_string(),
+ ),
+
+ Enum::Tagged { tag, variants } => {
+ for (i, (name, struct_ty)) in variants.iter().enumerate() {
+ if i != 0 {
+ self.push(" | ");
+ }
+
+ self.push("{\n");
+ self.indent();
+
+ self.push_indent();
+
+ self.push(&format!("{tag}: \"{name}\",\n"));
+
+ for (name, ty) in struct_ty.fields.iter() {
+ self.push_indent();
+ self.push(&format!("{name}: "));
+ self.push_ty(ty);
+ self.push(",\n");
+ }
+
+ self.dedent();
+
+ self.push_indent();
+ self.push("}");
+ }
+ }
+ },
+
+ Ty::Struct(struct_ty) => {
self.push("{\n");
self.indent();
- for (name, ty) in fields.iter() {
+ for (name, ty) in struct_ty.fields.iter() {
self.push_indent();
self.push(&format!("{name}: "));
self.push_ty(ty);
@@ -118,31 +157,13 @@ pub trait Output {
self.push("}");
}
- Ty::Enum { variants } => self.push(
- &variants
- .iter()
- .map(|v| format!("\"{}\"", v))
- .collect::>()
- .join(" | ")
- .to_string(),
- ),
+ Ty::Instance(name) => self.push(name.unwrap_or("Instance")),
- Ty::Instance(strict, name) => {
- self.push(if let Some(name) = name { name } else { "Instance" });
-
- if *strict {
- self.push("?")
- }
- }
+ Ty::Boolean => self.push("boolean"),
Ty::Vector3 => self.push("Vector3"),
-
- Ty::Ref(name) => self.push(&name.to_string()),
-
- Ty::Optional(ty) => {
- self.push_ty(ty);
- self.push("?");
- }
}
+
+ self.push(")");
}
fn push_file_header(&mut self, scope: &str) {
@@ -153,7 +174,5 @@ pub trait Output {
"-- {scope} generated by Zap v{} (https://github.com/red-blox/zap)",
env!("CARGO_PKG_VERSION")
));
-
- self.push(include_str!("base.luau"));
}
}
diff --git a/zap/src/output/luau/server.rs b/zap/src/output/luau/server.rs
index 9ae8eb23..222bcabc 100644
--- a/zap/src/output/luau/server.rs
+++ b/zap/src/output/luau/server.rs
@@ -1,20 +1,19 @@
use crate::{
- irgen::{gen_des, gen_ser},
- parser::{EvCall, EvDecl, EvSource, EvType, File, TyDecl},
- util::casing,
+ config::{Config, EvCall, EvDecl, EvSource, EvType, TyDecl},
+ irgen::{des, ser},
};
use super::Output;
-struct ServerOutput<'a> {
- file: &'a File,
- buff: String,
+struct ServerOutput<'src> {
+ config: &'src Config<'src>,
tabs: u32,
+ buf: String,
}
impl<'a> Output for ServerOutput<'a> {
fn push(&mut self, s: &str) {
- self.buff.push_str(s);
+ self.buf.push_str(s);
}
fn indent(&mut self) {
@@ -33,11 +32,11 @@ impl<'a> Output for ServerOutput<'a> {
}
impl<'a> ServerOutput<'a> {
- pub fn new(file: &'a File) -> Self {
+ pub fn new(config: &'a Config) -> Self {
Self {
- file,
- buff: String::new(),
+ config,
tabs: 0,
+ buf: String::new(),
}
}
@@ -52,41 +51,41 @@ impl<'a> ServerOutput<'a> {
self.push_line(&format!("function types.write_{name}(value: {name})"));
self.indent();
- self.push_stmts(&gen_ser(ty, "value".into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, "value", self.config.write_checks));
self.dedent();
self.push_line("end");
self.push_line(&format!("function types.read_{name}()"));
self.indent();
self.push_line("local value;");
- self.push_stmts(&gen_des(ty, "value".into(), true));
+ self.push_stmts(&des::gen(ty, "value", true));
self.push_line("return value");
self.dedent();
self.push_line("end");
}
fn push_tydecls(&mut self) {
- for tydecl in self.file.ty_decls.iter() {
+ for tydecl in self.config.tydecls.iter() {
self.push_tydecl(tydecl);
}
}
fn push_reliable_header(&mut self) {
- self.push_line("reliable.OnServerEvent:Connect(function(player, buff, inst)");
+ self.push_line("reliable.OnServerEvent:Connect(function(player, buf, inst)");
self.indent();
- self.push_line("incoming_buff = buff");
+ self.push_line("incoming_buf = buf");
self.push_line("incoming_inst = inst");
self.push_line("incoming_read = 0");
- self.push_line("local len = buffer.len(buff)");
+ self.push_line("local len = bufer.len(buf)");
self.push_line("while incoming_read < len do");
self.indent();
self.push_line(&format!(
- "local id = buffer.read{}(buff, read({}))",
- self.file.event_id_ty(),
- self.file.event_id_ty().size()
+ "local id = bufer.read{}(buf, read({}))",
+ self.config.event_id_ty(),
+ self.config.event_id_ty().size()
));
}
@@ -108,7 +107,7 @@ impl<'a> ServerOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&gen_des(&ev.data, "value".into(), true));
+ self.push_stmts(&des::gen(&ev.data, "value", true));
match ev.call {
EvCall::SingleSync => self.push_line(&format!("if events[{id}] then events[{id}](player, value) end")),
@@ -142,8 +141,8 @@ impl<'a> ServerOutput<'a> {
let mut first = true;
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Reliable)
@@ -158,16 +157,16 @@ impl<'a> ServerOutput<'a> {
}
fn push_unreliable_header(&mut self) {
- self.push_line("unreliable.OnServerEvent:Connect(function(player, buff, inst)");
+ self.push_line("unreliable.OnServerEvent:Connect(function(player, buf, inst)");
self.indent();
- self.push_line("incoming_buff = buff");
+ self.push_line("incoming_buf = buf");
self.push_line("incoming_inst = inst");
self.push_line("incoming_read = 0");
self.push_line(&format!(
- "local id = buffer.read{}(buff, read({}))",
- self.file.event_id_ty(),
- self.file.event_id_ty().size()
+ "local id = bufer.read{}(buf, read({}))",
+ self.config.event_id_ty(),
+ self.config.event_id_ty().size()
));
}
@@ -189,7 +188,7 @@ impl<'a> ServerOutput<'a> {
self.indent();
self.push_line("local value");
- self.push_stmts(&gen_des(&ev.data, "value".into(), true));
+ self.push_stmts(&des::gen(&ev.data, "value", true));
match ev.call {
EvCall::SingleSync => self.push_line(&format!("if events[{id}] then events[{id}](player, value) end")),
@@ -221,8 +220,8 @@ impl<'a> ServerOutput<'a> {
let mut first = true;
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client && ev_decl.evty == EvType::Unreliable)
@@ -237,9 +236,9 @@ impl<'a> ServerOutput<'a> {
}
fn push_callback_lists(&mut self) {
- self.push_line(&format!("local events = table.create({})", self.file.ev_decls.len()));
+ self.push_line(&format!("local events = table.create({})", self.config.evdecls.len()));
- for (i, _) in self.file.ev_decls.iter().enumerate().filter(|(_, ev_decl)| {
+ for (i, _) in self.config.evdecls.iter().enumerate().filter(|(_, ev_decl)| {
ev_decl.from == EvSource::Client && matches!(ev_decl.call, EvCall::ManyAsync | EvCall::ManySync)
}) {
let id = i + 1;
@@ -249,19 +248,19 @@ impl<'a> ServerOutput<'a> {
}
fn push_write_event_id(&mut self, id: usize) {
- self.push_line(&format!("local pos = alloc({})", self.file.event_id_ty().size()));
+ self.push_line(&format!("local pos = alloc({})", self.config.event_id_ty().size()));
self.push_line(&format!(
- "buffer.write{}(outgoing_buff, pos, {id})",
- self.file.event_id_ty()
+ "bufer.write{}(outgoing_buf, pos, {id})",
+ self.config.event_id_ty()
));
}
fn push_return_fire(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let fire = casing(self.file.casing, "Fire", "fire", "fire");
- let player = casing(self.file.casing, "Player", "player", "player");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire = self.config.casing.with("Fire", "fire", "fire");
+ let player = self.config.casing.with("Player", "player", "player");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire} = function({player}: Player, {value}: "));
@@ -276,14 +275,14 @@ impl<'a> ServerOutput<'a> {
self.push_write_event_id(id);
- self.push_stmts(&gen_ser(ty, value.into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
match ev.evty {
EvType::Reliable => self.push_line(&format!("player_map[{player}] = save()")),
EvType::Unreliable => {
- self.push_line("local buff = buffer.create(outgoing_used)");
- self.push_line("buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)");
- self.push_line(&format!("unreliable:FireClient({player}, buff, outgoing_inst)"));
+ self.push_line("local buf = bufer.create(outgoing_used)");
+ self.push_line("bufer.copy(buf, 0, outgoing_buf, 0, outgoing_used)");
+ self.push_line(&format!("unreliable:FireClient({player}, buf, outgoing_inst)"));
}
}
@@ -294,8 +293,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_all(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let fire_all = casing(self.file.casing, "FireAll", "fireAll", "fire_all");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_all = self.config.casing.with("FireAll", "fireAll", "fire_all");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_all} = function({value}: "));
@@ -307,25 +306,25 @@ impl<'a> ServerOutput<'a> {
self.push_write_event_id(id);
- self.push_stmts(&gen_ser(ty, value.into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
match ev.evty {
EvType::Reliable => {
- self.push_line("local buff, used, inst = outgoing_buff, outgoing_used, outgoing_inst");
+ self.push_line("local buf, used, inst = outgoing_buf, outgoing_used, outgoing_inst");
self.push_line("for player, outgoing in player_map do");
self.indent();
self.push_line("load(outgoing)");
self.push_line("local pos = alloc(used)");
- self.push_line("buffer.copy(outgoing_buff, pos, buff, 0, used)");
+ self.push_line("bufer.copy(outgoing_buf, pos, buf, 0, used)");
self.push_line("player_map[player] = save()");
self.dedent();
self.push_line("end");
}
EvType::Unreliable => {
- self.push_line("local buff = buffer.create(outgoing_used)");
- self.push_line("buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)");
- self.push_line("unreliable:FireAllClients(buff, outgoing_inst)")
+ self.push_line("local buf = bufer.create(outgoing_used)");
+ self.push_line("bufer.copy(buf, 0, outgoing_buf, 0, outgoing_used)");
+ self.push_line("unreliable:FireAllClients(buf, outgoing_inst)")
}
}
@@ -336,9 +335,9 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_except(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let fire_except = casing(self.file.casing, "FireExcept", "fireExcept", "fire_except");
- let except = casing(self.file.casing, "Except", "except", "except");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except");
+ let except = self.config.casing.with("Except", "except", "except");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_except} = function({except}: Player, {value}: "));
@@ -350,18 +349,18 @@ impl<'a> ServerOutput<'a> {
self.push_write_event_id(id);
- self.push_stmts(&gen_ser(ty, value.into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
match ev.evty {
EvType::Reliable => {
- self.push_line("local buff, used, inst = outgoing_buff, outgoing_used, outgoing_inst");
+ self.push_line("local buf, used, inst = outgoing_buf, outgoing_used, outgoing_inst");
self.push_line("for player, outgoing in player_map do");
self.indent();
self.push_line(&format!("if player ~= {except} then"));
self.indent();
self.push_line("load(outgoing)");
self.push_line("local pos = alloc(used)");
- self.push_line("buffer.copy(outgoing_buff, pos, buff, 0, used)");
+ self.push_line("bufer.copy(outgoing_buf, pos, buf, 0, used)");
self.push_line("player_map[player] = save()");
self.dedent();
self.push_line("end");
@@ -370,13 +369,13 @@ impl<'a> ServerOutput<'a> {
}
EvType::Unreliable => {
- self.push_line("local buff = buffer.create(outgoing_used)");
- self.push_line("buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)");
+ self.push_line("local buf = bufer.create(outgoing_used)");
+ self.push_line("bufer.copy(buf, 0, outgoing_buf, 0, outgoing_used)");
self.push_line("for player in player_map do");
self.indent();
self.push_line(&format!("if player ~= {except} then"));
self.indent();
- self.push_line("unreliable:FireClient(player, buff, outgoing_inst)");
+ self.push_line("unreliable:FireClient(player, buf, outgoing_inst)");
self.dedent();
self.push_line("end");
self.dedent();
@@ -391,9 +390,9 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_list(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let fire_list = casing(self.file.casing, "FireList", "fireList", "fire_list");
- let list = casing(self.file.casing, "List", "list", "list");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_list = self.config.casing.with("FireList", "fireList", "fire_list");
+ let list = self.config.casing.with("List", "list", "list");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_list} = function({list}: {{ Player }}, {value}: "));
@@ -405,27 +404,27 @@ impl<'a> ServerOutput<'a> {
self.push_write_event_id(id);
- self.push_stmts(&gen_ser(ty, value.into(), self.file.write_checks));
+ self.push_stmts(&ser::gen(ty, value, self.config.write_checks));
match ev.evty {
EvType::Reliable => {
- self.push_line("local buff, used, inst = outgoing_buff, outgoing_used, outgoing_inst");
+ self.push_line("local buf, used, inst = outgoing_buf, outgoing_used, outgoing_inst");
self.push_line(&format!("for _, player in {list} do"));
self.indent();
self.push_line("load(player_map[player])");
self.push_line("local pos = alloc(used)");
- self.push_line("buffer.copy(outgoing_buff, pos, buff, 0, used)");
+ self.push_line("bufer.copy(outgoing_buf, pos, buf, 0, used)");
self.push_line("player_map[player] = save()");
self.dedent();
self.push_line("end");
}
EvType::Unreliable => {
- self.push_line("local buff = buffer.create(outgoing_used)");
- self.push_line("buffer.copy(buff, 0, outgoing_buff, 0, outgoing_used)");
+ self.push_line("local buf = bufer.create(outgoing_used)");
+ self.push_line("bufer.copy(buf, 0, outgoing_buf, 0, outgoing_used)");
self.push_line(&format!("for _, player in {list} do"));
self.indent();
- self.push_line("unreliable:FireClient(player, buff, outgoing_inst)");
+ self.push_line("unreliable:FireClient(player, buf, outgoing_inst)");
self.dedent();
self.push_line("end");
}
@@ -437,8 +436,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_outgoing(&mut self) {
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
@@ -461,8 +460,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_setcallback(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let set_callback = casing(self.file.casing, "SetCallback", "setCallback", "set_callback");
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
+ let set_callback = self.config.casing.with("SetCallback", "setCallback", "set_callback");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{set_callback} = function({callback}: (Player, "));
@@ -479,8 +478,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_on(&mut self, ev: &EvDecl, id: usize) {
let ty = &ev.data;
- let on = casing(self.file.casing, "On", "on", "on");
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
+ let on = self.config.casing.with("On", "on", "on");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
self.push_indent();
self.push(&format!("{on} = function({callback}: (Player, "));
@@ -496,8 +495,8 @@ impl<'a> ServerOutput<'a> {
pub fn push_return_listen(&mut self) {
for (i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
@@ -529,12 +528,13 @@ impl<'a> ServerOutput<'a> {
}
pub fn output(mut self) -> String {
- if self.file.ev_decls.is_empty() {
- return self.buff;
- };
-
self.push_file_header("Server");
+ if self.config.evdecls.is_empty() {
+ return self.buf;
+ };
+
+ self.push(include_str!("base.luau"));
self.push(include_str!("server.luau"));
self.push_tydecls();
@@ -542,8 +542,8 @@ impl<'a> ServerOutput<'a> {
self.push_callback_lists();
if self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.any(|ev| ev.evty == EvType::Reliable && ev.from == EvSource::Client)
{
@@ -551,8 +551,8 @@ impl<'a> ServerOutput<'a> {
}
if self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.any(|ev| ev.evty == EvType::Unreliable && ev.from == EvSource::Client)
{
@@ -561,10 +561,10 @@ impl<'a> ServerOutput<'a> {
self.push_return();
- self.buff
+ self.buf
}
}
-pub fn code(file: &File) -> String {
- ServerOutput::new(file).output()
+pub fn code(config: &Config) -> String {
+ ServerOutput::new(config).output()
}
diff --git a/zap/src/output/typescript/client.rs b/zap/src/output/typescript/client.rs
index 7fdd605f..00022895 100644
--- a/zap/src/output/typescript/client.rs
+++ b/zap/src/output/typescript/client.rs
@@ -1,19 +1,16 @@
-use crate::{
- parser::{EvCall, EvSource, File, TyDecl},
- util::casing,
-};
+use crate::config::{Config, EvCall, EvSource, TyDecl};
use super::Output;
-struct ClientOutput<'a> {
- file: &'a File,
- buff: String,
+struct ClientOutput<'src> {
+ config: &'src Config<'src>,
tabs: u32,
+ buf: String,
}
impl<'a> Output for ClientOutput<'a> {
fn push(&mut self, s: &str) {
- self.buff.push_str(s);
+ self.buf.push_str(s);
}
fn indent(&mut self) {
@@ -31,11 +28,11 @@ impl<'a> Output for ClientOutput<'a> {
}
}
-impl<'a> ClientOutput<'a> {
- pub fn new(file: &'a File) -> Self {
+impl<'src> ClientOutput<'src> {
+ pub fn new(config: &'src Config<'src>) -> Self {
Self {
- file,
- buff: String::new(),
+ config,
+ buf: String::new(),
tabs: 0,
}
}
@@ -51,25 +48,25 @@ impl<'a> ClientOutput<'a> {
}
fn push_tydecls(&mut self) {
- for tydecl in self.file.ty_decls.iter() {
+ for tydecl in &self.config.tydecls {
self.push_tydecl(tydecl);
}
- if !self.file.ty_decls.is_empty() {
+ if !self.config.tydecls.is_empty() {
self.push("\n")
}
}
fn push_return_outgoing(&mut self) {
for (_i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
{
- let fire = casing(self.file.casing, "Fire", "fire", "fire");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire = self.config.casing.with("Fire", "fire", "fire");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_line(&format!("export const {name}: {{", name = ev.name));
self.indent();
@@ -86,20 +83,20 @@ impl<'a> ClientOutput<'a> {
pub fn push_return_listen(&mut self) {
for (_i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
{
let set_callback = match ev.call {
EvCall::SingleSync | EvCall::SingleAsync => {
- casing(self.file.casing, "SetCallback", "setCallback", "set_callback")
+ self.config.casing.with("SetCallback", "setCallback", "set_callback")
}
- EvCall::ManySync | EvCall::ManyAsync => casing(self.file.casing, "On", "on", "on"),
+ EvCall::ManySync | EvCall::ManyAsync => self.config.casing.with("On", "on", "on"),
};
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_line(&format!("export const {name}: {{", name = ev.name));
self.indent();
@@ -120,24 +117,24 @@ impl<'a> ClientOutput<'a> {
}
pub fn output(mut self) -> String {
- if self.file.ev_decls.is_empty() {
- return self.buff;
- };
-
self.push_file_header("Client");
+ if self.config.evdecls.is_empty() {
+ return self.buf;
+ };
+
self.push_tydecls();
self.push_return();
- self.buff
+ self.buf
}
}
-pub fn code(file: &File) -> Option {
- if !file.typescript {
+pub fn code(config: &Config) -> Option {
+ if !config.typescript {
return None;
}
- Some(ClientOutput::new(file).output())
+ Some(ClientOutput::new(config).output())
}
diff --git a/zap/src/output/typescript/mod.rs b/zap/src/output/typescript/mod.rs
index c540dd58..35e0a21e 100644
--- a/zap/src/output/typescript/mod.rs
+++ b/zap/src/output/typescript/mod.rs
@@ -1,4 +1,4 @@
-use crate::parser::Ty;
+use crate::config::{Enum, Ty};
pub mod client;
pub mod server;
@@ -17,27 +17,15 @@ pub trait Output {
fn push_ty(&mut self, ty: &Ty) {
match ty {
- Ty::Bool => self.push("boolean"),
-
- Ty::F32(_) => self.push("number"),
- Ty::F64(_) => self.push("number"),
-
- Ty::U8(_) => self.push("number"),
- Ty::U16(_) => self.push("number"),
- Ty::U32(_) => self.push("number"),
-
- Ty::I8(_) => self.push("number"),
- Ty::I16(_) => self.push("number"),
- Ty::I32(_) => self.push("number"),
-
+ Ty::Num(..) => self.push("number"),
Ty::Str { .. } => self.push("string"),
- Ty::Arr { ty, .. } => {
+ Ty::Arr(ty, ..) => {
self.push_ty(ty);
self.push("[]");
}
- Ty::Map { key, val } => {
+ Ty::Map(key, val) => {
self.push("{ [index: ");
self.push_ty(key);
self.push("]: ");
@@ -45,11 +33,56 @@ pub trait Output {
self.push(" }");
}
- Ty::Struct { fields } => {
+ Ty::Opt(ty) => {
+ self.push_ty(ty);
+ self.push(" | undefined");
+ }
+
+ Ty::Ref(name) => self.push(name),
+
+ Ty::Enum(enum_ty) => match enum_ty {
+ Enum::Unit(enumerators) => self.push(
+ &enumerators
+ .iter()
+ .map(|v| format!("\"{}\"", v))
+ .collect::>()
+ .join(" | ")
+ .to_string(),
+ ),
+
+ Enum::Tagged { tag, variants } => {
+ for (i, (name, struct_ty)) in variants.iter().enumerate() {
+ if i != 0 {
+ self.push(" | ");
+ }
+
+ self.push("{\n");
+ self.indent();
+
+ self.push_indent();
+
+ self.push(&format!("{tag}: \"{name}\",\n"));
+
+ for (name, ty) in struct_ty.fields.iter() {
+ self.push_indent();
+ self.push(&format!("{name}: "));
+ self.push_ty(ty);
+ self.push(",\n");
+ }
+
+ self.dedent();
+
+ self.push_indent();
+ self.push("}");
+ }
+ }
+ },
+
+ Ty::Struct(struct_ty) => {
self.push("{\n");
self.indent();
- for (name, ty) in fields.iter() {
+ for (name, ty) in struct_ty.fields.iter() {
self.push_indent();
self.push(&format!("{name}: "));
self.push_ty(ty);
@@ -61,30 +94,10 @@ pub trait Output {
self.push("}");
}
- Ty::Enum { variants } => self.push(
- &variants
- .iter()
- .map(|v| format!("\"{}\"", v))
- .collect::>()
- .join(" | ")
- .to_string(),
- ),
-
- Ty::Instance(strict, name) => {
- self.push(if let Some(name) = name { name } else { "Instance" });
+ Ty::Instance(name) => self.push(name.unwrap_or("Instance")),
- if *strict {
- self.push(" | undefined")
- }
- }
+ Ty::Boolean => self.push("boolean"),
Ty::Vector3 => self.push("Vector3"),
-
- Ty::Ref(name) => self.push(&name.to_string()),
-
- Ty::Optional(ty) => {
- self.push_ty(ty);
- self.push(" | undefined");
- }
}
}
diff --git a/zap/src/output/typescript/server.rs b/zap/src/output/typescript/server.rs
index c710bb14..afd29408 100644
--- a/zap/src/output/typescript/server.rs
+++ b/zap/src/output/typescript/server.rs
@@ -1,19 +1,16 @@
-use crate::{
- parser::{EvCall, EvDecl, EvSource, File, TyDecl},
- util::casing,
-};
+use crate::config::{Config, EvCall, EvDecl, EvSource, TyDecl};
use super::Output;
-struct ServerOutput<'a> {
- file: &'a File,
- buff: String,
+struct ServerOutput<'src> {
+ config: &'src Config<'src>,
tabs: u32,
+ buf: String,
}
impl<'a> Output for ServerOutput<'a> {
fn push(&mut self, s: &str) {
- self.buff.push_str(s);
+ self.buf.push_str(s);
}
fn indent(&mut self) {
@@ -32,11 +29,11 @@ impl<'a> Output for ServerOutput<'a> {
}
impl<'a> ServerOutput<'a> {
- pub fn new(file: &'a File) -> Self {
+ pub fn new(config: &'a Config) -> Self {
Self {
- file,
- buff: String::new(),
+ config,
tabs: 0,
+ buf: String::new(),
}
}
@@ -51,11 +48,11 @@ impl<'a> ServerOutput<'a> {
}
fn push_tydecls(&mut self) {
- for tydecl in self.file.ty_decls.iter() {
+ for tydecl in self.config.tydecls.iter() {
self.push_tydecl(tydecl);
}
- if !self.file.ty_decls.is_empty() {
+ if !self.config.tydecls.is_empty() {
self.push("\n")
}
}
@@ -63,9 +60,9 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire(&mut self, ev: &EvDecl) {
let ty = &ev.data;
- let fire = casing(self.file.casing, "Fire", "fire", "fire");
- let player = casing(self.file.casing, "Player", "player", "player");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire = self.config.casing.with("Fire", "fire", "fire");
+ let player = self.config.casing.with("Player", "player", "player");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire}: ({player}: Player, {value}: "));
@@ -76,8 +73,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_all(&mut self, ev: &EvDecl) {
let ty = &ev.data;
- let fire_all = casing(self.file.casing, "FireAll", "fireAll", "fire_all");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_all = self.config.casing.with("FireAll", "fireAll", "fire_all");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_all}: ({value}: "));
@@ -88,9 +85,9 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_except(&mut self, ev: &EvDecl) {
let ty = &ev.data;
- let fire_except = casing(self.file.casing, "FireExcept", "fireExcept", "fire_except");
- let except = casing(self.file.casing, "Except", "except", "except");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_except = self.config.casing.with("FireExcept", "fireExcept", "fire_except");
+ let except = self.config.casing.with("Except", "except", "except");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_except}: ({except}: Player, {value}: "));
@@ -101,9 +98,9 @@ impl<'a> ServerOutput<'a> {
fn push_return_fire_list(&mut self, ev: &EvDecl) {
let ty = &ev.data;
- let fire_list = casing(self.file.casing, "FireList", "fireList", "fire_list");
- let list = casing(self.file.casing, "List", "list", "list");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let fire_list = self.config.casing.with("FireList", "fireList", "fire_list");
+ let list = self.config.casing.with("List", "list", "list");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{fire_list}: ({list}: Player[], {value}: "));
@@ -113,8 +110,8 @@ impl<'a> ServerOutput<'a> {
fn push_return_outgoing(&mut self) {
for (_i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Server)
@@ -134,8 +131,8 @@ impl<'a> ServerOutput<'a> {
pub fn push_return_listen(&mut self) {
for (_i, ev) in self
- .file
- .ev_decls
+ .config
+ .evdecls
.iter()
.enumerate()
.filter(|(_, ev_decl)| ev_decl.from == EvSource::Client)
@@ -145,13 +142,13 @@ impl<'a> ServerOutput<'a> {
let set_callback = match ev.call {
EvCall::SingleSync | EvCall::SingleAsync => {
- casing(self.file.casing, "SetCallback", "setCallback", "set_callback")
+ self.config.casing.with("SetCallback", "setCallback", "set_callback")
}
- EvCall::ManySync | EvCall::ManyAsync => casing(self.file.casing, "On", "on", "on"),
+ EvCall::ManySync | EvCall::ManyAsync => self.config.casing.with("On", "on", "on"),
};
- let callback = casing(self.file.casing, "Callback", "callback", "callback");
- let player = casing(self.file.casing, "Player", "player", "player");
- let value = casing(self.file.casing, "Value", "value", "value");
+ let callback = self.config.casing.with("Callback", "callback", "callback");
+ let player = self.config.casing.with("Player", "player", "player");
+ let value = self.config.casing.with("Value", "value", "value");
self.push_indent();
self.push(&format!("{set_callback}: ({callback}: ({player}: Player, {value}: "));
@@ -169,24 +166,24 @@ impl<'a> ServerOutput<'a> {
}
pub fn output(mut self) -> String {
- if self.file.ev_decls.is_empty() {
- return self.buff;
- };
-
self.push_file_header("Server");
+ if self.config.evdecls.is_empty() {
+ return self.buf;
+ };
+
self.push_tydecls();
self.push_return();
- self.buff
+ self.buf
}
}
-pub fn code(file: &File) -> Option {
- if !file.typescript {
+pub fn code(config: &Config) -> Option {
+ if !config.typescript {
return None;
}
- Some(ServerOutput::new(file).output())
+ Some(ServerOutput::new(config).output())
}
diff --git a/zap/src/parser/convert.rs b/zap/src/parser/convert.rs
new file mode 100644
index 00000000..c788372d
--- /dev/null
+++ b/zap/src/parser/convert.rs
@@ -0,0 +1,371 @@
+use std::collections::HashSet;
+
+use crate::config::{Casing, Config, Enum, EvDecl, NumTy, Range, Struct, Ty, TyDecl};
+
+use super::{
+ reports::Report,
+ syntax_tree::{
+ Spanned, SyntaxBoolLit, SyntaxConfig, SyntaxDecl, SyntaxEnum, SyntaxEnumKind, SyntaxEvDecl, SyntaxIdentifier,
+ SyntaxNumLit, SyntaxOptValueKind, SyntaxRange, SyntaxRangeKind, SyntaxStrLit, SyntaxStruct, SyntaxTy,
+ SyntaxTyDecl, SyntaxTyKind,
+ },
+};
+
+pub fn convert(syntax_config: SyntaxConfig<'_>) -> (Config<'_>, Vec) {
+ let mut state = ConvertState::new(
+ syntax_config
+ .decls
+ .iter()
+ .filter_map(|decl| match decl {
+ SyntaxDecl::Ty(tydecl) => Some(tydecl.name.name),
+
+ _ => None,
+ })
+ .collect(),
+ );
+
+ let config = syntax_config.into_config(&mut state);
+
+ (config, state.into_reports())
+}
+
+struct ConvertState<'src> {
+ reports: Vec>,
+ tydecls: HashSet<&'src str>,
+}
+
+impl<'src> ConvertState<'src> {
+ fn new(tydecls: HashSet<&'src str>) -> Self {
+ Self {
+ reports: Vec::new(),
+ tydecls,
+ }
+ }
+
+ fn push_report(&mut self, report: Report<'src>) {
+ self.reports.push(report);
+ }
+
+ fn into_reports(self) -> Vec> {
+ self.reports
+ }
+
+ fn tydecl_exists(&self, name: &'src str) -> bool {
+ self.tydecls.contains(name)
+ }
+}
+
+impl<'src> SyntaxConfig<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> Config<'src> {
+ let mut write_checks = false;
+ let mut typescript = false;
+
+ let mut server_output = None;
+ let mut client_output = None;
+
+ let mut casing = Casing::Pascal;
+
+ for opt in self.opts {
+ match opt.name.into_config() {
+ "write_checks" => match opt.value.kind {
+ SyntaxOptValueKind::Bool(value) => write_checks = value.into_config(),
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "boolean",
+ }),
+ },
+
+ "typescript" => match opt.value.kind {
+ SyntaxOptValueKind::Bool(value) => typescript = value.into_config(),
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "boolean",
+ }),
+ },
+
+ "server_output" => match opt.value.kind {
+ SyntaxOptValueKind::Str(value) => server_output = Some(value.into_config()),
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "string",
+ }),
+ },
+
+ "client_output" => match opt.value.kind {
+ SyntaxOptValueKind::Str(value) => client_output = Some(value.into_config()),
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "string",
+ }),
+ },
+
+ "casing" => match opt.value.kind {
+ SyntaxOptValueKind::Str(value) => match value.into_config() {
+ "PascalCase" => casing = Casing::Pascal,
+ "camelCase" => casing = Casing::Camel,
+ "snake_case" => casing = Casing::Snake,
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "`PascalCase`, `camelCase`, or `snake_case`",
+ }),
+ },
+
+ _ => state.push_report(Report::AnalyzeInvalidOptValue {
+ span: opt.value.span(),
+ expected: "`PascalCase`, `camelCase`, or `snake_case`",
+ }),
+ },
+
+ _ => state.push_report(Report::AnalyzeUnknownOptName { span: opt.name.span() }),
+ }
+ }
+
+ let mut tydecls = Vec::new();
+ let mut evdecls = Vec::new();
+
+ for decl in self.decls {
+ match decl {
+ SyntaxDecl::Ty(tydecl) => tydecls.push(tydecl.into_config(state)),
+ SyntaxDecl::Ev(evdecl) => evdecls.push(evdecl.into_config(state)),
+ }
+ }
+
+ Config {
+ tydecls,
+ evdecls,
+
+ write_checks,
+ typescript,
+
+ server_output,
+ client_output,
+
+ casing,
+ }
+ }
+}
+
+impl<'src> SyntaxEvDecl<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> EvDecl<'src> {
+ let name = self.name.into_config();
+ let from = self.from;
+ let evty = self.evty;
+ let call = self.call;
+ let data = self.data.into_config(state);
+
+ EvDecl {
+ name,
+ from,
+ evty,
+ call,
+ data,
+ }
+ }
+}
+
+impl<'src> SyntaxTyDecl<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> TyDecl<'src> {
+ let name = self.name.into_config();
+ let ty = self.ty.into_config(state);
+
+ TyDecl { name, ty }
+ }
+}
+
+impl<'src> SyntaxTy<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> Ty<'src> {
+ match self.kind {
+ SyntaxTyKind::Num(numty, range) => {
+ let range = range.map(|r| match numty {
+ NumTy::F32 => r.into_config_with_range(state, f32::MIN.into(), f32::MAX.into()),
+ NumTy::F64 => r.into_config_with_range(state, f64::MIN, f64::MAX),
+
+ NumTy::U8 => r.into_config_with_range(state, u8::MIN.into(), u8::MAX.into()),
+ NumTy::U16 => r.into_config_with_range(state, u16::MIN.into(), u16::MAX.into()),
+ NumTy::U32 => r.into_config_with_range(state, u32::MIN.into(), u32::MAX.into()),
+
+ NumTy::I8 => r.into_config_with_range(state, i8::MIN.into(), i8::MAX.into()),
+ NumTy::I16 => r.into_config_with_range(state, i16::MIN.into(), i16::MAX.into()),
+ NumTy::I32 => r.into_config_with_range(state, i32::MIN.into(), i32::MAX.into()),
+ });
+
+ Ty::Num(numty, range.unwrap_or_default())
+ }
+
+ SyntaxTyKind::Str(range) => Ty::Str(
+ range
+ .map(|r| r.into_config_with_range(state, u16::MIN.into(), u16::MAX.into()))
+ .unwrap_or_default(),
+ ),
+
+ SyntaxTyKind::Arr(ty, range) => Ty::Arr(
+ Box::new(ty.into_config(state)),
+ range
+ .map(|r| r.into_config_with_range(state, u16::MIN.into(), u16::MAX.into()))
+ .unwrap_or_default(),
+ ),
+
+ SyntaxTyKind::Map(key, val) => Ty::Map(Box::new(key.into_config(state)), Box::new(val.into_config(state))),
+ SyntaxTyKind::Opt(ty) => Ty::Opt(Box::new(ty.into_config(state))),
+
+ SyntaxTyKind::Ref(name) => match name.into_config() {
+ "boolean" => Ty::Boolean,
+ "Vector3" => Ty::Vector3,
+
+ _ => {
+ let name = name.into_config();
+
+ if !state.tydecl_exists(name) {
+ state.push_report(Report::AnalyzeUnknownTypeRef {
+ span: self.span(),
+ name,
+ });
+ }
+
+ Ty::Ref(name)
+ }
+ },
+
+ SyntaxTyKind::Enum(syntax_enum) => Ty::Enum(syntax_enum.into_config(state)),
+ SyntaxTyKind::Struct(syntax_struct) => Ty::Struct(syntax_struct.into_config(state)),
+ SyntaxTyKind::Instance(name) => Ty::Instance(name.map(|name| name.into_config())),
+ }
+ }
+}
+
+impl<'src> SyntaxEnum<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> Enum<'src> {
+ let span = self.span();
+
+ match self.kind {
+ SyntaxEnumKind::Unit(enumerators) => {
+ if enumerators.is_empty() {
+ state.push_report(Report::AnalyzeEmptyEnum { span })
+ }
+
+ Enum::Unit(enumerators.into_iter().map(|v| v.into_config()).collect())
+ }
+
+ SyntaxEnumKind::Tagged { tag, variants } => {
+ if variants.is_empty() {
+ state.push_report(Report::AnalyzeEmptyEnum { span })
+ }
+
+ let tag_name = tag.into_config();
+
+ Enum::Tagged {
+ tag: tag_name,
+ variants: variants
+ .into_iter()
+ .map(|(name, value)| {
+ if let Some(field) = value.fields.iter().find(|field| field.0.name == tag_name) {
+ state.push_report(Report::AnalyzeEnumTagUsed {
+ tag_span: tag.span(),
+ used_span: field.0.span(),
+ tag: tag_name,
+ });
+ }
+
+ (name.into_config(), value.into_config(state))
+ })
+ .collect(),
+ }
+ }
+ }
+ }
+}
+
+impl<'src> SyntaxStruct<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> Struct<'src> {
+ let mut fields = Vec::new();
+
+ for field in self.fields {
+ fields.push((field.0.into_config(), field.1.into_config(state)));
+ }
+
+ Struct { fields }
+ }
+}
+
+impl<'src> SyntaxRange<'src> {
+ fn into_config(self, state: &mut ConvertState<'src>) -> Range {
+ let range = match self.kind {
+ SyntaxRangeKind::None => Range::new(None, None),
+ SyntaxRangeKind::Exact(num) => Range::new(Some(num.into_config()), Some(num.into_config())),
+ SyntaxRangeKind::WithMin(min) => Range::new(Some(min.into_config()), None),
+ SyntaxRangeKind::WithMax(max) => Range::new(None, Some(max.into_config())),
+ SyntaxRangeKind::WithMinMax(min, max) => Range::new(Some(min.into_config()), Some(max.into_config())),
+ };
+
+ if range.min().is_some() && range.max().is_some() && range.min().unwrap() > range.max().unwrap() {
+ state.push_report(Report::AnalyzeInvalidRange { span: self.span() });
+ }
+
+ range
+ }
+
+ fn into_config_with_range(self, state: &mut ConvertState<'src>, min: f64, max: f64) -> Range {
+ let range = match self.kind {
+ SyntaxRangeKind::None => Range::new(None, None),
+ SyntaxRangeKind::Exact(num) => Range::new(
+ Some(num.into_config_with_range(state, min, max)),
+ Some(num.into_config_with_range(state, min, max)),
+ ),
+ SyntaxRangeKind::WithMin(min_) => Range::new(Some(min_.into_config_with_range(state, min, max)), None),
+ SyntaxRangeKind::WithMax(max_) => Range::new(None, Some(max_.into_config_with_range(state, min, max))),
+ SyntaxRangeKind::WithMinMax(min_, max_) => Range::new(
+ Some(min_.into_config_with_range(state, min, max)),
+ Some(max_.into_config_with_range(state, min, max)),
+ ),
+ };
+
+ if range.min().is_some() && range.max().is_some() && range.min().unwrap() > range.max().unwrap() {
+ state.push_report(Report::AnalyzeInvalidRange { span: self.span() });
+ }
+
+ range
+ }
+}
+
+impl<'src> SyntaxStrLit<'src> {
+ fn into_config(self) -> &'src str {
+ self.value.trim_matches('"')
+ }
+}
+
+impl<'src> SyntaxNumLit<'src> {
+ fn into_config(self) -> f64 {
+ self.value.parse().unwrap()
+ }
+
+ fn into_config_with_range(self, state: &mut ConvertState, min: f64, max: f64) -> f64 {
+ let value = self.into_config();
+
+ if value < min || value > max {
+ state.push_report(Report::AnalyzeNumOutsideRange {
+ span: self.span(),
+ min,
+ max,
+ });
+ }
+
+ value
+ }
+}
+
+impl SyntaxBoolLit {
+ fn into_config(self) -> bool {
+ self.value
+ }
+}
+
+impl<'src> SyntaxIdentifier<'src> {
+ fn into_config(self) -> &'src str {
+ self.name
+ }
+}
diff --git a/zap/src/parser/grammar.lalrpop b/zap/src/parser/grammar.lalrpop
index 0ff91312..0bfd652e 100644
--- a/zap/src/parser/grammar.lalrpop
+++ b/zap/src/parser/grammar.lalrpop
@@ -1,17 +1,12 @@
-use std::collections::HashSet;
-use std::str::FromStr;
-
-use crate::Error;
-use crate::util::Range;
-use crate::parser::*;
-use crate::parser::working_ast::*;
+use crate::parser::{reports::Report, syntax_tree::*};
+use crate::config::{EvCall, EvSource, EvType, NumTy};
use lalrpop_util::ParseError;
-grammar(ref_decl: &mut HashSet, ref_used: &mut HashSet, ev_names: &mut HashSet);
+grammar;
extern {
- type Error = Error;
+ type Error = Report<'input>;
}
match {
@@ -21,169 +16,158 @@ match {
_
}
-pub File: File = => {
- let mut ty_decls = Vec::new();
- let mut ev_decls = Vec::new();
-
- for decl in decls {
- match decl {
- Decl::Ty(decl) => ty_decls.push(decl),
- Decl::Ev(decl) => ev_decls.push(decl),
- }
- }
-
- let mut casing = Casing::Pascal;
- let mut typescript = false;
- let mut write_checks = false;
-
- let mut server_output = PathBuf::from("network/server.luau");
- let mut client_output = PathBuf::from("network/client.luau");
-
- for opt in opts {
- match opt {
- Opt::Casing(v) => casing = v,
- Opt::TypeScript(v) => typescript = v,
- Opt::WriteChecks(v) => write_checks = v,
- Opt::ServerOutput(v) => server_output = v,
- Opt::ClientOutput(v) => client_output = v,
- }
- }
-
- File { ty_decls, ev_decls, casing, typescript, write_checks, server_output, client_output }
-};
-
-Opt: Opt = {
- "opt" "casing" "=" "camelCase" => Opt::Casing(Casing::Camel),
- "opt" "casing" "=" "snake_case" => Opt::Casing(Casing::Snake),
- "opt" "casing" "=" "PascalCase" => Opt::Casing(Casing::Pascal),
-
- "opt" "typescript" "=" "true" => Opt::TypeScript(true),
- "opt" "typescript" "=" "false" => Opt::TypeScript(false),
-
- "opt" "write_checks" "=" "true" => Opt::WriteChecks(true),
- "opt" "write_checks" "=" "false" => Opt::WriteChecks(false),
-
- "opt" "output_server" "=" => Opt::ServerOutput(PathBuf::from(<>)),
- "opt" "output_client" "=" => Opt::ClientOutput(PathBuf::from(<>)),
+pub Config: SyntaxConfig<'input> = {
+ => SyntaxConfig { start, opts, decls, end },
}
-Decl: Decl = {
- => Decl::Ty(decl),
- => Decl::Ev(decl),
+Opt: SyntaxOpt<'input> = {
+ "opt" "=" ";"? => SyntaxOpt { start, name, value, end },
}
-EvDecl: EvDecl = "event" "=" "{" > "}" =>? {
- if !ev_names.insert(name.clone()) {
- return Err(ParseError::User {
- error: Error::DuplicateEvent(name),
- })
- }
+OptValue: SyntaxOptValue<'input> = {
+ => SyntaxOptValue { start, kind, end },
+}
- let mut from = EvSource::Server;
- let mut evty = EvType::Reliable;
- let mut call = EvCall::SingleSync;
- let mut data = Ty::Bool;
+OptValueKind: SyntaxOptValueKind<'input> = {
+ StrLit => SyntaxOptValueKind::Str(<>),
+ NumLit => SyntaxOptValueKind::Num(<>),
+ BoolLit => SyntaxOptValueKind::Bool(<>),
+}
- for field in fields {
- match field {
- EvField::From(v) => from = v,
- EvField::Type(v) => evty = v,
- EvField::Call(v) => call = v,
- EvField::Data(v) => data = v,
- }
- }
+Decl: SyntaxDecl<'input> = {
+ => SyntaxDecl::Ev(decl),
+ => SyntaxDecl::Ty(decl),
+}
- Ok(EvDecl {
- name,
- from,
- evty,
- call,
- data,
- })
-};
+EvDecl: SyntaxEvDecl<'input> = {
+ "event" "=" "{"
+ "from" ":" ","
+ "type" ":" ","
+ "call" ":" ","
+ "data" ":" ","?
+ "}" ";"? => SyntaxEvDecl { start, name, from, evty, call, data, end },
+}
-EvField: EvField = {
- "from" ":" "Server" => EvField::From(EvSource::Server),
- "from" ":" "Client" => EvField::From(EvSource::Client),
+EvCall: EvCall = {
+ "SingleSync" => EvCall::SingleSync,
+ "SingleAsync" => EvCall::SingleAsync,
+ "ManySync" => EvCall::ManySync,
+ "ManyAsync" => EvCall::ManyAsync,
+}
- "type" ":" "Reliable" => EvField::Type(EvType::Reliable),
- "type" ":" "Unreliable" => EvField::Type(EvType::Unreliable),
+EvTy: EvType = {
+ "Reliable" => EvType::Reliable,
+ "Unreliable" => EvType::Unreliable,
+}
- "data" ":" => EvField::Data(ty),
+EvSource: EvSource = {
+ "Server" => EvSource::Server,
+ "Client" => EvSource::Client,
+}
- "call" ":" "SingleSync" => EvField::Call(EvCall::SingleSync),
- "call" ":" "SingleAsync" => EvField::Call(EvCall::SingleAsync),
- "call" ":" "ManySync" => EvField::Call(EvCall::ManySync),
- "call" ":" "ManyAsync" => EvField::Call(EvCall::ManyAsync),
+TyDecl: SyntaxTyDecl<'input> = {
+ "type" "=" ";"? => SyntaxTyDecl { start, name, ty, end },
}
-TyDecl: TyDecl = "type" "=" =>? {
- if !ref_decl.insert(name.clone()) {
- Err(ParseError::User {
- error: Error::DuplicateType(name),
- })
- } else {
- Ok(TyDecl { name, ty })
- }
-};
+Ty: SyntaxTy<'input> = {
+ => SyntaxTy { start, kind, end },
+}
-Ty: Ty = {
- "bool" => Ty::Bool,
+TyKind: SyntaxTyKind<'input> = {
+ "f32" ")")?> => SyntaxTyKind::Num(NumTy::F32, r),
+ "f64" ")")?> => SyntaxTyKind::Num(NumTy::F64, r),
- "f32" ")")?> => Ty::F32(r.unwrap_or_default().cast()),
- "f64" ")")?> => Ty::F64(r.unwrap_or_default().cast()),
+ "i8" ")")?> => SyntaxTyKind::Num(NumTy::I8, r),
+ "i16" ")")?> => SyntaxTyKind::Num(NumTy::I16, r),
+ "i32" ")")?> => SyntaxTyKind::Num(NumTy::I32, r),
- "i8" ")")?> => Ty::I8(r.unwrap_or_default().cast()),
- "i16" ")")?> => Ty::I16(r.unwrap_or_default().cast()),
- "i32" ")")?> => Ty::I32(r.unwrap_or_default().cast()),
+ "u8" ")")?> => SyntaxTyKind::Num(NumTy::U8, r),
+ "u16" ")")?> => SyntaxTyKind::Num(NumTy::U16, r),
+ "u32" ")")?> => SyntaxTyKind::Num(NumTy::U32, r),
- "u8" ")")?> => Ty::U8(r.unwrap_or_default().cast()),
- "u16" ")")?> => Ty::U16(r.unwrap_or_default().cast()),
- "u32" ")")?> => Ty::U32(r.unwrap_or_default().cast()),
+ "string" ")")?> => SyntaxTyKind::Str(r),
- "String" ")")?> => Ty::Str { len: r.unwrap_or_default().cast() },
- "[" "]" => Ty::Arr { ty: Box::new(ty), len: r.unwrap_or_default().cast() },
- "{" "[" "]:" "}" => Ty::Map { key: Box::new(key), val: Box::new(val) },
-
- "{" ":" )>> "}" => Ty::Struct { fields },
- "(" )>> ")" => Ty::Enum { variants },
+ "[" "]" => SyntaxTyKind::Arr(Box::new(ty), r),
+ "map" "{" "[" "]" ":" "}" => SyntaxTyKind::Map(Box::new(k), Box::new(v)),
- "Instance" ")")?> => Ty::Instance(false, class),
+ "?" => SyntaxTyKind::Opt(Box::new(ty)),
+ => SyntaxTyKind::Ref(name),
- "Vector3" => Ty::Vector3,
+ "enum" => SyntaxTyKind::Enum(e),
+ "struct" => SyntaxTyKind::Struct(s),
+ "Instance" ")")?> => SyntaxTyKind::Instance(c),
+}
- => {
- ref_used.insert(name.clone());
- Ty::Ref(name)
- },
+Enum: SyntaxEnum<'input> = {
+ => SyntaxEnum { start, kind, end },
+}
- "?" => match ty {
- Ty::Instance(_, class) => Ty::Instance(true, class),
+EnumKind: SyntaxEnumKind<'input> = {
+ "{" > "}" => SyntaxEnumKind::Unit(enumerators),
- _ => Ty::Optional(Box::new(ty)),
- },
+ "{" )>> "}" => SyntaxEnumKind::Tagged { tag, variants },
}
-Range: Range = {
- ".." => Range::default(),
+Struct: SyntaxStruct<'input> = {
+ "{" ":" )>> "}" => SyntaxStruct { start, fields, end },
+}
- ".." => Range::new(None, Some(max)),
+IntRange: SyntaxRange<'input> = {
+ => SyntaxRange { start, kind, end },
+}
- ".." => Range::new(Some(min), None),
+IntRangeKind: SyntaxRangeKind<'input> = {
+ ".." => SyntaxRangeKind::None,
+ => SyntaxRangeKind::Exact(n),
+ ".." => SyntaxRangeKind::WithMin(min),
+ ".." => SyntaxRangeKind::WithMax(max),
+ ".." => SyntaxRangeKind::WithMinMax(min, max),
+}
- => Range::new(Some(num), Some(num)),
- ".." => Range::new(Some(min), Some(max)),
+NumRange: SyntaxRange<'input> = {
+ => SyntaxRange { start, kind, end },
}
-Comma: Vec = {
- ",")*> => match e {
- None => v,
- Some(e) => {
- v.push(e);
- v
- }
- }
+NumRangeKind: SyntaxRangeKind<'input> = {
+ ".." => SyntaxRangeKind::None,
+ => SyntaxRangeKind::Exact(n),
+ ".." => SyntaxRangeKind::WithMin(min),
+ ".." => SyntaxRangeKind::WithMax(max),
+ ".." => SyntaxRangeKind::WithMinMax(min, max),
+}
+
+StrLit: SyntaxStrLit<'input> = {
+ => SyntaxStrLit { start, value, end },
+}
+
+IntLit: SyntaxNumLit<'input> = {
+ =>? {
+ if n.value.contains('.') {
+ Err(ParseError::User {
+ error: Report::ParserExpectedInt { span: n.span() }
+ })
+ } else {
+ Ok(n)
+ }
+ }
+}
+
+NumLit: SyntaxNumLit<'input> = {
+ => SyntaxNumLit { start, value, end },
}
-Number: f64 = r"[-+]?[0-9]*\.?[0-9]+" => f64::from_str(<>).unwrap();
-Word: String = r"[a-zA-Z_][a-zA-Z0-9_]*" => <>.to_string();
+BoolLit: SyntaxBoolLit = {
+ "true" => SyntaxBoolLit { start, value: true, end },
+ "false" => SyntaxBoolLit { start, value: false, end },
+}
+
+Identifier: SyntaxIdentifier<'input> = {
+ => SyntaxIdentifier { start, name, end },
+}
+
+Comma: Vec = {
+ ",")*> => match e {
+ Some(e) => { v.push(e); v },
+ None => v,
+ }
+}
diff --git a/zap/src/parser/mod.rs b/zap/src/parser/mod.rs
index 859233a2..c0ae5b25 100644
--- a/zap/src/parser/mod.rs
+++ b/zap/src/parser/mod.rs
@@ -1,178 +1,93 @@
-use std::{collections::HashSet, path::PathBuf};
+use std::collections::HashSet;
-use lalrpop_util::lalrpop_mod;
+use codespan_reporting::diagnostic::Severity;
+use lalrpop_util::{lalrpop_mod, ParseError};
-use crate::{
- util::{NumTy, Range},
- Error,
+use crate::config::{Config, EvType};
+
+use self::{
+ reports::Report,
+ syntax_tree::{Spanned, SyntaxEvDecl},
};
-mod working_ast;
+mod convert;
+mod reports;
+mod syntax_tree;
lalrpop_mod!(pub grammar);
-#[derive(Debug)]
-pub struct File {
- pub ty_decls: Vec,
- pub ev_decls: Vec,
-
- pub server_output: PathBuf,
- pub client_output: PathBuf,
-
- pub casing: Casing,
- pub typescript: bool,
- pub write_checks: bool,
-}
-
-impl File {
- pub fn event_id_ty(&self) -> NumTy {
- NumTy::from_f64(0.0, self.ev_decls.len() as f64)
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum Casing {
- Pascal,
- Camel,
- Snake,
-}
-
-#[derive(Debug, Clone)]
-pub struct EvDecl {
- pub name: String,
- pub from: EvSource,
- pub evty: EvType,
- pub call: EvCall,
- pub data: Ty,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum EvSource {
- Server,
- Client,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum EvType {
- Reliable,
- Unreliable,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum EvCall {
- SingleSync,
- SingleAsync,
- ManySync,
- ManyAsync,
-}
-
-#[derive(Debug, Clone)]
-pub struct TyDecl {
- pub name: String,
- pub ty: Ty,
-}
-
-#[derive(Debug, Clone)]
-pub enum Ty {
- Bool,
-
- F32(Range),
- F64(Range),
-
- I8(Range),
- I16(Range),
- I32(Range),
-
- U8(Range),
- U16(Range),
- U32(Range),
+pub fn parse(input: &str) -> (Option>, Vec) {
+ let parse_result = grammar::ConfigParser::new().parse(input);
- Str { len: Range },
- Arr { len: Range, ty: Box },
- Map { key: Box, val: Box },
+ if let Ok(syntax_config) = parse_result {
+ let evdecls: Vec> = syntax_config
+ .decls
+ .iter()
+ .filter_map(|decl| match decl {
+ syntax_tree::SyntaxDecl::Ev(evdecl) => Some(evdecl.clone()),
+ _ => None,
+ })
+ .collect();
- Struct { fields: Vec<(String, Ty)> },
- Enum { variants: Vec },
+ let (config, mut reports) = convert::convert(syntax_config);
+ let max_unreliable_size = 900 - config.event_id_ty().size();
- Instance(bool, Option),
- Vector3,
-
- Ref(String),
-
- Optional(Box),
-}
-
-impl Ty {
- pub fn exact_size(&self) -> Option {
- match self {
- Ty::Bool => Some(1),
-
- Ty::F32(_) => Some(4),
- Ty::F64(_) => Some(8),
-
- Ty::I8(_) => Some(1),
- Ty::I16(_) => Some(2),
- Ty::I32(_) => Some(4),
-
- Ty::U8(_) => Some(1),
- Ty::U16(_) => Some(2),
- Ty::U32(_) => Some(4),
-
- Ty::Str { len } => len.exact().map(|len| len as usize),
-
- Ty::Arr { len, ty } => {
- if let Some(len) = len.exact() {
- ty.exact_size().map(|ty_size| len as usize * ty_size)
- } else {
- None
- }
- }
+ if config.evdecls.is_empty() {
+ reports.push(Report::AnalyzeEmptyEvDecls);
+ }
- Ty::Map { .. } => None,
+ for ev in config.evdecls.iter().filter(|ev| ev.evty == EvType::Unreliable) {
+ let max_size = ev.data.max_size(&config, &mut HashSet::new());
- Ty::Struct { fields } => {
- let mut size = 0;
+ if let Some(max_size) = max_size {
+ if max_size > max_unreliable_size {
+ let evdecl = evdecls.iter().find(|evdecl| evdecl.name.name == ev.name).unwrap();
- for (_, ty) in fields {
- if let Some(ty_size) = ty.exact_size() {
- size += ty_size;
- } else {
- return None;
- }
+ reports.push(Report::AnalyzeOversizeUnreliable {
+ ev_span: evdecl.span(),
+ ty_span: evdecl.data.span(),
+ max_size: max_unreliable_size,
+ size: max_size,
+ });
}
-
- Some(size)
+ } else {
+ let evdecl = evdecls.iter().find(|evdecl| evdecl.name.name == ev.name).unwrap();
+
+ reports.push(Report::AnalyzePotentiallyOversizeUnreliable {
+ ev_span: evdecl.span(),
+ ty_span: evdecl.data.span(),
+ max_size: max_unreliable_size,
+ });
}
+ }
- Ty::Enum { variants } => Some(NumTy::from_f64(0.0, variants.len() as f64).size()),
-
- Ty::Instance(_, _) => Some(2),
-
- Ty::Vector3 => Some(12),
-
- // At some point this should evaluate the size of the referenced type
- // for now the extra complexity isn't worth it
- Ty::Ref(_) => None,
-
- Ty::Optional(ty) => ty.exact_size().map(|size| size + 1),
+ if reports.iter().any(|report| report.severity() == Severity::Error) {
+ (None, reports)
+ } else {
+ (Some(config), reports)
}
- }
-}
+ } else {
+ let report = match parse_result.unwrap_err() {
+ ParseError::InvalidToken { location } => Report::LexerInvalidToken {
+ span: location..location,
+ },
-pub fn parse(code: &str) -> Result {
- let mut ref_decl = HashSet::new();
- let mut ref_used = HashSet::new();
+ ParseError::UnrecognizedEof { location, expected } => Report::ParserUnexpectedEOF {
+ span: location..location,
+ expected,
+ },
- let file = grammar::FileParser::new()
- .parse(&mut ref_decl, &mut ref_used, &mut HashSet::new(), code)
- .map_err(|e| Error::ParseError(e.to_string()))?;
+ ParseError::UnrecognizedToken { token, expected } => Report::ParserUnexpectedToken {
+ span: token.0..token.2,
+ expected,
+ token: token.1,
+ },
- let unknown_refs = ref_used.difference(&ref_decl).collect::