From 601b15aeee2d8b0693dffa98a89ec2e73bb11067 Mon Sep 17 00:00:00 2001 From: Michael Lamparski Date: Thu, 15 May 2025 20:15:55 -0400 Subject: [PATCH 1/4] add tan/asin/acos/atan --- build/gen_macros.rs | 4 +++- doc/syntax.md | 14 +++++++------- map/v2.anmm | 2 +- src/ast/mod.rs | 8 ++++++++ src/core_mapfiles/anm.rs | 18 +++++++++--------- src/fmt.rs | 10 ++++++---- src/parse/lalrparser.lalrpop | 8 ++++++++ src/parse/lexer.rs | 4 ++++ src/passes/const_simplify.rs | 8 ++++++++ src/passes/type_check.rs | 8 ++++++++ src/quote.rs | 4 ++++ 11 files changed, 66 insertions(+), 22 deletions(-) diff --git a/build/gen_macros.rs b/build/gen_macros.rs index 23087d86..9d718703 100644 --- a/build/gen_macros.rs +++ b/build/gen_macros.rs @@ -99,7 +99,9 @@ pub fn gen_ast_macros() -> String { "expr_unop", &[ ("op", ArgKind::Token(&[ "-", "!", - "sin", "cos", "sqrt", + "sin", "cos", "tan", + "asin", "acos", "atan", + "sqrt", "_S", "_f", // $ has to be written as _S since it's a macro meta-char "%", "int", "float", ])), diff --git a/doc/syntax.md b/doc/syntax.md index fb256b95..f8881d84 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -41,9 +41,9 @@ Literals can be: * **Integers:** `123`, `0x123`, `-0b00101`, `true`, `false`. There is no octal syntax. Literal numbers have no optional leading plus. * **Floats:** `1.0`, `-1.3f`, `2f`. Again, no leading plus. -* **Strings:** `"The quick brown fox\njumped over the lazy dog"`. The control characters for strings are `\0`, `\n`, `\r`, `\\`, and `\"`. +* **Strings:** `"The quick brown fox\njumped over the lazy dog"`. The control characters for strings are `\0`, `\n`, `\r`, `\\`, and `\"`. -Source text files for `truth` **must** be encoded in UTF-8. `truth` handles the conversion between the Shift-JIS encoding used in the binary files and the UTF-8 encoding used in source text. (in the future, you may even be able to configure which encoding is used in the binary files... but source text will always be UTF-8!) +Source text files for `truth` **must** be encoded in UTF-8. `truth` handles the conversion between the Shift-JIS encoding used in the binary files and the UTF-8 encoding used in source text. (in the future, you may even be able to configure which encoding is used in the binary files... but source text will always be UTF-8!) ### Variables @@ -139,12 +139,12 @@ Other things present in expressions: * The **ternary operator** `a ? b : c`. Right associative, [the way it should be](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/#operators). * **Unary negation** `-x`. -* **Special functions** `sin(x)` and `cos(x)`. +* **Special functions** `sin(x)` and `cos(x)`. Some languages support `acos`, `asin`, `tan`, and `sqrt`. * **Logical right shift `>>>`**. * All ints in truth are signed so `>>` is an arithmetic right shift (sign-extended) for consistency. `>>>` is provided as a separate operator for doing logical right shifts (zero-extended). * **Explicit casts** `_S` (float to int) and `_f` (int to float): `I0 = _S(F0);` - * That example is identical to `I0 = $F0`, but `_S` is also more generally usable in larger expressions where it may introduce a temporary + * That example is identical to `I0 = $F0`, but `_S` is also more generally usable in larger expressions where it may introduce a temporary ### Assignments @@ -217,7 +217,7 @@ times(I2 = I0) { ``` #### About conditions - + In the desugared form of `times()` above, you'll notice that there is a `while (--var)`. This `--var` is not an expression! Basically, a **condition** can either be an expression `` or a predecrement operation `--`. The latter compiles to a special kind of jump available in ANM and early ECL that decrements a variable and jumps if it is nonzero. (*every single `while` loop in vanilla ANM files uses this form of jump,* so the ability to decompile it seemed... kind of important!) @@ -290,7 +290,7 @@ Notice that, unlike conditional blocks, *the conditional jump has no braces*; it ```C // this compiles to a single instruction in anm if (I0 != 0) goto else_label; - + // this typically compiles to more than one instruction if (I0 != 0) { goto else_label; @@ -317,7 +317,7 @@ However, this is generally obsolete. Normally, the only reason the game ever us label: // has time label 15 bar() goto label @ 10; - + // is equivalent to 10: diff --git a/map/v2.anmm b/map/v2.anmm index ff8311b7..b37cf757 100644 --- a/map/v2.anmm +++ b/map/v2.anmm @@ -71,7 +71,7 @@ 61 fsin 62 fcos 63 ftan -64 acos +64 facos 65 fatan 66 validRad diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 239bdafa..189008e5 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -695,6 +695,10 @@ string_enum! { #[strum(serialize = "~")] BitNot, #[strum(serialize = "sin")] Sin, #[strum(serialize = "cos")] Cos, + #[strum(serialize = "tan")] Tan, + #[strum(serialize = "asin")] Asin, + #[strum(serialize = "acos")] Acos, + #[strum(serialize = "atan")] Atan, #[strum(serialize = "sqrt")] Sqrt, #[strum(serialize = "$")] EncodeI, #[strum(serialize = "%")] EncodeF, @@ -711,6 +715,10 @@ impl UnOpKind { UnOpKind::BitNot => OpClass::Bitwise, UnOpKind::Sin => OpClass::FloatMath, UnOpKind::Cos => OpClass::FloatMath, + UnOpKind::Tan => OpClass::FloatMath, + UnOpKind::Asin => OpClass::FloatMath, + UnOpKind::Acos => OpClass::FloatMath, + UnOpKind::Atan => OpClass::FloatMath, UnOpKind::Sqrt => OpClass::FloatMath, UnOpKind::CastI => OpClass::Cast, UnOpKind::CastF => OpClass::Cast, diff --git a/src/core_mapfiles/anm.rs b/src/core_mapfiles/anm.rs index 95e0f16b..3104ae72 100644 --- a/src/core_mapfiles/anm.rs +++ b/src/core_mapfiles/anm.rs @@ -150,9 +150,9 @@ static ANM_INS_07_09: &'static CoreSignatures = &CoreSignatures { (Th07, 60, Some(("ff", None))), (Th07, 61, Some(("ff", Some(IKind::UnOp(U::Sin, Ty::Float))))), (Th07, 62, Some(("ff", Some(IKind::UnOp(U::Cos, Ty::Float))))), - (Th07, 63, Some(("ff", None))), - (Th07, 64, Some(("ff", None))), - (Th07, 65, Some(("ff", None))), + (Th07, 63, Some(("ff", Some(IKind::UnOp(U::Tan, Ty::Float))))), + (Th07, 64, Some(("ff", Some(IKind::UnOp(U::Acos, Ty::Float))))), + (Th07, 65, Some(("ff", Some(IKind::UnOp(U::Atan, Ty::Float))))), (Th07, 66, Some(("f", None))), (Th07, 67, Some(("SSot", Some(IKind::CondJmp(B::Eq, Ty::Int))))), (Th07, 68, Some(("ffot", Some(IKind::CondJmp(B::Eq, Ty::Float))))), @@ -235,9 +235,9 @@ static ANM_INS_095_128: &'static CoreSignatures = &CoreSignatures { (Th095, 41, Some(("ff", None))), (Th095, 42, Some(("ff", Some(IKind::UnOp(U::Sin, Ty::Float))))), (Th095, 43, Some(("ff", Some(IKind::UnOp(U::Cos, Ty::Float))))), - (Th095, 44, Some(("ff", None))), - (Th095, 45, Some(("ff", None))), - (Th095, 46, Some(("ff", None))), + (Th095, 44, Some(("ff", Some(IKind::UnOp(U::Tan, Ty::Float))))), + (Th095, 45, Some(("ff", Some(IKind::UnOp(U::Acos, Ty::Float))))), + (Th095, 46, Some(("ff", Some(IKind::UnOp(U::Atan, Ty::Float))))), (Th095, 47, Some(("f", None))), (Th095, 48, Some(("fff", None))), (Th095, 49, Some(("fff", None))), @@ -353,9 +353,9 @@ static ANM_INS_13_185: &CoreSignatures = &CoreSignatures { (Th13, 123, Some(("ff", None))), (Th13, 124, Some(("ff", Some(IKind::UnOp(U::Sin, Ty::Float))))), (Th13, 125, Some(("ff", Some(IKind::UnOp(U::Cos, Ty::Float))))), - (Th13, 126, Some(("ff", None))), - (Th13, 127, Some(("ff", None))), - (Th13, 128, Some(("ff", None))), + (Th13, 126, Some(("ff", Some(IKind::UnOp(U::Tan, Ty::Float))))), + (Th13, 127, Some(("ff", Some(IKind::UnOp(U::Acos, Ty::Float))))), + (Th13, 128, Some(("ff", Some(IKind::UnOp(U::Atan, Ty::Float))))), (Th13, 129, Some(("f", None))), (Th13, 130, Some(("ffff", None))), (Th13, 131, Some(("ffff", None))), diff --git a/src/fmt.rs b/src/fmt.rs index bac1c71d..d6956d82 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -886,12 +886,14 @@ impl Format for ast::Expr { }) }, ast::Expr::UnOp(op, x) => match op.value { - token![unop -] | token![!] | token![~] + | token![unop -] | token![!] | token![~] => out.fmt_optional_parens(|out| out.fmt((op, x))), - token![unop $] | token![unop %] | - token![unop int] | token![unop float] | - token![sin] | token![cos] | token![sqrt] + | token![unop $] | token![unop %] + | token![unop int] | token![unop float] + | token![sin] | token![cos] | token![tan] + | token![asin] | token![acos] | token![atan] + | token![sqrt] => out.fmt((op, "(", SuppressParens(x), ")")), }, ast::Expr::XcrementOp { order: ast::XcrementOpOrder::Pre, op, var } => out.fmt((op, var)), diff --git a/src/parse/lalrparser.lalrpop b/src/parse/lalrparser.lalrpop index fbfebed4..b219afe1 100644 --- a/src/parse/lalrparser.lalrpop +++ b/src/parse/lalrparser.lalrpop @@ -107,6 +107,10 @@ extern { "timeof" => Token::TimeOf, "sin" => Token::Sin, "cos" => Token::Cos, + "tan" => Token::Tan, + "asin" => Token::Asin, + "acos" => Token::Acos, + "atan" => Token::Atan, "sqrt" => Token::Sqrt, "_S" => Token::LegacyEncodeI, "_f" => Token::LegacyEncodeF, @@ -570,6 +574,10 @@ OpXcrement: ast::XcrementOpKind = { FuncUnOpKeyword: ast::UnOpKind = { "sin" => token![sin], "cos" => token![cos], + "tan" => token![tan], + "asin" => token![asin], + "acos" => token![acos], + "atan" => token![atan], "sqrt" => token![sqrt], // FIXME: differentiate read sigils from type casts "_S" => token![$], diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 3748107c..2fe55fc7 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -99,6 +99,10 @@ define_token_enum! { #[token("timeof")] TimeOf, #[token("sin")] Sin, #[token("cos")] Cos, + #[token("tan")] Tan, + #[token("asin")] Asin, + #[token("acos")] Acos, + #[token("atan")] Atan, #[token("sqrt")] Sqrt, #[token("_S")] LegacyEncodeI, #[token("_f")] LegacyEncodeF, diff --git a/src/passes/const_simplify.rs b/src/passes/const_simplify.rs index 26ead1fd..8aa7c977 100644 --- a/src/passes/const_simplify.rs +++ b/src/passes/const_simplify.rs @@ -51,6 +51,10 @@ impl ast::UnOpKind { token![unop ~] => Some(ScalarValue::Int(!x)), token![unop sin] | token![unop cos] | + token![unop tan] | + token![unop asin] | + token![unop acos] | + token![unop atan] | token![unop sqrt] => uncaught_type_error(), token![unop int] => Some(ScalarValue::Int(x)), token![unop float] => Some(ScalarValue::Float(x as f32)), @@ -64,6 +68,10 @@ impl ast::UnOpKind { token![unop ~] => uncaught_type_error(), token![unop sin] => Some(ScalarValue::Float(x.sin())), token![unop cos] => Some(ScalarValue::Float(x.cos())), + token![unop tan] => Some(ScalarValue::Float(x.tan())), + token![unop asin] => Some(ScalarValue::Float(x.asin())), + token![unop acos] => Some(ScalarValue::Float(x.acos())), + token![unop atan] => Some(ScalarValue::Float(x.atan())), token![unop sqrt] => Some(ScalarValue::Float(x.sqrt())), token![unop int] => Some(ScalarValue::Int(x as i32)), token![unop float] => Some(ScalarValue::Float(x)), diff --git a/src/passes/type_check.rs b/src/passes/type_check.rs index 827096fa..19236f6f 100644 --- a/src/passes/type_check.rs +++ b/src/passes/type_check.rs @@ -652,6 +652,10 @@ impl ExprTypeChecker<'_, '_> { | token![unop sin] | token![unop cos] + | token![unop tan] + | token![unop asin] + | token![unop acos] + | token![unop atan] | token![unop sqrt] => self.require_float(arg_ty, op.span, arg_span), } @@ -681,6 +685,10 @@ impl ast::Expr { token![unop sin] | token![unop cos] | + token![unop tan] | + token![unop asin] | + token![unop acos] | + token![unop atan] | token![unop sqrt] => ScalarType::Float, token![unop $] => ScalarType::Int, diff --git a/src/quote.rs b/src/quote.rs index baa1546b..e4df012a 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -33,6 +33,10 @@ macro_rules! token { ($(unop)? ~) => { $crate::ast::UnOpKind::BitNot }; ($(unop)? sin) => { $crate::ast::UnOpKind::Sin }; ($(unop)? cos) => { $crate::ast::UnOpKind::Cos }; + ($(unop)? tan) => { $crate::ast::UnOpKind::Tan }; + ($(unop)? asin) => { $crate::ast::UnOpKind::Asin }; + ($(unop)? acos) => { $crate::ast::UnOpKind::Acos }; + ($(unop)? atan) => { $crate::ast::UnOpKind::Atan }; ($(unop)? sqrt) => { $crate::ast::UnOpKind::Sqrt }; ( unop $) => { $crate::ast::UnOpKind::EncodeI }; ( unop %) => { $crate::ast::UnOpKind::EncodeF }; From fb970a2f35935e7881b79ce23b2e7c63046c3014 Mon Sep 17 00:00:00 2001 From: zero318 Date: Sat, 2 Jul 2022 00:31:06 -0400 Subject: [PATCH 2/4] Add scratch registers for IN-StB and fix size arg sign --- src/formats/ecl/ecl_06.rs | 43 +++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/formats/ecl/ecl_06.rs b/src/formats/ecl/ecl_06.rs index 2e7ffc56..75808929 100644 --- a/src/formats/ecl/ecl_06.rs +++ b/src/formats/ecl/ecl_06.rs @@ -1003,7 +1003,7 @@ impl LanguageHooks for OldeEclHooks { Game::Th06 => enum_map::enum_map!{ ScalarType::Int => vec![ R(-10001), R(-10002), R(-10003), R(-10004), // I0-I3 - R(-10009), R(-10010), R(-10011), R(-10012), // I4-I7 + R(-10009), R(-10010), R(-10011), R(-10012), // IC0-IC3 ], ScalarType::Float => vec![ R(-10005), R(-10006), R(-10007), R(-10008), // F0-F3 @@ -1013,23 +1013,44 @@ impl LanguageHooks for OldeEclHooks { Game::Th07 => enum_map::enum_map!{ ScalarType::Int => vec![ R(10000), R(10001), R(10002), R(10003), // I0-I3 - R(10012), R(10013), R(10014), R(10015), // I4-I7 + R(10012), R(10013), R(10014), R(10015), // IC0-IC3 + //R(10029), R(10030), R(10031), R(10032), // PARAM_A-PARAM_D ], ScalarType::Float => vec![ R(10004), R(10005), R(10006), R(10007), // F0-F3 R(10008), R(10009), R(10010), R(10011), // F4-F7 R(10072), R(10074), // F8-F9 + //R(10033), R(10034), R(10035), R(10036), // PARAM_R-PARAM_N ], ScalarType::String => vec![], }, - Game::Th08 => enum_map::enum_map!{ + Game::Th08 | Game::Th09 => enum_map::enum_map!{ ScalarType::Int => vec![ - R(10000), R(10001), R(10002), R(10003), - R(10012), R(10013), R(10014), R(10015), + R(10000), R(10001), R(10002), R(10003), // I0-I3 + R(10004), R(10005), R(10006), R(10007), // I4-I7 + R(10036), R(10037), R(10038), R(10039), // IC0-IC3 + //R(10053), R(10054), R(10055), R(10056), // PARAM_A-PARAM_D ], ScalarType::Float => vec![ - R(10004), R(10005), R(10006), R(10007), - R(10008), R(10009), R(10010), R(10011), + R(10016), R(10017), R(10018), R(10019), // F0-F3 + R(10020), R(10021), R(10022), R(10023), // F4-F7 + R(10094), R(10095), // F8-F9 + //R(10057), R(10058), R(10059), R(10060), // PARAM_R-PARAM_N + ], + ScalarType::String => vec![], + }, + Game::Th095 => enum_map::enum_map!{ + ScalarType::Int => vec![ + R(10000), R(10001), R(10002), R(10003), // I0-I3 + R(10004), R(10005), R(10006), R(10007), // I4-I7 + R(10020), R(10021), R(10022), R(10023), // IC0-IC3 + //R(10036), R(10037), R(10038), R(10039), // PARAM_A-PARAM_D + ], + ScalarType::Float => vec![ + R(10008), R(10009), R(10010), R(10011), // F0-F3 + R(10012), R(10013), R(10014), R(10015), // F4-F7 + R(10077), R(10078), R(10079), R(10080), // F8-F11 + //R(10040), R(10041), R(10042), R(10043), // PARAM_R-PARAM_N ], ScalarType::String => vec![], }, @@ -1076,7 +1097,7 @@ impl InstrFormat for OldeEclHooks { fn read_instr(&self, f: &mut BinReader, emitter: &dyn Emitter) -> ReadResult { let time = f.read_i32()?; let opcode = f.read_u16()?; - let size = f.read_u16()? as usize; + let size = f.read_i16()? as usize; let before_difficulty = f.read_u8()?; // according to zero, not referenced in any game let difficulty = f.read_u8()?; let param_mask = f.read_u16()?; @@ -1110,7 +1131,7 @@ impl InstrFormat for OldeEclHooks { fn write_instr(&self, f: &mut BinWriter, _: &dyn Emitter, instr: &RawInstr) -> WriteResult { f.write_i32(instr.time)?; f.write_u16(instr.opcode)?; - f.write_u16(self.instr_size(instr) as _)?; + f.write_i16(self.instr_size(instr) as _)?; f.write_u8(0)?; f.write_u8(instr.difficulty)?; @@ -1126,7 +1147,7 @@ impl InstrFormat for OldeEclHooks { fn write_terminal_instr(&self, f: &mut BinWriter, _: &dyn Emitter) -> WriteResult { f.write_i32(-1)?; // time f.write_i16(-1)?; // opcode - f.write_u16(self.instr_header_size() as _)?; // size + f.write_i16(self.instr_header_size() as _)?; // size f.write_u16(0xff00)?; // difficulty f.write_u16(0x00ff)?; // param_mask Ok(()) @@ -1162,7 +1183,7 @@ impl InstrFormat for TimelineFormat06 { } let opcode = f.read_u16()?; - let size = f.read_u16()? as usize; + let size = f.read_i16()? as usize; let args_size = size.checked_sub(self.instr_header_size()).ok_or_else(|| { emitter.as_sized().emit(error!("bad instruction size ({} < {})", size, self.instr_header_size())) From 478d6848f2a1581f051de79de42c5f77c88e6312 Mon Sep 17 00:00:00 2001 From: Michael Lamparski Date: Sat, 23 Aug 2025 16:45:17 -0400 Subject: [PATCH 3/4] update testcase snapshot --- ...n__integration__type_check__diffswitch__missing_first.snap | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/compile-fail/integration__integration__type_check__diffswitch__missing_first.snap b/tests/compile-fail/integration__integration__type_check__diffswitch__missing_first.snap index d50bc101..4ef8b19c 100644 --- a/tests/compile-fail/integration__integration__type_check__diffswitch__missing_first.snap +++ b/tests/compile-fail/integration__integration__type_check__diffswitch__missing_first.snap @@ -9,6 +9,4 @@ error: unexpected token `:` │ ^ unexpected token │ = - Expected one of "!", "$", "%", "(", "++", "-", "--", "REG", "_S", "_f", "anim", "case", "cos", "default", "ecli", "entry", "float", "int", "mapfile", "offsetof", "script", "sin", "sqrt", "timeof", "~", FLOAT, FLOAT_RAD, IDENT, INSTR, INT or STRING - - + Expected one of "!", "$", "%", "(", "++", "-", "--", "REG", "_S", "_f", "acos", "anim", "asin", "atan", "case", "cos", "default", "ecli", "entry", "float", "int", "mapfile", "offsetof", "script", "sin", "sqrt", "tan", "timeof", "~", FLOAT, FLOAT_RAD, IDENT, INSTR, INT or STRING From 5a9eb9134156b65abdc05cd12fe4e71e02786cd6 Mon Sep 17 00:00:00 2001 From: Michael Lamparski Date: Sat, 23 Aug 2025 16:46:45 -0400 Subject: [PATCH 4/4] add signed integers to AST I began doing this a long time ago and got pulled away. Finishing up the remaining work was straightforward. --- src/ast/meta.rs | 5 ++- src/ast/mod.rs | 27 +++++++++++---- src/fmt.rs | 65 ++++++++++++++++++++++++++++++++---- src/formats/anm/mod.rs | 2 +- src/llir/raise/early.rs | 8 ++--- src/parse/lalrparser.lalrpop | 2 +- 6 files changed, 89 insertions(+), 20 deletions(-) diff --git a/src/ast/meta.rs b/src/ast/meta.rs index a9ede23b..1ce9b062 100644 --- a/src/ast/meta.rs +++ b/src/ast/meta.rs @@ -576,7 +576,10 @@ impl ToMeta for f32 { fn to_meta(&self) -> Meta { Meta::Scalar(sp!((*self).into())) } } impl ToMeta for bool { - fn to_meta(&self) -> Meta { Meta::Scalar(sp!(ast::Expr::LitInt { value: *self as i32, radix: ast::IntRadix::Bool })) } + fn to_meta(&self) -> Meta { Meta::Scalar(sp!(ast::Expr::LitInt { + value: *self as i32, + format: ast::IntFormat::BOOL, + }))} } impl ToMeta for String { fn to_meta(&self) -> Meta { Meta::Scalar(sp!(self.to_owned().into())) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 189008e5..5c6921cc 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -454,8 +454,8 @@ pub enum Expr { LitInt { value: raw::LangInt, /// A hint to the formatter on how it should write the integer. - /// (may not necessarily represent the original radix of a parsed token) - radix: IntRadix, + /// (not meaningful when parsing) + format: IntFormat, }, LitFloat { value: raw::LangFloat }, LitString(LitString), @@ -469,14 +469,29 @@ pub enum Expr { }, } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IntFormat { + pub signed: bool, + pub radix: IntRadix, +} + +impl IntFormat { + pub const UNSIGNED: IntFormat = IntFormat { signed: false, radix: IntRadix::Dec }; + pub const SIGNED: IntFormat = IntFormat { signed: true, radix: IntRadix::Dec }; + pub const HEX: IntFormat = IntFormat { signed: false, radix: IntRadix::Hex }; + pub const BIN: IntFormat = IntFormat { signed: false, radix: IntRadix::Bin }; + pub const BOOL: IntFormat = IntFormat { signed: true, radix: IntRadix::Bool }; + /// Used to decompile `jmp` in function syntax. + pub const SIGNED_HEX: IntFormat = IntFormat { signed: true, radix: IntRadix::Hex }; +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum IntRadix { /// Display as decimal. Dec, /// Display as hexadecimal, with an `0x` prefix. Hex, - /// Display as potentially negative hexadecimal, with an `0x` prefix. - SignedHex, /// Display as binary, with an `0b` prefix. Bin, /// Use `true` and `false` if the value is `1` or `0`. Otherwise, fall back to decimal. @@ -850,7 +865,7 @@ string_enum! { } impl From for Expr { - fn from(value: raw::LangInt) -> Expr { Expr::LitInt { value, radix: IntRadix::Dec } } + fn from(value: raw::LangInt) -> Expr { Expr::LitInt { value, format: IntFormat::SIGNED } } } impl From for Expr { fn from(value: raw::LangFloat) -> Expr { Expr::LitFloat { value } } @@ -1269,7 +1284,7 @@ macro_rules! generate_visitor_stuff { Expr::XcrementOp { op: _, order: _, var } => { v.visit_var(var); }, - Expr::LitInt { value: _, radix: _ } => {}, + Expr::LitInt { value: _, format: _ } => {}, Expr::LitFloat { value: _ } => {}, Expr::LitString(_s) => {}, Expr::LabelProperty { .. } => {}, diff --git a/src/fmt.rs b/src/fmt.rs index d6956d82..85c993ba 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -899,13 +899,36 @@ impl Format for ast::Expr { ast::Expr::XcrementOp { order: ast::XcrementOpOrder::Pre, op, var } => out.fmt((op, var)), ast::Expr::XcrementOp { order: ast::XcrementOpOrder::Post, op, var } => out.fmt((var, op)), ast::Expr::EnumConst { enum_name, ident } => out.fmt((enum_name, ".", ident)), - ast::Expr::LitInt { value: 0, radix: ast::IntRadix::Bool } => out.fmt("false"), - ast::Expr::LitInt { value: 1, radix: ast::IntRadix::Bool } => out.fmt("true"), - ast::Expr::LitInt { value, radix: ast::IntRadix::Bool } => out.fmt(value), - ast::Expr::LitInt { value, radix: ast::IntRadix::Dec } => out.fmt(value), - ast::Expr::LitInt { value, radix: ast::IntRadix::Hex } => out.fmt(format_args!("{:#x}", value)), - ast::Expr::LitInt { value, radix: ast::IntRadix::SignedHex } => out.fmt(format_args!("{:#x}", SignedRadix(*value))), - ast::Expr::LitInt { value, radix: ast::IntRadix::Bin } => out.fmt(format_args!("{:#b}", value)), + ast::Expr::LitInt { value, format } => match format { + // These are the decimal formats + &ast::IntFormat::SIGNED => out.fmt(value), + &ast::IntFormat::UNSIGNED => { + out.fmt(format_args!("{}", *value as u32)) + }, + + &ast::IntFormat { radix: ast::IntRadix::Hex, signed } => { + match signed { + false => out.fmt(format_args!("{:#x}", value)), + true => out.fmt(format_args!("{:#x}", SignedRadix(*value))), + } + }, + + &ast::IntFormat { radix: ast::IntRadix::Bin, signed } => { + match signed { + false => out.fmt(format_args!("{:#b}", value)), + true => out.fmt(format_args!("{:#b}", SignedRadix(*value))), + } + }, + + &ast::IntFormat { radix: ast::IntRadix::Bool, signed } => { + match (value, signed) { + (0, _) => out.fmt("false"), + (1, _) => out.fmt("true"), + (_, true) => out.fmt(value), + (_, false) => out.fmt(format_args!("{:#x}", *value as u32)), + } + }, + }, ast::Expr::LitFloat { value } => out.fmt(value), ast::Expr::LitString(x) => out.fmt(x), ast::Expr::LabelProperty { label, keyword } => out.fmt((keyword, "(", label, ")")), @@ -1136,4 +1159,32 @@ mod tests { assert!(reformat::(3, r#"meta { x: 25 }"#).ends_with("\n")); assert!(reformat::(9999, r#" script lol { nop(); }"#).ends_with("\n")); } + + #[test] + fn integer_formats() { + use ast::IntRadix as R; + + fn fmt_int(value: i32, signed: bool, radix: R) -> String { + stringify(&ast::Expr::LitInt { + value, + format: ast::IntFormat { signed, radix } + }) + } + + assert_eq!(fmt_int(20, true, R::Dec), "20"); + assert_eq!(fmt_int(-20, true, R::Dec), "-20"); + assert_eq!(fmt_int(-0x30, true, R::Hex), "-0x30"); + assert_eq!(fmt_int(-0x30, false, R::Hex), "0xffffffd0"); + assert_eq!(fmt_int(-0b100, true, R::Bin), "-0b100"); + + assert_eq!(fmt_int(0, true, R::Bool), "false"); + assert_eq!(fmt_int(0, false, R::Bool), "false"); + assert_eq!(fmt_int(1, true, R::Bool), "true"); + assert_eq!(fmt_int(1, false, R::Bool), "true"); + assert_eq!(fmt_int(2, true, R::Bool), "2"); + assert_eq!(fmt_int(2, false, R::Bool), "0x2"); + assert_eq!(fmt_int(-2, true, R::Bool), "-2"); + assert_eq!(fmt_int(-2, false, R::Bool), "0xfffffffe"); + } } + diff --git a/src/formats/anm/mod.rs b/src/formats/anm/mod.rs index ac3b5c8c..a1a8b311 100644 --- a/src/formats/anm/mod.rs +++ b/src/formats/anm/mod.rs @@ -686,7 +686,7 @@ fn format_to_meta(format_num: u32) -> Meta { } fn colorkey_to_meta(colorkey: u32) -> impl ToMeta { - ast::Expr::LitInt { value: colorkey as i32, radix: ast::IntRadix::Hex } + ast::Expr::LitInt { value: colorkey as i32, format: ast::IntFormat::HEX } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/llir/raise/early.rs b/src/llir/raise/early.rs index 7215ab48..60ebd8cf 100644 --- a/src/llir/raise/early.rs +++ b/src/llir/raise/early.rs @@ -181,7 +181,7 @@ fn early_raise_intrinsics( fn raise_mask(value: raw::ParamMask) -> ast::Expr { ast::Expr::LitInt { value: value.into(), - radix: ast::IntRadix::Bin, + format: ast::IntFormat::BIN, } } @@ -192,7 +192,7 @@ fn raise_nargs(value: raw::ArgCount) -> ast::Expr { fn raise_pop(value: raw::StackPop) -> ast::Expr { ast::Expr::LitInt { value: value.into(), - radix: ast::IntRadix::Hex, + format: ast::IntFormat::HEX, } } @@ -698,7 +698,7 @@ impl AtomRaiser<'_, '_> { }, | ArgEncoding::Color - => Ok(ast::Expr::LitInt { value: raw.expect_int(), radix: ast::IntRadix::Hex }), + => Ok(ast::Expr::LitInt { value: raw.expect_int(), format: ast::IntFormat::HEX }), | ArgEncoding::Float => Ok(ast::Expr::from(raw.expect_float())), @@ -721,7 +721,7 @@ impl AtomRaiser<'_, '_> { | Err(IllegalOffset) => { emitter.emit(warning!("invalid offset in a jump instruction")).ignore(); - Ok(ast::Expr::LitInt { value: raw.expect_int(), radix: ast::IntRadix::SignedHex }) + Ok(ast::Expr::LitInt { value: raw.expect_int(), format: ast::IntFormat::SIGNED_HEX }) }, }, } diff --git a/src/parse/lalrparser.lalrpop b/src/parse/lalrparser.lalrpop index b219afe1..1ce6dee4 100644 --- a/src/parse/lalrparser.lalrpop +++ b/src/parse/lalrparser.lalrpop @@ -612,7 +612,7 @@ ExprTerm: ast::Expr = { > > => ast::Expr::XcrementOp { var, op, order: ast::XcrementOpOrder::Post }, - => ast::Expr::LitInt { value, radix: ast::IntRadix::Dec }, + => ast::Expr::LitInt { value, format: ast::IntFormat::SIGNED }, => ast::Expr::LitFloat { value },