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::>(); + ParseError::ExtraToken { token } => Report::ParserExtraToken { span: token.0..token.2 }, - // TODO: Better error reporting with error location - if !unknown_refs.is_empty() { - return Err(Error::UnknownTypeRef(unknown_refs[0].to_owned())); - } + ParseError::User { error } => error, + }; - Ok(file) + (None, vec![report]) + } } diff --git a/zap/src/parser/reports.rs b/zap/src/parser/reports.rs new file mode 100644 index 00000000..f34a7643 --- /dev/null +++ b/zap/src/parser/reports.rs @@ -0,0 +1,281 @@ +use codespan_reporting::diagnostic::{Diagnostic, Label, Severity}; +use lalrpop_util::lexer::Token; + +pub type Span = core::ops::Range; + +#[derive(Debug, Clone)] +pub enum Report<'src> { + LexerInvalidToken { + span: Span, + }, + + ParserUnexpectedEOF { + span: Span, + expected: Vec, + }, + + ParserUnexpectedToken { + span: Span, + expected: Vec, + token: Token<'src>, + }, + + ParserExtraToken { + span: Span, + }, + + ParserExpectedInt { + span: Span, + }, + + AnalyzeEmptyEvDecls, + + AnalyzeOversizeUnreliable { + ev_span: Span, + ty_span: Span, + max_size: usize, + size: usize, + }, + + AnalyzePotentiallyOversizeUnreliable { + ev_span: Span, + ty_span: Span, + max_size: usize, + }, + + AnalyzeInvalidRange { + span: Span, + }, + + AnalyzeEmptyEnum { + span: Span, + }, + + AnalyzeEnumTagUsed { + tag_span: Span, + used_span: Span, + tag: &'src str, + }, + + AnalyzeInvalidOptValue { + span: Span, + expected: &'static str, + }, + + AnalyzeUnknownOptName { + span: Span, + }, + + AnalyzeUnknownTypeRef { + span: Span, + name: &'src str, + }, + + AnalyzeNumOutsideRange { + span: Span, + min: f64, + max: f64, + }, +} + +impl<'src> Report<'src> { + pub fn severity(&self) -> Severity { + match self { + Self::LexerInvalidToken { .. } => Severity::Error, + + Self::ParserUnexpectedEOF { .. } => Severity::Error, + Self::ParserUnexpectedToken { .. } => Severity::Error, + Self::ParserExtraToken { .. } => Severity::Error, + Self::ParserExpectedInt { .. } => Severity::Error, + + Self::AnalyzeEmptyEvDecls => Severity::Warning, + Self::AnalyzeOversizeUnreliable { .. } => Severity::Error, + Self::AnalyzePotentiallyOversizeUnreliable { .. } => Severity::Warning, + Self::AnalyzeInvalidRange { .. } => Severity::Error, + Self::AnalyzeEmptyEnum { .. } => Severity::Error, + Self::AnalyzeEnumTagUsed { .. } => Severity::Error, + Self::AnalyzeInvalidOptValue { .. } => Severity::Error, + Self::AnalyzeUnknownOptName { .. } => Severity::Warning, + Self::AnalyzeUnknownTypeRef { .. } => Severity::Error, + Self::AnalyzeNumOutsideRange { .. } => Severity::Error, + } + } + + fn message(&self) -> String { + match self { + Self::LexerInvalidToken { .. } => "invalid token".to_string(), + + Self::ParserUnexpectedEOF { expected, .. } => { + format!("expected {}, found end of file", expected.join(", ")) + } + + Self::ParserUnexpectedToken { expected, .. } => format!("expected {}", expected.join(", ")), + Self::ParserExtraToken { .. } => "extra token".to_string(), + Self::ParserExpectedInt { .. } => "expected integer".to_string(), + + Self::AnalyzeEmptyEvDecls => "no event declarations".to_string(), + Self::AnalyzeOversizeUnreliable { .. } => "oversize unreliable".to_string(), + Self::AnalyzePotentiallyOversizeUnreliable { .. } => "potentially oversize unreliable".to_string(), + Self::AnalyzeInvalidRange { .. } => "invalid range".to_string(), + Self::AnalyzeEmptyEnum { .. } => "empty enum".to_string(), + Self::AnalyzeEnumTagUsed { .. } => "enum tag used in variant".to_string(), + Self::AnalyzeInvalidOptValue { expected, .. } => format!("invalid opt value, expected {}", expected), + Self::AnalyzeUnknownOptName { .. } => "unknown opt name".to_string(), + Self::AnalyzeUnknownTypeRef { name, .. } => format!("unknown type reference '{}'", name), + Self::AnalyzeNumOutsideRange { .. } => "number outside range".to_string(), + } + } + + fn code(&self) -> &str { + match self { + Self::LexerInvalidToken { .. } => "1001", + + Self::ParserUnexpectedEOF { .. } => "2001", + Self::ParserUnexpectedToken { .. } => "2002", + Self::ParserExtraToken { .. } => "2003", + Self::ParserExpectedInt { .. } => "2004", + + Self::AnalyzeEmptyEvDecls { .. } => "3001", + Self::AnalyzeOversizeUnreliable { .. } => "3002", + Self::AnalyzePotentiallyOversizeUnreliable { .. } => "3003", + Self::AnalyzeInvalidRange { .. } => "3004", + Self::AnalyzeEmptyEnum { .. } => "3005", + Self::AnalyzeEnumTagUsed { .. } => "3006", + Self::AnalyzeInvalidOptValue { .. } => "3007", + Self::AnalyzeUnknownOptName { .. } => "3008", + Self::AnalyzeUnknownTypeRef { .. } => "3009", + Self::AnalyzeNumOutsideRange { .. } => "3010", + } + } + + fn labels(&self) -> Vec> { + match self { + Self::LexerInvalidToken { span } => vec![Label::primary((), span.clone()).with_message("invalid token")], + + Self::ParserUnexpectedEOF { span, .. } => { + vec![Label::primary((), span.clone()).with_message("unexpected end of file")] + } + + Self::ParserUnexpectedToken { span, .. } => { + vec![Label::primary((), span.clone()).with_message("unexpected token")] + } + + Self::ParserExtraToken { span } => { + vec![Label::primary((), span.clone()).with_message("extra token")] + } + + Self::ParserExpectedInt { span } => { + vec![Label::primary((), span.clone()).with_message("expected integer")] + } + + Self::AnalyzeEmptyEvDecls => vec![], + + Self::AnalyzeOversizeUnreliable { ev_span, ty_span, .. } => { + vec![ + Label::primary((), ev_span.clone()).with_message("event is unreliable"), + Label::secondary((), ty_span.clone()).with_message("type is too large"), + ] + } + + Self::AnalyzePotentiallyOversizeUnreliable { ev_span, ty_span, .. } => { + vec![ + Label::primary((), ev_span.clone()).with_message("event is unreliable"), + Label::secondary((), ty_span.clone()).with_message("type may be too large"), + ] + } + + Self::AnalyzeInvalidRange { span } => { + vec![Label::primary((), span.clone()).with_message("invalid range")] + } + + Self::AnalyzeEmptyEnum { span } => { + vec![Label::primary((), span.clone()).with_message("empty enum")] + } + + Self::AnalyzeEnumTagUsed { + tag_span, used_span, .. + } => { + vec![ + Label::primary((), tag_span.clone()).with_message("enum tag"), + Label::secondary((), used_span.clone()).with_message("used in variant"), + ] + } + + Self::AnalyzeInvalidOptValue { span, .. } => { + vec![Label::primary((), span.clone()).with_message("invalid opt value")] + } + + Self::AnalyzeUnknownOptName { span } => { + vec![Label::primary((), span.clone()).with_message("unknown opt name")] + } + + Self::AnalyzeUnknownTypeRef { span, .. } => { + vec![Label::primary((), span.clone()).with_message("unknown type reference")] + } + + Self::AnalyzeNumOutsideRange { span, .. } => { + vec![Label::primary((), span.clone()).with_message("number outside range")] + } + } + } + + fn notes(&self) -> Option> { + match self { + Self::LexerInvalidToken { .. } => None, + + Self::ParserUnexpectedEOF { .. } => None, + Self::ParserUnexpectedToken { .. } => None, + Self::ParserExtraToken { .. } => None, + Self::ParserExpectedInt { .. } => None, + + Self::AnalyzeEmptyEvDecls => Some(vec!["add an event declaration to allow zap to output code".to_string()]), + Self::AnalyzeOversizeUnreliable { max_size, .. } => Some(vec![ + format!("all unreliable events must be under {max_size} bytes in size"), + "consider adding a upper limit to any arrays or strings".to_string(), + "upper limits can be added for arrays by doing `[..10]`".to_string(), + "upper limits can be added for strings by doing `[..10]`".to_string(), + ]), + Self::AnalyzePotentiallyOversizeUnreliable { max_size, .. } => Some(vec![ + format!("all unreliable events must be under {max_size} bytes in size"), + "consider adding a upper limit to any arrays or strings".to_string(), + "upper limits can be added for arrays by doing `[..10]`".to_string(), + "upper limits can be added for strings by doing `(..10)`".to_string(), + ]), + Self::AnalyzeInvalidRange { .. } => Some(vec![ + "ranges must be in the form `min..max`".to_string(), + "ranges can be invalid if `min` is greater than `max`".to_string(), + ]), + Self::AnalyzeEmptyEnum { .. } => Some(vec![ + "enums cannot be empty".to_string(), + "if you're looking to create an empty type, use a struct with no fields".to_string(), + "a struct with no fields can be created by doing `struct {}`".to_string(), + ]), + Self::AnalyzeEnumTagUsed { .. } => Some(vec![ + "tagged enums use the tag field in passed structs to determine what variant to use".to_string(), + "you cannot override this tag field in a variant as that would break this behavior".to_string(), + ]), + Self::AnalyzeInvalidOptValue { .. } => None, + Self::AnalyzeUnknownOptName { .. } => None, + Self::AnalyzeUnknownTypeRef { .. } => None, + Self::AnalyzeNumOutsideRange { min, max, .. } => Some(vec![ + format!("(inclusive) min: {}", min), + format!("(inclusive) max: {}", max), + ]), + } + } +} + +impl<'src> From> for Diagnostic<()> { + fn from(val: Report<'src>) -> Self { + let diagnostic = Diagnostic::new(val.severity()) + .with_code(val.code()) + .with_message(val.message()) + .with_labels(val.labels()); + + if let Some(notes) = val.notes() { + diagnostic.with_notes(notes) + } else { + diagnostic + } + } +} diff --git a/zap/src/parser/syntax_tree.rs b/zap/src/parser/syntax_tree.rs new file mode 100644 index 00000000..00850926 --- /dev/null +++ b/zap/src/parser/syntax_tree.rs @@ -0,0 +1,235 @@ +use crate::config::{EvCall, EvSource, EvType, NumTy}; + +use super::reports::Span; + +pub trait Spanned { + fn span(&self) -> Span; + + fn start(&self) -> usize { + self.span().start + } + + fn end(&self) -> usize { + self.span().end + } + + fn len(&self) -> usize { + self.end() - self.start() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxConfig<'src> { + pub start: usize, + pub opts: Vec>, + pub decls: Vec>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxConfig<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxOpt<'src> { + pub start: usize, + pub name: SyntaxIdentifier<'src>, + pub value: SyntaxOptValue<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxOpt<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxOptValue<'src> { + pub start: usize, + pub kind: SyntaxOptValueKind<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxOptValue<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SyntaxOptValueKind<'src> { + Str(SyntaxStrLit<'src>), + Num(SyntaxNumLit<'src>), + Bool(SyntaxBoolLit), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SyntaxDecl<'src> { + Ev(SyntaxEvDecl<'src>), + Ty(SyntaxTyDecl<'src>), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxEvDecl<'src> { + pub start: usize, + pub name: SyntaxIdentifier<'src>, + pub from: EvSource, + pub evty: EvType, + pub call: EvCall, + pub data: SyntaxTy<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxEvDecl<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxTyDecl<'src> { + pub start: usize, + pub name: SyntaxIdentifier<'src>, + pub ty: SyntaxTy<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxTyDecl<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxTy<'src> { + pub start: usize, + pub kind: SyntaxTyKind<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxTy<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SyntaxTyKind<'src> { + Num(NumTy, Option>), + Str(Option>), + Arr(Box>, Option>), + Map(Box>, Box>), + Opt(Box>), + Ref(SyntaxIdentifier<'src>), + + Enum(SyntaxEnum<'src>), + Struct(SyntaxStruct<'src>), + Instance(Option>), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxEnum<'src> { + pub start: usize, + pub kind: SyntaxEnumKind<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxEnum<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SyntaxEnumKind<'src> { + Unit(Vec>), + + Tagged { + tag: SyntaxStrLit<'src>, + variants: Vec<(SyntaxIdentifier<'src>, SyntaxStruct<'src>)>, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxStruct<'src> { + pub start: usize, + pub fields: Vec<(SyntaxIdentifier<'src>, SyntaxTy<'src>)>, + pub end: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SyntaxRange<'src> { + pub start: usize, + pub kind: SyntaxRangeKind<'src>, + pub end: usize, +} + +impl<'src> Spanned for SyntaxRange<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SyntaxRangeKind<'src> { + None, + Exact(SyntaxNumLit<'src>), + WithMin(SyntaxNumLit<'src>), + WithMax(SyntaxNumLit<'src>), + WithMinMax(SyntaxNumLit<'src>, SyntaxNumLit<'src>), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SyntaxStrLit<'src> { + pub start: usize, + pub value: &'src str, + pub end: usize, +} + +impl<'src> Spanned for SyntaxStrLit<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SyntaxNumLit<'src> { + pub start: usize, + pub value: &'src str, + pub end: usize, +} + +impl<'src> Spanned for SyntaxNumLit<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SyntaxBoolLit { + pub start: usize, + pub value: bool, + pub end: usize, +} + +impl Spanned for SyntaxBoolLit { + fn span(&self) -> Span { + self.start..self.end + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SyntaxIdentifier<'src> { + pub start: usize, + pub name: &'src str, + pub end: usize, +} + +impl<'src> Spanned for SyntaxIdentifier<'src> { + fn span(&self) -> Span { + self.start..self.end + } +} diff --git a/zap/src/parser/working_ast.rs b/zap/src/parser/working_ast.rs deleted file mode 100644 index b0aeeb5d..00000000 --- a/zap/src/parser/working_ast.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::path::PathBuf; - -use super::{Casing, EvCall, EvDecl, EvSource, EvType, Ty, TyDecl}; - -#[derive(Debug, Clone)] -pub enum EvField { - From(EvSource), - Type(EvType), - Call(EvCall), - Data(Ty), -} - -#[derive(Debug, Clone)] -pub enum Decl { - Ev(EvDecl), - Ty(TyDecl), -} - -#[derive(Debug, Clone)] -pub enum Opt { - ServerOutput(PathBuf), - ClientOutput(PathBuf), - Casing(Casing), - TypeScript(bool), - WriteChecks(bool), -} diff --git a/zap/src/util.rs b/zap/src/util.rs deleted file mode 100644 index c492b7c6..00000000 --- a/zap/src/util.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::fmt::Display; - -use num_traits::*; - -use crate::parser::Casing; - -#[derive(Debug, Clone, Copy)] -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 cast(self) -> Range { - Range { - min: self.min.map(|x| NumCast::from(x).unwrap()), - max: self.max.map(|x| NumCast::from(x).unwrap()), - } - } -} - -impl Range { - pub fn exact(&self) -> Option { - if self.min.is_some() && self.min == self.max { - Some(self.min.unwrap()) - } else { - None - } - } - - pub fn exact_f64(&self) -> Option { - if self.min.is_some() && self.min == self.max { - Some(NumCast::from(self.min.unwrap()).unwrap()) - } else { - None - } - } -} - -impl Default for Range { - fn default() -> Self { - Self { min: None, max: 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)] -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"), - } - } -} - -pub fn casing(casing: Casing, pascal: &'static str, camel: &'static str, snake: &'static str) -> &'static str { - match casing { - Casing::Pascal => pascal, - Casing::Camel => camel, - Casing::Snake => snake, - } -}