- Joined
- Nov 7, 2014
- Messages
- 571
JiffyJass - standalone Jass interpreter
Differences between Blizzard's Jass and JiffyJass:
Usage
The command line invocation requires a command name followed by a command specific arguments:
run command:
Executes the script in the specified files by first initializing all global variables and then calls function main. The '--' separates the list of script files from the list of command line arguments that can be accessed by the script.
The order of the script files is important. If file-1 depends on a declaration in file-2 then file-1 needs to come before file-2 in the run command invocation.
check command:
Checks the specified files for parsing and typing errors. Does not run the script.
help command:
Prints information about the other available commands.
Alias declarations
The 'alias' identifier is a reserved keyword in Jass (a variable with the name 'alias' cannot be declared). I don't know the semantics of the alias keyword in Blizzard's Jass but in JiffyJass it can be used to declare aliases to types, natives and functions.
Functions as values
All functions can now be used as values, i.e they can be stored in variables, passed as arguments and returned from functions. There are no anonymous functions/lambdas though.
Arrays as values
Arrays are now first class values i.e they can be stored in variables, passed as arguments and returned from functions. There are 2 new keywords that are used to create and destroy array values. The keyword 'new' is used to create a new array value with a specified element type. The 'delete' keyword is used to destroy an array value when it's no longer needed.
For backwards compatibility global arrays are automatically 'new'ed. Local arrays without an initializer are zero initialized like other types.
There are no multidimensional arrays. One can emulate them with 1 dimensional arrays and indexing math or with jagged arrays.
There is no function overloading but there are builtin "generic" functions that can work with an array of any type. The placeholder type 'any' stands for any type.
The functions 'arrlen', 'arrsetlen', 'arrput' and 'arrpop' query/modify an internal array "length" field. All arrays can still be indexed from 0 to JASS_ARRAY_SIZE-1 but this "length" field can be used to keep track of how many elements are logically in the array.
Maps
Maps/hashtables are similar to arrays but their index/key type is not restricted to integers. For symmetry with arrays they are automatically 'new'ed in globals blocks as well.
There are a few "generic" functions for maps as well. Again, the type 'any' here stands for any type.
User defined types/structs
Structs are an aggregation of fields. They have reference semantics just like arrays and maps, i.e an assignment of 2 variables of a struct type does not copy the values of their corresponding fields, instead the two variables refer to the same struct value. Structs can extend other structs, this is the same as copying the fields of the base struct to the fields of the extending struct. If a struct does not extend another struct then it implicitly extends the 'Any' struct, which has no fields. Struct values have a builtin 'typename' field which returns the name of their struct type. This can be used to test the runtime type of a struct value and to safely do a downcast.
Another way to do dynamic dispatch, instead of the case analysis above. is to use fields of function types.
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
Differences between Blizzard's Jass and JiffyJass:
- characters in single quotes (rawcode literals) are used verbatim (no backslash escaping, i.e '\n' is an error)
- there are no octal literals (0377 == 377, not 255)
- globals blocks, types and natives can be declared in any order
- variables that are not initialized on declaration are zero initialized
- variable shadowing is not allowed
- constant functions are not checked for global variable assignment
- the binary '%' operator always returns a positive value (euclidean remainder)
- an integer to real cast is also inserted in return statements
- operations with reals (floating point numbers) can produce infinities and nans
- strings are not Unicode aware
- functions with any signature (not just code (taking/returing nothing)) can be used as values
- arrays are first class values
- maps, which are like arrays but their index/key can be any type, not just an integer
- user defined types called structs
Usage
The command line invocation requires a command name followed by a command specific arguments:
Code:
jj <command> <command-args>
run command:
Code:
jj run <files> [-- <script-args>]
// examples:
jj run main.j blizzard.j common.j
jj run main.j -- arg1 arg2
jj run common.j main.j // wrong file order
The order of the script files is important. If file-1 depends on a declaration in file-2 then file-1 needs to come before file-2 in the run command invocation.
check command:
Code:
jj check <files>
// examples:
jj check main.j common.j
help command:
Code:
jj help
jj // same as jj help
Alias declarations
The 'alias' identifier is a reserved keyword in Jass (a variable with the name 'alias' cannot be declared). I don't know the semantics of the alias keyword in Blizzard's Jass but in JiffyJass it can be used to declare aliases to types, natives and functions.
JASS:
alias Foo = integer // 'Foo' is an alias for the 'integer' type
globals
Foo foo = 1
endglobals
native GetHandleId takes handle h returns integer
alias H2I = GetHandleId // 'H2I' is an alias for the 'GetHandleId' native
function veryLongFunctionName takes nothing returns nothing
endfunction
alias shorterName = veryLongFunctionName // 'shorterName' is an alias for the 'veryLongFunctionName' function
Functions as values
All functions can now be used as values, i.e they can be stored in variables, passed as arguments and returned from functions. There are no anonymous functions/lambdas though.
JASS:
function foo takes nothing returns nothing
call jjPrintln("foo()")
endfunction
function bar takes nothing returns nothing
call jjPrintln("bar()")
endfunction
function square takes integer x returns integer
return x*x
endfunction
function fns1 takes nothing returns nothing
local (function takes nothing returns nothing) f1
local code f2 // 'code' is an alias for the above function type
local (function takes integer returns integer) f3
set f1 = function foo
call f1()
set f2 = bar // the function keyword can be omitted
call f2()
set f3 = square
call jjPrintln(I2S(f3(2)))
endfunction
Arrays as values
Arrays are now first class values i.e they can be stored in variables, passed as arguments and returned from functions. There are 2 new keywords that are used to create and destroy array values. The keyword 'new' is used to create a new array value with a specified element type. The 'delete' keyword is used to destroy an array value when it's no longer needed.
JASS:
alias ArrInt = integer array
function sum takes integer n, ArrInt xs returns integer
local integer sum = 0
local integer a = 0
loop
exitwhen a == n
set sum = sum + xs[a]
set a = a + 1
endloop
return sum
endfunction
function main takes nothing returns nothing
local ArrInt xs = new ArrInt
set xs[0] = 1
set xs[1] = 2
set xs[2] = 3
call jjPrintln(I2S(sum(3, xs)))
delete xs
endfunction
For backwards compatibility global arrays are automatically 'new'ed. Local arrays without an initializer are zero initialized like other types.
JASS:
globals
integer array xs1 = new integer array
integer array xs2 // same as above, but the 'new integer array' is implicit here
integer array xs3 = null // can use null if the initialization is done elsewhere
endglobals
There are no multidimensional arrays. One can emulate them with 1 dimensional arrays and indexing math or with jagged arrays.
JASS:
alias ArrInt = integer array
alias ArrArrInt = ArrInt array
function main takes nothing returns nothing
local ArrArrInt xxs = new ArrArrInt
set xxs[0] = new ArrInt
set xxs[1] = new ArrInt
set xxs[2] = new ArrInt
set xxs[0][0] = 10
set xxs[1][100] = 20
set xxs[2][JASS_MAX_ARRAY_SIZE - 1] = 30
call jjPrintln(I2S(xxs[0][0]))
call jjPrintln(I2S(xxs[1][100]))
call jjPrintln(I2S(xxs[2][JASS_MAX_ARRAY_SIZE - 1]))
delete xxs[0]
delete xxs[1]
delete xxs[2]
delete xxs
endfunction
There is no function overloading but there are builtin "generic" functions that can work with an array of any type. The placeholder type 'any' stands for any type.
The functions 'arrlen', 'arrsetlen', 'arrput' and 'arrpop' query/modify an internal array "length" field. All arrays can still be indexed from 0 to JASS_ARRAY_SIZE-1 but this "length" field can be used to keep track of how many elements are logically in the array.
JASS:
// returns the value of the length field
generic function arrlen takes any array xs returns integer
// sets the value of the length field
generic function arrsetlen takes any array xs, integer newLen returns nothing
// the array's element type must match the type of 'x'
// xs[len] = x; len += 1
generic function arrput takes any array xs, any x returns nothing
// if len == 0 then the zero value of the type is returned
// if len > 0 then len -= 1; return xs[len]
generic function arrpop takes any array xs returns any
// copies/moves 'n' elements of array 'src' starting from index 'srcOff' to array 'dst'
// dst[dstOff+0] = src[srcOff+0]
// dst[dstOff+1] = src[srcOff+1]
// ...
// dst[dstOff+n - 1] = src[srcOff+n - 1]
//
// 'dst' and 'src' can overlap
generic function arrmove takes integer n, any array dst, integer dstOff, any array src, integer srcOff returns nothing
// 'start' and 'end' - half open interval [start, end)
// dst[start] = x
// dst[start+1] = x
// ...
// dst[start + (end - start) - 1] = x
generic function arrset takes any x, any array dst, integer start, integer end returns nothing
// returns the index of 'x' in 'xs' in the range [start, end)
// if 'x' is not found then -1 is returned
generic function arrlsearch takes any array xs, integer start, integer end, any x returns integer
// returns the index of the first element in 'xs'/[start, end) for which the predicate 'p' returns true
// if 'p' returns false for all of the elements in the range [start, end) then -1 is returned
generic function arrlsearchfn takes any array xs, integer start, integer end, (function takes any x returns boolean) p returns integer
// sorts the elements of 'xs'/[start, end) using the comparison function 'cmpFn'
// the comparison function takes 2 arguments, 'a' and 'b', and returns:
// an integer < 0 if 'a' < 'b'
// an integer > 0 if 'a' > 'b'
// and 0 if 'a' == 'b'
generic function arrsort takes any array xs, integer start, integer end, (function takes any a, any b returns integer) cmpFn returns nothing
// precondition: the 'xs' array must be sorted
//
// uses binary search to search for 'x' in 'xs'/[start, end) using the comparison function 'cmpFn' to narrow the search
// the comparison function takes 2 arguments, 'a' and 'b', and returns:
// an integer < 0 if 'a' < 'b'
// an integer > 0 if 'a' > 'b'
// and 0 if 'a' == 'b'
//
// returns the index of 'x', if 'x' is found (index >= 0)
//
// returns a biased index (index < 0) if 'x' is not found
// the biased index can be "unbiased": unbiasedIdx = -biasedIdx - 1
// an element can then be inserted at index = unbiasedIdx, such that the
// 'xs' remains sorted
generic function arrbsearch takes any x, any array xs, integer start, integer end, (function takes any a, any b returns integer) cmpFn returns integer
alias ArrCode = code array
function foo takes nothing returns nothing
call jjPrintln("foo()")
endfunction
function bar takes nothing returns nothing
call jjPrintln("bar()")
endfunction
function main takes nothing returns nothing
local ArrCode fns = new ArrCode
local integer n
local integer a
call arrput(fns, foo)
call arrput(fns, bar)
set n = arrlen(fns)
set a = 0
loop
exitwhen a == n
call fns[a]()
set a = a + 1
endloop
loop
exitwhen 0 == arrlen(fns)
call arrpop(fns)()
endloop
endfunction
Maps
Maps/hashtables are similar to arrays but their index/key type is not restricted to integers. For symmetry with arrays they are automatically 'new'ed in globals blocks as well.
JASS:
alias MapStrInt = (map takes string returns integer)
globals
MapStrInt m1 = new MapStrInt
MapStrInt m2 // same as above, but the 'new MapStrInt' is implicit here
MapStrInt m3 = null // can use null if the initialization is done elsewhere
endglobals
function main takes nothing returns nothing
set m1["A"] = 10
call jjPrintln(I2S(m1["A"]))
// if the key is not found the zero value of the type is returned
call jjPrintln(I2S(m1["B"]))
delete m1
delete m2
// passing null values to delete is okay
delete m3
endfunction
There are a few "generic" functions for maps as well. Again, the type 'any' here stands for any type.
JASS:
// stores the keys of the map in the array 'keys' and returns the number of keys
// the key type of the map must match the element type of the array
generic function mapkeys takes (map takes any returns any) m, any array keys returns integer
// removes key 'key' and its associated value from 'm'
generic function mapdel takes (map takes any returns any) m, any key returns nothing
// returns true if key 'key' is in 'm', otherwise returns false
generic function maphave takes (map takes any returns any) m, any key returns boolean
alias MapStrInt = (map takes string returns integer)
function main takes nothing returns nothing
local MapStrInt m = new MapStrInt
local string array keys = new string array
local integer n
local integer a
set m["A"] = 10
set m["B"] = 20
set n = mapkeys(m, keys)
set a = 0
loop
exitwhen a == n
call jjPrintln("m[" + keys[a] + "] = " + I2S(m[keys[a]]))
set a = a + 1
endloop
set a = n - 1
loop
exitwhen a == -1
if maphave(m, keys[a]) then
call jjPrintln("removing key '" + keys[a] + "'")
call mapdel(m, keys[a])
endif
set a = a - 1
endloop
set n = mapkeys(m, keys)
call jjPrintln(I2S(n))
delete m
delete keys
endfunction
User defined types/structs
Structs are an aggregation of fields. They have reference semantics just like arrays and maps, i.e an assignment of 2 variables of a struct type does not copy the values of their corresponding fields, instead the two variables refer to the same struct value. Structs can extend other structs, this is the same as copying the fields of the base struct to the fields of the extending struct. If a struct does not extend another struct then it implicitly extends the 'Any' struct, which has no fields. Struct values have a builtin 'typename' field which returns the name of their struct type. This can be used to test the runtime type of a struct value and to safely do a downcast.
JASS:
struct Point2D
real x
real y
endstruct
function newPoint2D takes real x, real y returns Point2D
local Point2D p = new Point2D
set p.x = x
set p.y = y
return p
endfunction
function printPoint2D takes Point2D p returns nothing
call jjPrintln("Point2D { x: " + R2S(p.x) + ", y: " + R2S(p.y) + " }")
endfunction
struct Point3D extends Point2D
real z
endstruct
function newPoint3D takes real x, real y, real z returns Point2D
local Point3D p = new Point3D
set p.x = x
set p.y = y
set p.z = z
return p
endfunction
function printPoint3D takes Point3D p returns nothing
call jjPrintln("Point3D { x: " + R2S(p.x) + ", y: " + R2S(p.y) + ", z: " + R2S(p.z) + " }")
endfunction
function main takes nothing returns nothing
local Point2D array pts
local Point2D p
local string T
local integer n
local integer a
set pts = new Point2D array
call arrput(pts, newPoint2D(1, 2))
call arrput(pts, newPoint3D(3, 4, 5))
call arrput(pts, newPoint2D(6, 7))
set n = arrlen(pts)
set a = 0
loop
exitwhen a == n
set p = pts[a]
set T = p.typename
if Point2D == T then
call printPoint2D(p)
elseif Point3D == T then
// downcast to Point3D
call printPoint3D(p.(Point3D))
else
call assert(false)
endif
set a = a + 1
endloop
endfunction
Another way to do dynamic dispatch, instead of the case analysis above. is to use fields of function types.
JASS:
struct Shape
(function takes Shape s returns real) getArea
(function takes Shape s returns string) toString
real x
real y
endstruct
function shapeGetArea takes Shape s returns real
return s.getArea(s)
endfunction
function shapeToString takes Shape s returns string
return s.toString(s)
endfunction
struct Rect extends Shape
real w
real h
endstruct
function rectGetArea takes Shape s returns real
local Rect r = s.(Rect) // downcast
return r.w * r.h
endfunction
function rectToString takes Shape s returns string
local Rect r = s.(Rect)
return "Rect { x: " + R2S(r.x) + ", y: " + R2S(r.y) + ", w: " + R2S(r.w) + ", h: " + R2S(r.h) + " }"
endfunction
function newRect takes real x, real y, real w, real h returns Rect
local Rect r = new Rect
set r.getArea = rectGetArea
set r.toString = rectToString
set r.x = x
set r.y = y
set r.w = w
set r.h = h
return r
endfunction
struct Circle extends Shape
real r
endstruct
function circleGetArea takes Shape s returns real
local Circle c = s.(Circle)
return 3.14159 * c.r*c.r
endfunction
function circleToString takes Shape s returns string
local Circle c = s.(Circle)
return "Circle { x: " + R2S(c.x) + ", y: " + R2S(c.y) + ", r: " + R2S(c.r) + " }"
endfunction
function newCircle takes real x, real y, real r returns Circle
local Circle c = new Circle
set c.getArea = circleGetArea
set c.toString = circleToString
set c.x = x
set c.y = y
set c.r = r
return c
endfunction
function main takes nothing returns nothing
local Shape array shapes
local Shape s
local integer n
local integer a
set shapes = new Shape array
call arrput(shapes, newRect(1, 2, 3, 4))
call arrput(shapes, newCircle(5, 6, 7))
call arrput(shapes, newRect(8, 9, 10, 11))
set n = arrlen(shapes)
set a = 0
loop
exitwhen a == n
set s = shapes[a]
call jjPrintln(shapeToString(s) + ", area: " + R2S(shapeGetArea(s)))
set a = a + 1
endloop
endfunction
1.0.13
- bug fix: assignments to arrays/maps indexed by function call expressions should no longer crash
1.0.12
- bug fix: 'jjGetFileInfo' now does call 'CloseHandle'
1.0.11
- bug fix: comparison operators with real arguments in function argument context should now work
- bug fix: S2I can now handle negative integers ("-1234")
1.0.10
- added the natives 'jjStringSearch', 'jjErrPrint', 'jjErrPrintln', 'jjGetCwd', 'jjSetCwd', 'jjCreateDir', 'jjRemoveDir', 'jjRemoveFile', 'jjDirGetFilenames', 'jjGetFileInfo', 'jjTimepointNowHi', 'jjTimepointNowLo', 'jjCompareTimepoints and 'jjBreakTimepoint'
- natives that take string arguments to files/directories assume UTF-8 encoding
- the strings returned by 'jjGetScriptArg' use UTF-8 encoding now
1.0.9
- added the functions 'arrmove', 'arrset', 'arrlsearch', 'arrlsearchfn', 'arrsort' and 'arrbsearch'
- common.j now documents the generic functions
1.0.8
- removed the trigger and hashtable related natives because they can now be implemented in userland
- added continue keyword for jumping at the start of a loop
- all functions can now be used as values
- arrays are now first class values
- added maps
- added user defined types/structs
1.0.7
- the alias keyword can now be used to declare aliases for types, natives and functions
1.0.6
- new function: assert (see 'jj help run')
- the debug keyword can now be used to disable statements (see 'jj help run')
1.0.5
- new natives: jjTypecastS2I and jjTypecastI2S
1.0.4
- bug fix: arrays should now really be zero initialized
1.0.3
- crash fix: recovering from parse errors should no longer crash
1.0.2
- bug fix: assignment to constant crash
- new native: jjBitcastI2R
1.0.1
- initial release
Attachments
Last edited: