Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build/gen_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
])),
Expand Down
14 changes: 7 additions & 7 deletions doc/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 `<expr>` or a predecrement operation `--<var>`. 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!)
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion map/v2.anmm
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
61 fsin
62 fcos
63 ftan
64 acos
64 facos
65 fatan
66 validRad

Expand Down
5 changes: 4 additions & 1 deletion src/ast/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())) }
Expand Down
35 changes: 29 additions & 6 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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.
Expand Down Expand Up @@ -695,6 +710,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,
Expand All @@ -711,6 +730,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,
Expand Down Expand Up @@ -842,7 +865,7 @@ string_enum! {
}

impl From<raw::LangInt> 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<raw::LangFloat> for Expr {
fn from(value: raw::LangFloat) -> Expr { Expr::LitFloat { value } }
Expand Down Expand Up @@ -1261,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 { .. } => {},
Expand Down
18 changes: 9 additions & 9 deletions src/core_mapfiles/anm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))))),
Expand Down Expand Up @@ -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))),
Expand Down Expand Up @@ -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))),
Expand Down
75 changes: 64 additions & 11 deletions src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,24 +886,49 @@ 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)),
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, ")")),
Expand Down Expand Up @@ -1134,4 +1159,32 @@ mod tests {
assert!(reformat::<ast::ScriptFile>(3, r#"meta { x: 25 }"#).ends_with("\n"));
assert!(reformat::<ast::ScriptFile>(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");
}
}

2 changes: 1 addition & 1 deletion src/formats/anm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Loading