• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Blizzard's hidden jass2lua transpiler

Status
Not open for further replies.
Level 19
Joined
Jan 3, 2022
Messages
320
A few threads here have made me curious:
  1. "...then I found the automatic JASS to LUA converter." - Macadamia found by mistake that WorldEdit can transpile all Jass code to Lua. Nobody knows how exactly
  2. "Lua, embedded jass" - Tasyen found out, that Lua maps can have Jass code, automatically transpiled, with the //! beginusercode | //! endusercode preprocessor directive.
Internally the transpiler is called "jass2lua" and is statically included in WorldEdit.exe and "Warcraft III.exe". Well why would the game exe need the transpiler included. An oversight? No, they've left old Jass files untouched. But I found out a way to invoke it at will, the way they intended. Watch this:
JASS:
function PreloadFiles takes nothing returns nothing
    call PreloadStart()
    call Preload( "" )
call BlzSetAbilityTooltip(1097690227, "CUSTOMDATA", 0)
//" )
    call Preload( "" )
endfunction
//! beginusercode

local tbl = {}
print("Hello from the Lua world!", _VERSION)

//! endusercode
function a takes nothing returns nothing
 //" )
    call PreloadEnd( 0.0 )

endfunction
For a Lua map the Jass code is transpiled in memory to Lua and executed!
Lua:
Preloader("Preload-file-with-lua.txt")

I played around with Preload files a little but the results were disappointing:
  • Any Lua code declared as user code: works!
  • Lua functions inside Jass body (like print a string): crash, OK
  • global variables in Jass functions: crash
  • local variables in Jass functions (local real): crash
Later I found a tutorial which explained, that editor's global variables are unreachable for PreloadFiles Jass. Though it doesn't explain the local variable crash. They didn't expect this much hackery :'(

Still this begs the question: what are the features of their jass2lua transpiler? From my previous thread, it seems to support vJass' syntactic sugar because there're strings like library/module. How can it be invoked on arbitrary pieces of code without crashing? I haven't tried Tasyen's method yet in WE.
Maybe there're people who are interested to legally reverse engineer the code that's running on their machines and find a way to make it useful / tell us how good it is (in a clean room way). Can it beat cjass2lua?
 
Last edited:

~El

Level 17
Joined
Jun 13, 2016
Messages
556
I played around with Preload files a little but the results were disappointing:
  • Any Lua code declared as user code: works!
  • Lua functions inside Jass body (like print a string): crash, OK
  • global variables in Jass functions: crash
  • local variables in Jass functions (local real): crash
Preload files are very sensitive to really weird and small things, and tend to crash a lot, as you've observed. They work best when they contain no JASS code and are just a plain Lua file.

There are ways to get data in and out of Preload files, though, and you can use them as a way to persistently store data for a player.

Here's a simple proof-of-concept script (in typescript, admittedly), that can be used to write and read an arbitrary amount of data to/from Preload files: cerrie/preloader.ts at master · ceres-wc3/cerrie

I've used it with moderate success to implement a code live-reload system for WC3. You can find the rest of the code in the same repository.
 
Level 19
Joined
Jan 3, 2022
Messages
320

Testing results of Jass2Lua transpiler​

I've used @Tasyen 's method to test various Jass language constructs. I don't know vJass at all, even if works with JassHelper (vJass->Jass->Lua?), I decided to only test all the Jass features described in "JASS Manual" by Jeff Pang. Any ideas what else to test are welcome! :psmile:
  • Switch WorldEdit to Lua
    • If your map is currently in Jass:
      • Go to Trigger Editor, File > Export Triggers... (will create 2 files)
      • Delete all triggers on map
      • Now you can switch to Lua
      • Import Triggers...
  • Create a New Custom Script (Ctrl+U)
  • Insert Jass code between (no spaces before //! ):
    • //! endusercode
    • call BJDebugMsg("Hello from Jass world")
    • //! beginusercode
  • Extract "war3map.lua" from the map with an MPQ editor
    • Alternative desperate method: WorldEdit creates a temp dir at "/path/to/map/mapName.w3mTemp/" with all map files before packing it to MPQ map archive. However it is immediately removed after archiving.

Warcraft III World Editor version: 1.32k (6114), SHA256: 1e8faeed4b1593398af44ab5d6f2b68ab303a7fb375ec6a2ebdb4a26c088ea0e

Insights:​

Breaking:

  • (Length=0) Empty string equality comparison against null - See post below
    • I don't think this is a concern for real code, I can't imagine any valid example?
  • (2022 Update) Floating point comparisons: In Jass, 0.0 == 0.0009 is true and 0.0 == 0.001 is false. The epsilon is set to 0.001 for Jass equality comparison ==, reportedly NOT for != comparison.
    • In Lua the epsilon value is very small, so 0.0 == 0.0009 is false too. When converting maps to Lua, pay attention to number conditions and counters (like n + 0.1).
  • (Dec 2022): debug statement of Jass is handled improperly, see discussion below, starting post 18.

Note:​

  • __jarray(default_value) is used for Jass arrays and TypeDefine() function for Jass object type definitions.
  • Local uninitialized variables are always transpiled to have nil
    • Rules for local uninitialized arrays differ. They're always at least tables = {}, but Integer, Real, and Boolean are = __jarray(default_value) with 0 / 0.0 / false respectively. See post below for test code.
  • The transpiler parses code, discarding comments and indentation, indents automatically.
  • Octal numbers throw a syntax error during Jass verification
  • Three-letter FourCC codes throw a syntax error, single-letter codes are automatically converted at transpile time
  • Type information is lost in the output, but it's used for correct transpilation, for example integer division, string concatenation
  • Jass' debug instructions are converted into an if _DEBUG then wrapper: you can change it at run-time yourself

FYI:​

  • FourCC codes 'Amls' are converted as FourCC("Amls")
  • Jass code is checked for correctness, e.g. can't return string as number or use the return bug's construct with double returns
  • Unary plus in set a = +1 is removed correctly
  • Multi-line strings and \" are escaped and converted correctly.
    • I pasted the code directly into WE on Windows, so the line break was converted as "\r\n", not "\n"!

TLDR:​

Very solid work, it's a shame they weren't given enough time for their ambitions. I see no issues to use this for automatic conversion. You just have to correct all unset variable declarations in Jass, so they arrive in Lua with a default value set correctly. This is either some manual work or a quick and dirty script to do this with regex.

Code:
--[[ WorldEdit is set to Lua mode, this is a piece of pasted custom code that's in Jass
//! endusercode


//! beginusercode
]]
Lua:
--[[ WorldEdit is set to Lua mode, this is a piece of pasted custom code that's in Jass
]]

JASS:
type betterhandle         extends     handle
Lua:
TypeDefine('betterhandle', 'handle')

JASS:
globals

    integer glint = 1234
    real glreal = 12.34
    boolean b1 = true
    boolean b2 = false
    string s1 = "oi mate"
    integer objId = 'Amls'
    unit array myUnits
 
endglobals
Note: Original indenting was not preserved
Lua:
glint = 1234
glreal = 12.34
b1 = true
b2 = false
s1 = "oi mate"
objId = FourCC("Amls")
myUnits = {}

JASS:
globals

    constant integer constint = 314
    constant real constreal = 3.14
    constant playercolor PLAYER_COLOR_NAVY_TEST = ConvertPlayerColor(13)
 
endglobals
Note: Original indenting was not preserved
Lua:
constint = 314
constreal = 3.14
PLAYER_COLOR_NAVY_TEST = ConvertPlayerColor(13)

JASS:
function f1 takes nothing returns nothing

endfunction

function f2 takes integer a returns integer
    return a
endfunction

function f3 takes integer a, real b returns real
    return b
endfunction
Lua:
function f1()
end

function f2(a)
    return a
end

function f3(a, b)
    return b
end

JASS:
// Line 115: Syntax error - Types mismatch expected 'boolean', given 'string'
function f4 takes string s returns boolean
    return s
endfunction

JASS:
function intdiv takes integer a returns integer
    return a/2
endfunction
Lua:
function intdiv(a)
    return a // 2
end

JASS:
function strconcat takes string a returns string
    return a+a
endfunction
Lua:
function strconcat(a)
    return a .. a
end

JASS:
// Line 512: Syntax error - Identifier 'main' (function) redefinition (@c:/wc3maps/jass2lua.w3mTemp/war3map.j:152:1)
// Line   0: Unknown compile error - internal error in the Lua transpiler
// Line 512: Unknown compile error - Identifier 'main' (function) redefinition (@war3map.j:152:1)

function main takes nothing returns nothing
    local unit u
    set u = CreateUnit(player1, 'hC00', 100, 100, 90)
endfunction

JASS:
function varsAndArrays takes nothing returns nothing
    local integer i
    local integer iset = 0
    local integer n = 3
    local integer array intArr
 
    set i = 5
    set intArr[0] = 100
    set intArr[1] = 111
    set intArr[2] = 122
    set intArr[n] = 133
endfunction
Lua:
function varsAndArrays()
    local i
    local iset = 0
    local n = 3
    local intArr = __jarray(0)
    i = 5
    intArr[0] = 100
    intArr[1] = 111
    intArr[2] = 122
    intArr[n] = 133
end

JASS:
function hexAndOctalAndFourCC takes nothing returns nothing
    local integer objId_4 = 'Amls'
    // Line 195: Syntax error - syntax error
    // Line   0: Unknown compile error - internal error in the LUA transpiler
    // Line 195: Unknown compile error - syntax error
    // local integer objId_3 = 'mls'
    local integer objId_decimal100 = 'd'
 
    local integer zero = 0
    local integer hexzero = $0
    local integer hexB = $B
    local integer hexFF = $FF
 
    local integer hex0x0 = 0x0
    local integer hex0bigx0 = 0X0
    local integer hex0x1f = 0x1f
 
    local integer octal07 = 07
    // Line 211: Syntax error - syntax error
    // Line   0: Unknown compile error - internal error in the LUA transpiler
    // Line 211: Unknown compile error - syntax error
    // local integeroctal070 = 070
 
endfunction
Lua:
function hexAndOctalAndFourCC()
    local objId_4 = FourCC("Amls")
    local objId_decimal100 = 100
    local zero = 0
    local hexzero = 0x0
    local hexB = 0xb
    local hexFF = 0xff
    local hex0x0 = 0x0
    local hex0bigx0 = 0x0
    local hex0x1f = 0x1f
    local octal07 = 7
end

JASS:
function conditionals takes nothing returns nothing
    if ("_f equals in" == "parentheses") then
        call BJDebugMsg("Do something")
    elseif "maybe_lse_f" == "is true without parentheses" then
        call BJDebugMsg("Do something other")
    else
        call BJDebugMsg("Do something else")
    endif
 
endfunction
Lua:
function conditionals()
    if ("_f equals in" == "parentheses") then
        BJDebugMsg("Do something")
    elseif "maybe_lse_f" == "is true without parentheses" then
        BJDebugMsg("Do something other")
    else
        BJDebugMsg("Do something else")
    end
end

JASS:
function conditionalsNested takes nothing returns nothing
    if ("if equals in" == "parentheses") then
        call BJDebugMsg("Do something")
    elseif "maybe elseif" == "is true without parentheses" then
        call BJDebugMsg("Do something other")
        if ("did i get you" == "no") then
            call BJDebugMsg("From nested if")
        endif
    else
        call BJDebugMsg("Do something else")
    endif
 
endfunction
Lua:
function conditionalsNested()
    if ("if equals in" == "parentheses") then
        BJDebugMsg("Do something")
    elseif "maybe elseif" == "is true without parentheses" then
        BJDebugMsg("Do something other")
        if ("did i get you" == "no") then
            BJDebugMsg("From nested if")
        end
    else
        BJDebugMsg("Do something else")
    end
end

JASS:
function neverEnds takes nothing returns nothing
    loop
        call BJDebugMsg("Running in circles")
    endloop
endfunction
Lua:
function neverEnds()
    while (true) do
        BJDebugMsg("Running in circles")
    end
end

JASS:
function alwaysEnds takes nothing returns nothing
    loop
        call BJDebugMsg("Running around")
        exitwhen true
    endloop
endfunction
Lua:
function alwaysEnds()
    while (true) do
        BJDebugMsg("Running around")
        if (true) then break end
    end
end

JASS:
function buggyCode takes nothing returns nothing
    local integer one = 1
    set one = 1
    debug set one = 0
endfunction
Lua:
function buggyCode()
    local one = 1
    one = 1
    if _DEBUG then
        one = 0
    end
end

JASS:
function expressionism takes nothing returns nothing
    local integer one = 1
    local integer two
    local integer three
    local integer four
 
    local boolean yes = true
    local boolean no = false
    local boolean result = false
 
    set two = one + 1
    set two = one + one
 
    set four = two * two
 
    set three = four / two + one
 
    set result = yes == no
    set result = yes != no
    set result = yes and no
    set result = yes or no
    set result = yes and not no
 
    set result = one == two
    set result = one != two
    set result = one >= two
    set result = one <= two
    set result = one > two
    set result = one < two
 
 
endfunction
Lua:
function expressionism()
    local one = 1
    local two
    local three
    local four
    local yes = true
    local no = false
    local result = false
    two = one + 1
    two = one + one
    four = two * two
    three = four // two + one
    result = yes == no
    result = yes ~= no
    result = (yes and no)
    result = (yes or no)
    result = (yes and not no)
    result = one == two
    result = one ~= two
    result = one >= two
    result = one <= two
    result = one > two
    result = one < two
end

JASS:
function individualism takes nothing returns nothing
    local integer one = 1
    local integer minusOne = -1
    local integer plusOneInvalidLua = +1
 
    local boolean notTrue = not true
 
endfunction
Lua:
function individualism()
    local one = 1
    local minusOne = -1
    local plusOneInvalidLua = 1
    local notTrue = not true
end

JASS:
function thevoid takes nothing returns nothing
    // Syntax error
    //local integer intNull = null
    local string stringNull = null
    // Syntax error - Types mismatch expected 'real', given 'null'
    //local real realNull = null
 
    // Also Syntax error
    //local boolean booleanNull = null
    local boolean booleanEmpty
 
 
endfunction
Lua:
function thevoid()
    local stringNull = ""
    local booleanEmpty
end

JASS:
function dudeWhereAreMyComments takes nothing returns nothing
    // I am the comment you are looking for
    call BJDebugMsg("Believe it or not but there must be a comment above this line")
 
endfunction
Lua:
function dudeWhereAreMyComments()
    BJDebugMsg("Believe it or not but there must be a comment above this line")
end

JASS:
// Line 410: Syntax error - Types mismatch expected 'integer', given 'string'
function doubleReturnStr2Int takes string s returns integer
    return s
    return 0
endfunction

JASS:
native Pow_brokenFake takes real x, real power returns real

constant native MathRound_brokenFake takes real r returns integer
The corresponding Lua code was literally empty, all erased.

JASS:
function weirdStrings takes nothing returns nothing
    local string multilineJassString = "line1
line2"
    local string escapedN = "line1\nline2"
    local string escapedDoubleQuotes = "before\"after"
endfunction
Lua:
function weirdStrings()
    local multilineJassString = "line1\r\nline2"
    local escapedN = "line1\nline2"
    local escapedDoubleQuotes = "before\"after"
end
 

Attachments

  • Jass2Lua-transpiler-test-v1.w3m
    19.8 KB · Views: 36
Last edited:
Level 19
Joined
Jan 3, 2022
Messages
320
Well... good to know. Thanks! I was in the middle of writing this last post :peasant-shocked: As you see I have no idea of Jass, documentation is few and far between so I had assumed it assigns default values instead. I will update the above in a second. The transpiler is totally good to use then!
Below I've provided tests of all variables that can have "default" values in WorldEdit. Single variables and arrays, globally and (custom script) locally.
All single local variables are left as nil, however arrays are default 0 / 0.0 / false for Integer/Real/Boolean respectively thanks to __jarray() that sets the __index metatable. Other array types are just transpiled to {}.

Testing default variable values
Lua:
-- Single globals
udg_defaultBoolean = false
udg_defaultDialog = nil
udg_defaultInteger = 0
udg_defaultPlayerGroup = nil
udg_defaultReal = 0.0
udg_defaultTimer = nil
udg_defaultUnitGroup = nil
-- Array globals
udg_defaultBoolean_arr = __jarray(false)
udg_defaultReal_arr = __jarray(0.0)
udg_defaultPlayerGroup_arr = {}
udg_defaultInteger_arr = __jarray(0)
udg_defaultDialog_arr = {}
udg_defaultTimer_arr = {}
udg_defaultUnitGroup_arr = {}

function InitGlobals()
    local i = 0
    -- init single globals
    udg_defaultBoolean = false
    udg_defaultDialog = DialogCreate()
    udg_defaultInteger = 0
    udg_defaultPlayerGroup = CreateForce()
    udg_defaultReal = 0.0
    udg_defaultTimer = CreateTimer()
    udg_defaultUnitGroup = CreateGroup()
    -- init array globals
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultBoolean_arr[i] = false
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultReal_arr[i] = 0.0
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultPlayerGroup_arr[i] = CreateForce()
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultInteger_arr[i] = 0
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultDialog_arr[i] = DialogCreate()
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultTimer_arr[i] = CreateTimer()
        i = i + 1
    end
    i = 0
    while (true) do
        if ((i > 1)) then break end
        udg_defaultUnitGroup_arr[i] = CreateGroup()
        i = i + 1
    end
end

-- custom code like "local boolean defBool"
function testDefaultLocalVariables()
    local defBool
    local defDialog
    local defInteger
    local defPlayerGroup
    local defReal
    local defTimer
    local defUnitGroup
end

-- custom code like "local boolean array defBool"
function testDefaultLocalVariablesArrays()
    local defBool = __jarray(false)
    local defDialog = {}
    local defInteger = __jarray(0)
    local defPlayerGroup = {}
    local defReal = __jarray(0.0)
    local defTimer = {}
    local defUnitGroup = {}
end

Edit and PS: I've found a copy-pasted developer's explanation in Hive's Discord
0xeb said:
Backstory: LUA and Jass in the new PTR

We abstracted the scripting facilities from the game and divorced it from being stuck to a single engine: Jass2.

The Jass2 interpreter has been slightly modified but nothing major.
LUA is implemented as another scripting interface

The game engine now supports 2 scripting engines. The engine selection takes place on runtime based on the map type (decided in the editor).

Editor:
All graphical triggers in a LUA map are still emitted as Jass2 but converted to LUA on save.
Therefore there's an alpha version of a Jass2 to LUA converter in the editor.

We don't have Blizzard.lua or Common.lua or any other *.ai, *.pld scripts. Instead even if a map is a LUA map, when other parts of the map require Jass2 scripts, they will be converted to LUA and executed with LUA.

Therefore we have two modes of execution:
  • LUA: if at least there's one LUA scripts, all other scripts (Jass2) are converted to LUA and executed by LUA
  • Jass2/vJass: if only Jass scripts exist, then Jass2 engine (the same old one) will execute the scripts.

Begin/Enduser code:
These are only Jass constructs and should be used by the editor only. They won't work in a LUA script.

The "//! beginusercode" .... "//! endusercode" are meant to allow the editor to insert LUA code inside Jass2 code and then instruct the Jass2Lua converter to skip those code blocks and not convert them to LUA.

Natives and LUA:
If you want to learn about natives, you can check blizzard.j and common.j or other Jass scripts. LUA uses the exact same code (after the conversion takes place)

Hope that clears up everything.
 
Last edited:
Level 9
Joined
Jul 20, 2018
Messages
176
Check this.
JASS:
function A takes nothing returns nothing
    local integer i = 0
    if i == null then
        call BJDebugMsg("i is zero!")
    endif
endfunction
This is a correct JASS code, I am interested if null will be converted here to 0.

Jass' debug instructions are converted into an if _DEBUG then wrapper: you can change it at run-time yourself
This is interesting because JASS does not have debug mode. It is the feature of vJass which actually does not work because Blizzard bundled vJass poorly.
 
Level 19
Joined
Jan 3, 2022
Messages
320
First, debug is quick to explain: it actually is a Jass2 feature, see JASS Manual: Statements at the bottom. This flag is injected in luahelper.lua as _DEBUG = $debug$. vJass is a pre-processor and yes you can use your own debug there, but it will never end up in the final Jass code if debug mode in vJass was disabled.

Great suggestion, I went further and tested boolean, int, real, string.

Jass results, original:​

integer i = 0 is null
boolean b = false is null
string s = "" is NOT null
real r = 0 is null

Jass2Lua results: String is wrong!​

[OK] integer i = 0 is null
[OK] boolean b = false is null
[BAD] string s = "" is null
[OK] real r = 0 is null

I thought empty strings were supposed to be null too? Instead do they compare pointers/integer keys into the string table?

Null vs Integer test from here: All good​

Code:
[OK]  null<=0   -   true
[OK]  null==0   -   true
[OK]  null>=0   -   true
[OK]  null>0    -   false
[OK]  null<0    -   false
[OK]  null!=0   -   false

[OK]  null<=1   -   true
[OK]  null==1   -   false
[OK]  null>=1   -   false
[OK]  null>1    -   false
[OK]  null<1    -   true
[OK]  null!=1   -   true

JASS:
//! endusercode

function TestZeroVars takes nothing returns nothing
    local integer i = 0
    local real r = 0
    local boolean b = false
    local string s = ""

    if i == null then
        call BJDebugMsg("integer i = 0 is null!")
    else
        call BJDebugMsg("integer i = 0 is NOT null!")
    endif

    if r == null then
        call BJDebugMsg("real r = 0 is null!")
    else
        call BJDebugMsg("real r = 0 is NOT null!")
    endif

    if b == null then
        call BJDebugMsg("boolean b = false is null!")
    else
        call BJDebugMsg("boolean b = false is NOT null!")
    endif

    if s == null then
        call BJDebugMsg("string s = \"\" is null!")
    else
        call BJDebugMsg("string s = \"\" is NOT null!")
    endif
endfunction



function TestByteCodingBug takes nothing returns nothing
    local string boolDesc = "false"
    if null <= 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null<=0   -   "+boolDesc)
    
    set boolDesc = "false"
    if null == 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null==0   -   "+boolDesc)
    
    set boolDesc = "false"
    if null >= 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null>=0   -   "+boolDesc)
    
    set boolDesc = "false"
    if null > 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null>0   -   "+boolDesc)
    
    set boolDesc = "false"
    if null < 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null<0   -   "+boolDesc)
    
    set boolDesc = "false"
    if null != 0 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null!=0   -   "+boolDesc)
    
    call BJDebugMsg("============================")
    
    set boolDesc = "false"
    if null <= 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null<=1   -   "+boolDesc)
    
    set boolDesc = "false"
    if null == 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null==1   -   "+boolDesc)
    
    set boolDesc = "false"
    if null >= 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null>=1   -   "+boolDesc)
    
    set boolDesc = "false"
    if null > 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null>1   -   "+boolDesc)
    
    set boolDesc = "false"
    if null < 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null<1   -   "+boolDesc)
    
    set boolDesc = "false"
    if null != 1 then
        set boolDesc = "true"
    endif
    call BJDebugMsg("null!=1   -   "+boolDesc)
endfunction



//! beginusercode

Lua:
function TestZeroVars()
    local i = 0
    local r = 0.0
    local b = false
    local s = ""
    if i == 0 then
        BJDebugMsg("integer i = 0 is null!")
    else
        BJDebugMsg("integer i = 0 is NOT null!")
    end
    if r == 0.0 then
        BJDebugMsg("real r = 0 is null!")
    else
        BJDebugMsg("real r = 0 is NOT null!")
    end
    if b == false then
        BJDebugMsg("boolean b = false is null!")
    else
        BJDebugMsg("boolean b = false is NOT null!")
    end
    if s == "" then
        BJDebugMsg("string s = \"\" is null!")
    else
        BJDebugMsg("string s = \"\" is NOT null!")
    end
end

function TestByteCodingBug()
    local boolDesc = "false"
    if 0 <= 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null<=0   -   " .. boolDesc)
    boolDesc = "false"
    if 0 == 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null==0   -   " .. boolDesc)
    boolDesc = "false"
    if 0 >= 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null>=0   -   " .. boolDesc)
    boolDesc = "false"
    if 0 > 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null>0   -   " .. boolDesc)
    boolDesc = "false"
    if 0 < 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null<0   -   " .. boolDesc)
    boolDesc = "false"
    if 0 ~= 0 then
        boolDesc = "true"
    end
    BJDebugMsg("null!=0   -   " .. boolDesc)
    BJDebugMsg("============================")
    boolDesc = "false"
    if 0 <= 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null<=1   -   " .. boolDesc)
    boolDesc = "false"
    if 0 == 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null==1   -   " .. boolDesc)
    boolDesc = "false"
    if 0 >= 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null>=1   -   " .. boolDesc)
    boolDesc = "false"
    if 0 > 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null>1   -   " .. boolDesc)
    boolDesc = "false"
    if 0 < 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null<1   -   " .. boolDesc)
    boolDesc = "false"
    if 0 ~= 1 then
        boolDesc = "true"
    end
    BJDebugMsg("null!=1   -   " .. boolDesc)
end
 

Attachments

  • jass integer null test v1.32 lua.w3x
    17.3 KB · Views: 17
Level 18
Joined
Jan 1, 2018
Messages
728

Jass results, original:​

integer i = 0 is null
boolean b = false is null
string s = "" is NOT null
real r = 0 is null

Jass2Lua results: String is wrong!​

[OK] integer i = 0 is null
[OK] boolean b = false is null
[BAD] string s = "" is null
[OK] real r = 0 is null

I thought empty strings were supposed to be null too? Instead do they compare pointers/integer keys into the string table?
I find this a bit misleading, if you look at the transpiled code you see that the nulls have been replaced with the type's default values, so you're actually comparing 0 with 0, 0.0 with 0.0, false with false, and empty string with empty string. Of course these are always true. The only issue here is that the transpiler replaced a null string with an empty string, because these are not the same (in both jass and lua).

There actually are more issues with strings after transpiling from jass to lua. The two that I know of:
For substrings, if the starting offset is at the end of the string, both jass and lua return an empty string, but if the offset is at the end of the string + n, then in jass the result is null, while in lua the result is still an empty string.

The second issue, which probably explains the above two cases where null strings are replaced with an empty string in lua, is string concatenation where one of the arguments is nil, which will crash the trigger, while in jass the result is simply the not-null argument (or null if both arguments are null).
 
Level 19
Joined
Jan 3, 2022
Messages
320
As a matter of this topic I really only care whether the transpiler is 100% correctly translating Jass behavior to Lua. That's why I described it as wrong. Because strings behave differently, that's up to manual checking :cry: But before your reply I couldn't come up with a useful example.
Nulls from other languages are truly a pain in Lua, but in case of Jass the comparisons only need a substitute function that returns the correct result like StringEquals(a, b). Well it's an "alpha" version transpiler... and will forever remain one.
string, because these are not the same (in both jass and lua).

For substrings
Do you mean Jass API and SubString etc? The default string library in Lua returns "" or throws an error if values are wrong. The SubString function alone isn't an issue at all in my opinion, if you convert a whole map then you can substitute it with a function that always works as expected(tm).

Since you've brought up a valid use-case where it can break: gotta check the default natives and see how they behave. It'd be great if they're all consistently converted from null to "" when talking to Lua code but apparently not? The list is impressive at 74 matches:
Bash:
bash$ grep -P 'returns[\t\s]+string' * | sed -E 's/\s+/ /g'
JASS:
blizzard.j:function StringIdentity takes string theString returns string
blizzard.j:function SubStringBJ takes string source, integer start, integer end returns string
blizzard.j:function GetAbilityEffectBJ takes integer abilcode, effecttype t, integer index returns string
blizzard.j:function GetAbilitySoundBJ takes integer abilcode, soundtype t returns string
blizzard.j:function GetLastPlayedMusic takes nothing returns string
blizzard.j:function UnitId2StringBJ takes integer unitId returns string
blizzard.j:function OrderId2StringBJ takes integer orderId returns string
blizzard.j:function GetStoredStringBJ takes string key, string missionKey, gamecache cache returns string
blizzard.j:function LoadStringBJ takes integer key, integer missionKey, hashtable table returns string
blizzard.j:function GetAbilityName takes integer abilcode returns string
blizzard.j:function MeleeGetCrippledWarningMessage takes player whichPlayer returns string
blizzard.j:function MeleeGetCrippledTimerMessage takes player whichPlayer returns string
blizzard.j:function MeleeGetCrippledRevealedMessage takes player whichPlayer returns string
cheats.j:function TertiaryStringOp takes boolean expr, string a, string b returns string
cheats.j:function DebugIdInteger2IdString takes integer value returns string
common.j:constant native OrderId2String takes integer orderId returns string
common.j:constant native UnitId2String takes integer unitId returns string
common.j:constant native AbilityId2String takes integer abilityId returns string
common.j:constant native GetObjectName takes integer objectId returns string
common.j:native I2S takes integer i returns string
common.j:native R2S takes real r returns string
common.j:native R2SW takes real r, integer width, integer precision returns string
common.j:native SubString takes string source, integer start, integer end returns string
common.j:native StringCase takes string source, boolean upper returns string
common.j:native GetLocalizedString takes string source returns string
common.j:native GetPlayerName takes player whichPlayer returns string
common.j:constant native ParseTags takes string taggedString returns string
common.j: //constant native string GetTriggeringVariableName takes nothing returns string
common.j:constant native GetSaveBasicFilename takes nothing returns string
common.j:constant native GetEventPlayerChatString takes nothing returns string
common.j:constant native GetEventPlayerChatStringMatched takes nothing returns string
common.j:native GetDestructableName takes destructable d returns string
common.j:constant native GetItemName takes item whichItem returns string
common.j:native GetHeroProperName takes unit whichHero returns string
common.j:constant native GetUnitName takes unit whichUnit returns string
common.j:native GetStoredString takes gamecache cache, string missionKey, string key returns string
common.j:native LoadStr takes hashtable table, integer parentKey, integer childKey returns string
common.j:native SkinManagerGetLocalPath takes string key returns string
common.j:native LeaderboardGetLabelText takes leaderboard lb returns string
common.j:native MultiboardGetTitleText takes multiboard lb returns string
common.j:native BlzCameraSetupGetLabel takes camerasetup whichSetup returns string
common.j:native GetDialogueSpeakerNameKey takes sound soundHandle returns string
common.j:native GetDialogueTextKey takes sound soundHandle returns string
common.j:native GetAbilityEffect takes string abilityString, effecttype t, integer index returns string
common.j:native GetAbilityEffectById takes integer abilityId, effecttype t, integer index returns string
common.j:native GetAbilitySound takes string abilityString, soundtype t returns string
common.j:native GetAbilitySoundById takes integer abilityId, soundtype t returns string
common.j:native BlzGetAbilityTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityActivatedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityActivatedExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityResearchTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityResearchExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityIcon takes integer abilCode returns string
common.j:native BlzGetAbilityActivatedIcon takes integer abilCode returns string
common.j:native BlzGetItemDescription takes item whichItem returns string
common.j:native BlzGetItemTooltip takes item whichItem returns string
common.j:native BlzGetItemExtendedTooltip takes item whichItem returns string
common.j:native BlzGetItemIconPath takes item whichItem returns string
common.j:native BlzGetAnimName takes animtype whichAnim returns string
common.j:native RequestExtraStringData takes integer dataType, player whichPlayer, string param1, string param2, boolean param3, integer param4, integer param5, integer param6 returns string
common.j:native BlzFrameGetName takes framehandle frame returns string
common.j:native BlzFrameGetText takes framehandle frame returns string
common.j:native BlzGetTriggerFrameText takes nothing returns string
common.j:native BlzGetTriggerSyncPrefix takes nothing returns string
common.j:native BlzGetTriggerSyncData takes nothing returns string
common.j:native BlzGetLocale takes nothing returns string
common.j:// native BlzFourCC2S takes integer value returns string
common.j:native BlzGetAbilityStringField takes ability whichAbility, abilitystringfield whichField returns string
common.j:native BlzGetAbilityStringLevelField takes ability whichAbility, abilitystringlevelfield whichField, integer level returns string
common.j:native BlzGetAbilityStringLevelArrayField takes ability whichAbility, abilitystringlevelarrayfield whichField, integer level, integer index returns string
common.j:native BlzGetItemStringField takes item whichItem, itemstringfield whichField returns string
common.j:native BlzGetUnitStringField takes unit whichUnit, unitstringfield whichField returns string
common.j:native BlzGetUnitWeaponStringField takes unit whichUnit, unitweaponstringfield whichField, integer index returns string
 
Level 18
Joined
Jan 1, 2018
Messages
728
As a matter of this topic I really only care whether the transpiler is 100% correctly translating Jass behavior to Lua. That's why I described it as wrong. Because strings behave differently, that's up to manual checking :cry: But before your reply I couldn't come up with a useful example.
Nulls from other languages are truly a pain in Lua, but in case of Jass the comparisons only need a substitute function that returns the correct result like StringEquals(a, b). Well it's an "alpha" version transpiler... and will forever remain one.



Do you mean Jass API and SubString etc? The default string library in Lua returns "" or throws an error if values are wrong. The SubString function alone isn't an issue at all in my opinion, if you convert a whole map then you can substitute it with a function that always works as expected(tm).
Yea it's nice that you can fix natives with lua, I did this already for the examples that I posted: Map Transpiler
Like it says in that post, 'nil .. nil' will not work though, for that you'd have to change the transpiler to output an invocation to a custom function like 'StringConcat(a, b)' instead of using the concat operator.
 
Level 19
Joined
Jan 3, 2022
Messages
320
I've tested input and return values and types for the Lua API using Lua ingame console and a short typeof function:
Code:
function typeof(...)
    local t,n={...},select("#", ...)
    for i=1,n do print(tostring(t[i]), type(t[i])) end
end
JASS:
blizzard.j:function StringIdentity takes string theString returns string
-> returns localized string from "Identity String"
-> "abc" -> "abc", empty string too
-> nil throws error, expected string

blizzard.j:function SubStringBJ takes string source, integer start, integer end returns string
-> see SubString

blizzard.j:function GetAbilityEffectBJ takes integer abilcode, effecttype t, integer index returns string
-> typecheck on effecttype, todo
blizzard.j:function GetAbilitySoundBJ takes integer abilcode, soundtype t returns string

blizzard.j:function GetLastPlayedMusic takes nothing returns string
-> (empty) string

blizzard.j:function UnitId2StringBJ takes integer unitId returns string
-> random number/string -> empty string
-> nil -> error number expected, got nil

blizzard.j:function OrderId2StringBJ takes integer orderId returns string
-> see above

blizzard.j:function GetStoredStringBJ takes string key, string missionKey, gamecache cache returns string
-> "123", "123", nil -> empty string
-> "123", "123", 123 -> error on arg #1

blizzard.j:function LoadStringBJ takes integer key, integer missionKey, hashtable table returns string
-> "123", "123", nil -> empty string
-> "123", "123", "boo" -> err arg #1, hashtable expected

blizzard.j:function GetAbilityName takes integer abilcode returns string
-> random integer -> "Default string"
-> too big integer -> "number has no integer" representation
-> valid ID (number, string autoconverted) -> ability name string

blizzard.j:function MeleeGetCrippledWarningMessage takes player whichPlayer returns string
-> MeleeGetCrippledWarningMessage(GetLocalPlayer()) -> localized string, ends with \n
-> nil instead of player -> localized string for Alliance
-> else (including 0) -> error

blizzard.j:function MeleeGetCrippledTimerMessage takes player whichPlayer returns string
-> same as above
blizzard.j:function MeleeGetCrippledRevealedMessage takes player whichPlayer returns string
-> not tested, see above

cheats.j:function TertiaryStringOp takes boolean expr, string a, string b returns string
cheats.j:function DebugIdInteger2IdString takes integer value returns string
-> cheats.j wasn't loaded

common.j:constant native OrderId2String takes integer orderId returns string
-> random number or string "0-9" or proper hexadecimal strings ("0x2") -> empty string
-> else number expected got no value/string

common.j:constant native UnitId2String takes integer unitId returns string
common.j:constant native AbilityId2String takes integer abilityId returns string
-> not tested

common.j:constant native GetObjectName takes integer objectId returns string
-> see OrderId2String

common.j:native I2S takes integer i returns string
-> anything except integer errors

common.j:native R2S takes real r returns string
-> apparently equivalent to string.format("%.3f")
-> 1.2 -> "1.200"
-> 1.123 -> "1.123"
-> math.huge -> inf
-> 0/0 -> 1.000 !!! must be at least NaN (actually -NaN)
-> Generally, WC3 Lua: all other functions etc. returned 1.0 for 0/0 (NaN): string.format, tostring etc.!!!
-> WC3 Lua: print(2*(0/0)) -> 2.0 !!!

common.j:native R2SW takes real r, integer width, integer precision returns string
-> see OrderId2String's handling of number arguments

common.j:native SubString takes string source, integer start, integer end returns string
-> substring or empty string, no errors or nil

common.j:native StringCase takes string source, boolean upper returns string
-> any expression or variable that is TRUE / FALSE for Lua equals true/false
-> e.g. true: "1" and "0" and {} and 1
-> false: false, nil

common.j:native GetLocalizedString takes string source returns string
-> "REFORGED" -> "Reforged"
-> strings. numbers auto-converted to string, everything else error

common.j:native GetPlayerName takes player whichPlayer returns string
-> wrong types throw error, full string with tag#123 if correct

common.j:constant native ParseTags takes string taggedString returns string
-> strings. numbers auto-converted to string, everything else error

common.j: //constant native string GetTriggeringVariableName takes nothing returns string
-> doesn't exist in Lua

common.j:constant native GetSaveBasicFilename takes nothing returns string
-> (empty) string

common.j:constant native GetEventPlayerChatString takes nothing returns string
-> string. empty strings possible?

common.j:constant native GetEventPlayerChatStringMatched takes nothing returns string
-> (empty) string

common.j:native GetDestructableName takes destructable d returns string
-> nil -> empty string
-> destructable handle -> ?
-> else -> error

common.j:constant native GetItemName takes item whichItem returns string
-> same as above
common.j:native GetHeroProperName takes unit whichHero returns string
common.j:constant native GetUnitName takes unit whichUnit returns string


common.j:native GetStoredString takes gamecache cache, string missionKey, string key returns string
common.j:native LoadStr takes hashtable table, integer parentKey, integer childKey returns string
-> htbl = InitHashtable() // type: "userdata", tostring: "hashtable: <address>"
-> accessing random missing parent key & child -> returns empty string


// the rest remains untested
common.j:native SkinManagerGetLocalPath takes string key returns string
common.j:native LeaderboardGetLabelText takes leaderboard lb returns string
common.j:native MultiboardGetTitleText takes multiboard lb returns string
common.j:native BlzCameraSetupGetLabel takes camerasetup whichSetup returns string
common.j:native GetDialogueSpeakerNameKey takes sound soundHandle returns string
common.j:native GetDialogueTextKey takes sound soundHandle returns string
common.j:native GetAbilityEffect takes string abilityString, effecttype t, integer index returns string
common.j:native GetAbilityEffectById takes integer abilityId, effecttype t, integer index returns string
common.j:native GetAbilitySound takes string abilityString, soundtype t returns string
common.j:native GetAbilitySoundById takes integer abilityId, soundtype t returns string
common.j:native BlzGetAbilityTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityActivatedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityActivatedExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityResearchTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityResearchExtendedTooltip takes integer abilCode, integer level returns string
common.j:native BlzGetAbilityIcon takes integer abilCode returns string
common.j:native BlzGetAbilityActivatedIcon takes integer abilCode returns string
common.j:native BlzGetItemDescription takes item whichItem returns string
common.j:native BlzGetItemTooltip takes item whichItem returns string
common.j:native BlzGetItemExtendedTooltip takes item whichItem returns string
common.j:native BlzGetItemIconPath takes item whichItem returns string
common.j:native BlzGetAnimName takes animtype whichAnim returns string
common.j:native RequestExtraStringData takes integer dataType, player whichPlayer, string param1, string param2, boolean param3, integer param4, integer param5, integer param6 returns string
common.j:native BlzFrameGetName takes framehandle frame returns string
common.j:native BlzFrameGetText takes framehandle frame returns string
common.j:native BlzGetTriggerFrameText takes nothing returns string
common.j:native BlzGetTriggerSyncPrefix takes nothing returns string
common.j:native BlzGetTriggerSyncData takes nothing returns string
common.j:native BlzGetLocale takes nothing returns string
common.j:// native BlzFourCC2S takes integer value returns string
common.j:native BlzGetAbilityStringField takes ability whichAbility, abilitystringfield whichField returns string
common.j:native BlzGetAbilityStringLevelField takes ability whichAbility, abilitystringlevelfield whichField, integer level returns string
common.j:native BlzGetAbilityStringLevelArrayField takes ability whichAbility, abilitystringlevelarrayfield whichField, integer level, integer index returns string
common.j:native BlzGetItemStringField takes item whichItem, itemstringfield whichField returns string
common.j:native BlzGetUnitStringField takes unit whichUnit, unitstringfield whichField returns string
common.j:native BlzGetUnitWeaponStringField takes unit whichUnit, unitweaponstringfield whichField, integer index returns string

Conclusion:​

  • NaN values were automatically reset to 1.0 during math operations!
  • All null strings are returned as a zero-length string: "" (instead of an error or nil). This is consistent across the board
    • For localized string functions if none was found, the input is returned
    • Ability descriptions returned "Default string" instead of "" for erroneous values
  • Where type number was expected, Lua API converted number strings automatically
    • (number) 123
    • (string) "123" -> 123
    • (string) "0x7b" -> 123
  • Where type boolean was expected, non-boolean types were converted according to Lua rules (e.g. "" and {} are true)
  • Strict type-checking on WC3's custom types (handles, represented as userdata)
    • yet nil was an acceptable value in these cases, returning default value (see MeleeGetCrippledWarningMessage)
I didn't test Jass behavior with equivalent inputs. I am glad strings are handled consistently. If you rewrite Jass to Lua or use the transpiler, continue to be cautious of string comparisons where str == nil, and str == ""

Type conversion via engine's Hashtables​

I suppose this is identical to Jass:
Code:
htbl = InitHashtable()
typeof(SaveStr(htbl, 3, 3, "strring"))
-> true, boolean
typeof(LoadStr(htbl,3,3))
-> strring, string

typeof(SaveStr(htbl, 3, 3, nil))
-> error, string expected

typeof(SaveStr(htbl, 3, 3, ""))
-> true, boolean

typeof(LoadStr(htbl, 3, 3))
-> , string

typeof(LoadInteger/Real/Boolean(htbl, 3, 3))
-> 0, number
-> 0.0, number
-> false, boolean


This will be my final post for a while, I've researched all I needed to begin a Jass map's conversion. Input and quirks are welcome.

Update: transpiler test of double return of the same type:
JASS:
function doubleBooleanTrueFalse takes nothing returns boolean
    return true // this will be returned
    return false // misleading
endfunction

function getDoubleBool takes nothing returns string
    local boolean b = doubleBooleanTrueFalse()
    if b == true then
        return "true"
    elseif b == false then
        return "false"
    else
        return "null/unknown"
    endif
endfunction
That's correctly transpiled to:
Lua:
function myfunction()
    do return true end
    return false
end
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Luashine I am curious to know your thoughts on my vJass2Lua converter, which does, in fact, use a ton of regex find and replace to do the job.

 
Level 19
Joined
Jan 3, 2022
Messages
320
@Bribe my response is the following:

I will admit that I've converted the GUI triggers of a map I work on with Lua pattern matching too but that was simple Jass. Overall Jass is so simple that regexp'ing your way through 98% of it is possible and will work, at least because there're no multi-line statements. My own end goal would be to write a parser that takes Jass and all of its varieties (vJass, cJass, Zinc...) and output an almost ideomatic Lua code, or at least something that doesn't look like machine code but with keywords if-then-endif (this is what vJass->Jass->Lua looks like). I think you're wasting time instead of taking your time to learn and write a parser.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Thank you, I appreciate your feedback. I’m not sure it’s what kind of feedback I was looking for, but I can see where you’re coming from, and it makes perfect sense.

Like you’ve mentioned, regex can do about 98% of what’s out there. But I disagree that a parser should need to include everything. The way I see it, there’s a limited stash of vJass resources out there, and all we need to do is convert the important ones to Lua. Blizzard doesn’t offer a silver bullet solution to this problem, and I’m just trying to envision a way to get people moved out of the dead-end vJass ecosystem.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
So, my last reply was a bit shorter as I had run out of time. I had meant to expand on this a bit further, if you don't mind hearing me out.

I started with this little project of mine by being totally overwhelmed by the complexity of cJass2Lua, and underwhelmed by its (in)ability to handle vJass.

This is why I do not think we need a parser that supports cJass, because one already exists.

As for Zinc, I think it's a mess of a language, far less complete than even vJass, and it is literally impossible to parse with a regex tool.

I'm not sure why you would bring up cJass as being in need of a parser, but not Wurst. Wurst's parser already compiles to Lua, however, so is just as independent as cJass here.

As for library count of resources, I'd say between vJass and Zinc, it is apparent that Zinc never "took off". The fact that textmacros didn't play well between the two resources played some part in it, but I'd say the main reason for Zinc's failure is because its only benefit was anonymous functions - which, to most, was not enough of a reason to swap over.

If your goal is to develop something to parse everything, I wouldn't even know where you would start with that. Part of the reason that these sorts of things are so under-developed is due to the massive amount of time necessary to get them off of the ground. cJass and JassHelper are both full of spaghetti code that doesn't make any sense to anyone other than the people who wrote them at the time they were written. If you asked Vexorian now to look over his code, he wouldn't have a clue where to start. Not sure about VanDamm. The mountain of the problem is that neither of those parsers were well-documented within the code, and the variable names don't make any sense at all.

What is really my motivation behind all of this, is that we have a massively-underutilized, very powerful language in Lua that we are just scratching the surface of as a community. GUI variables being able to be treated as function calls is one of the biggest breakthroughs in WarCraft 3 coding history (if you're looking to cater to a wide audience, you need to cater to GUI). The problem is, it took years to even discover, because I am (sadly) one of the very few people who write in code but still care about the GUI community. Thanks to Global Variable Remapper, big systems can have GUI extensions while at zero performance cost. None of this would be possible without Lua.
 
Level 19
Joined
Jan 3, 2022
Messages
320
This kind of insight changes a lot. I haven't looked at available resources and what languages besides vanilla Jass they're written in, but if you say that Zinc practically remained unused, then it's almost not worth entertaining that idea at all.
cJass and JassHelper are both full of spaghetti code that doesn't make any sense to anyone other than the people who wrote them at the time they were written. If you asked Vexorian now to look over his code, he wouldn't have a clue where to start. Not sure about VanDamm. The mountain of the problem is that neither of those parsers were well-documented within the code, and the variable names don't make any sense at all.
That's good to know, because the first thought is "just change their transpilers to output Lua code" and if that's harder than anticipated, sure does makes you question if that's worth the time, because the effort needed to accomplish the above has doubled if not more.
Following that, a question: you must've thought to expand JassHelper too and decided not to?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
This kind of insight changes a lot. I haven't looked at available resources and what languages besides vanilla Jass they're written in, but if you say that Zinc practically remained unused, then it's almost not worth entertaining that idea at all.

That's good to know, because the first thought is "just change their transpilers to output Lua code" and if that's harder than anticipated, sure does makes you question if that's worth the time, because the effort needed to accomplish the above has doubled if not more.
Following that, a question: you must've thought to expand JassHelper too and decided not to?
As far as I know, the only person besides Vexorian who was able to understand JassHelper (and added to it) was @cohadar. He managed to change the initialization order of onInit methods and added some “for loop” stuff. But his branch didn’t get adopted and introduced some new bugs.

Even changing the output of JassHelper doesn’t do much of anything, because JassHelper complies into the entire war3map.j file, which is not what you want if you want triggers and syntax that make sense in the context they were written in. I’ve developed vJass style struct in order to allow my parser to keep the layout and syntax of the code familiar to people who understood the original vJass that went into the code.

JassHelper would, in the worst of cases, take a single vJass function and explode it into as many as 3-4 copies of itself. This was to handle “functions as objects”. The underlying code was actually using triggerconditions and triggeractions in conjunction with TriggerEvaluate/Execute to “call” functions.

A beautiful aspect of Lua is that it doesn’t get “uglified” or “obfuscated” when the map gets saved. Most of what JassHelper does is try to make vJass make sense to JASS.

The main missing element in my mind is “pJass” because we don’t have a Lua syntax checker. WarCraft 3 checks basic syntax like missing endblocks, but it is a pain to debug. Thankfully, @Eikonium gave us [Lua] - Debug Utils (Ingame Console etc.) and gave us a great tutorial on VScode which allows us to check Lua syntax on the fly.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Has anyone run tests to determine how "debug" lines compile in the regular Jass2Lua interpreter when it can't directly enclose those single debug lines with "if _DEBUG" then?

JASS:
function DebugTest takes nothing returns nothing
    if false then
    debug elseif false then
        call BJDebugMsg("This never prints.")
    debug else
        call BJDebugMsg("This only prints when in debug mode.")
    endif
endfunction

The transcompiler would have to do this:

Lua:
function DebugTest()
    if false then
    elseif _DEBUG then
        if false then
            BJDebugMsg("This never prints.")
        else
            BJDebugMsg("This only prints when in debug mode.")
        end
    end
end

But then, it could get weird:

JASS:
function DebugTest takes nothing returns nothing
    if false then
    debug elseif true then
        call BJDebugMsg("This only prints in Debug Mode.")
    elseif true then
        call BJDebugMsg("This only prints when not in debug mode.")
    debug elseif true then
        call BJDebugMsg("This second part also only prints when not in debug mode.")
    else
        call BJDebugMsg("This never prints.")
    endif
endfunction
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That's interesting - thanks for testing and sharing! So it completely ignores the request to only run those lines in debugging circumstances in order to reduce the complexity of handling that logic.

Currently in vJass2Lua, I have commented-out all of the lines starting with "debug", but in the next big update I wanted it to try to use "if _DEBUG then" wherever possible. I just wasn't sure what Blizzard's approach was to handling this complex process.

I think the best solution is that I give the user the choice to transcompile vJass into Lua with debug functionality enabled, or disabled.
 
Level 9
Joined
Jul 20, 2018
Messages
176
According to docs, debug can be set only before the whole if then else, and the fact that you can set it before elseif or only else is a lack of proper syntax check in Blizzard Jass2Lua transpiler. You cannot do this in Jass2.

1669894015687.png


debug works differently in vJass: it comments out line, not whole blocks as Jass2 does. To run block only when vJass debug enabled, all its lines must be marked with debug or it wholly must be wrapped with static if DEBUG then block.

Thus, I think vJass2Lua must do the same approach as vJass does: (a) make a compile-time flag to enable debug, (b) if flag is set, ignore all debug-ed lines while transpiling.
 
Last edited:
Status
Not open for further replies.
Top