- Joined
- Mar 14, 2019
- Messages
- 8
github:GitHub - LuciferFor/JassForge-Project
Hi everyone,
I’d like to share JassForge, a compiler/project template for Warcraft III map scripting.
Repository:
github.com
JassForge is built to make JASS map development more modern and more comfortable.
It packages the compiler, map script structure, include/import setup, build scripts, and example code into one workflow, so you can compile war3map.j and write it back into Map.w3x more easily.
Main features:
What’s included:
Project goal:
The goal is to provide a smoother and more expressive workflow for Warcraft III scripting, especially for people who want something more modern than traditional JASS/vJASS-style development.
If you are interested, I’d really appreciate feedback on:
Thanks for checking it out.
Search the VSCode Extension Marketplace for JassForge.
It is compatible with JASS / vJASS / Zinc.
Hi everyone,
I’d like to share JassForge, a compiler/project template for Warcraft III map scripting.
Repository:
GitHub - LuciferFor/JassForge-Project
Contribute to LuciferFor/JassForge-Project development by creating an account on GitHub.
JassForge is built to make JASS map development more modern and more comfortable.
It packages the compiler, map script structure, include/import setup, build scripts, and example code into one workflow, so you can compile war3map.j and write it back into Map.w3x more easily.
Main features:
- Zinc-style syntax ({}, ;, ->)
- Strongly typed enums
- Default parameters and reference parameters
- Static local variables
- Function pointer types
- Lambdas and closures
- Generic structs
- new/delete/let lifecycle syntax
- Struct reflection
- Standard containers like vector, stack, queue, and map
- Hashtable double-index syntax sugar
- Array initialization, raw strings, and interpreter grouping
What’s included:
- JassForge compiler
- Example Warcraft III map project structure
- Include/import files
- PowerShell build scripts
- Demo code showing language features
Project goal:
The goal is to provide a smoother and more expressive workflow for Warcraft III scripting, especially for people who want something more modern than traditional JASS/vJASS-style development.
If you are interested, I’d really appreciate feedback on:
- syntax design
- workflow/build process
- usability for real map projects
- compatibility expectations in the Warcraft III modding ecosystem
Thanks for checking it out.
JASS:
//! zinc
library JassForgeFeatureDemo {
// -----------------------------------------------------------------
// Demo 1: Strongly typed enum
// When using an enum, you must write qualified names like DemoMode.Boss.
// You cannot write Boss directly.
// -----------------------------------------------------------------
enum DemoMode: integer {
Idle = 0,
Farming = 1,
Boss = 2
}
// -----------------------------------------------------------------
// Demo 2: Function pointer types
// JassForge can declare function pointers with signatures,
// which are more strongly typed than traditional code.
// -----------------------------------------------------------------
type ScoreFormatter extends function(string, integer) -> string;
type IntTransform extends function(integer) -> integer;
// -----------------------------------------------------------------
// Demo 3: Global variables, array initialization, hashtable
// Zinc global arrays can be initialized directly with initializer lists.
// -----------------------------------------------------------------
integer DemoGlobalScore = 0;
string DemoLabels[] = { "Default parameters", "Reference parameters", "Struct reflection" };
hashtable DemoTable = InitHashtable();
// -----------------------------------------------------------------
// Demo 4: Generic struct
// It will be monomorphized at compile time.
// For example, Box<integer> will generate a concrete instance type.
// -----------------------------------------------------------------
struct Box<T> {
T value;
public method assign(T v) {
this.value = v;
}
public method get() -> T {
return this.value;
}
}
// -----------------------------------------------------------------
// Demo 5: Constructor, destructor, default fields, readonly, reflection
// new/delete/let will all trigger lifecycle logic.
// -----------------------------------------------------------------
struct DemoStats {
integer hp = 100;
real speed = 1.00;
string name = "Unnamed";
readonly integer rawId = 'Hpal';
DemoMode mode = DemoMode.Idle;
DemoStats(string heroName, integer startHp) {
this.name = heroName;
this.hp = startHp;
}
~DemoStats() {
BJDebugMsg("[delete/let] Destructor: " + this.name);
}
public method describe(string prefix = "Character") -> string {
return prefix + " " + this.name + " hp=" + I2S(this.hp);
}
public static method fieldInfo() -> string {
return "Field count=" + I2S(DemoStats.size()) + ", type of field #2=" + DemoStats.type(1);
}
}
// -----------------------------------------------------------------
// Demo 6: interpreter block
// When generating war3map.j, interpreter-start / interpreter-end
// comments will be output.
// -----------------------------------------------------------------
interpreter "ScaffoldDemo/InterpreterBlock" {
function InterpreterHello() {
BJDebugMsg("[interpreter] Called function through interpreter block path");
}
}
function AddScore(integer base, integer bonus = 10) -> integer {
return base + bonus;
}
function BumpScore(integer &score, integer delta) {
score += delta;
}
function FormatScore(string label, integer score) -> string {
return label + "=" + I2S(score);
}
function CountOnCall() -> integer {
static integer count = 0;
count += 1;
return count;
}
function BoolText(boolean value) -> string {
return value ? "true" : "false";
}
function DemoEnum(DemoMode mode) {
if (mode == DemoMode.Boss) {
BJDebugMsg("[enum] Current mode: Boss");
} else {
BJDebugMsg("[enum] Current mode is not Boss");
}
}
function DemoDefaultAndRef() {
integer score = AddScore(5);
BumpScore(score, 3);
DemoGlobalScore = score;
BJDebugMsg("[Default parameter / & reference parameter] " + I2S(score));
}
function DemoStaticLocal() {
BJDebugMsg("[static local variable] 1st time=" + I2S(CountOnCall()));
BJDebugMsg("[static local variable] 2nd time=" + I2S(CountOnCall()));
}
function DemoFunctionPointerAndLambda() {
ScoreFormatter formatter = FormatScore;
IntTransform transform = function(integer value) -> integer {
return value + 7;
};
BJDebugMsg("[function pointer / lambda] " + formatter.evaluate("Score", transform.evaluate(5)));
}
function DemoSysFunction() {
sys::function<FormatScore> exactFormatter = FormatScore;
BJDebugMsg("[sys::function<function name>] " + exactFormatter("Exact binding", 77));
}
function DemoGenericStruct() {
Box<integer> intBox = Box<integer>.create();
Box<string> stringBox = Box<string>.create();
intBox.assign(42);
stringBox.assign("generic string");
BJDebugMsg("[generic struct] int=" + I2S(intBox.get()) + " string=" + stringBox.get());
intBox.destroy();
stringBox.destroy();
}
function DemoNewDeleteAndReflection() {
DemoStats hero = new DemoStats("Test Hero", 250);
integer hp = hero.at("hp");
hero.at("hp") = hp + 50;
BJDebugMsg("[new/delete] " + hero.describe());
BJDebugMsg("[struct reflection] " + DemoStats.fieldInfo());
BJDebugMsg("[struct reflection] name field=" + hero.at("name", string));
delete hero;
}
function DemoLetAutoCleanup() {
let DemoStats temp("temporary let object", 80);
BJDebugMsg("[let] " + temp.describe("Auto-cleanup object"));
}
function DemoVectorAndRangeFor() {
sys::vector<integer> values = sys::vector<integer>.create();
values.push_back(3);
values.push_back(1);
values.push_back(2);
sys::sort(values);
for (integer value : values) {
BJDebugMsg("[sys::vector / range-for] value=" + I2S(value));
}
values.destroy();
}
function DemoStack() {
sys::stack<string> names = sys::stack<string>.create();
names.push("First layer");
names.push("Second layer");
names.push("Top of stack");
BJDebugMsg("[sys::stack] size=" + I2S(names.size()) + " top=" + names.top());
names.pop();
BJDebugMsg("[sys::stack] top after pop=" + names.top());
names.clear();
BJDebugMsg("[sys::stack] empty after clear=" + BoolText(names.empty()));
names.destroy();
}
function DemoQueue() {
sys::queue<integer> waves = sys::queue<integer>.create();
waves.push(10);
waves.push(20);
waves.push(30);
BJDebugMsg("[sys::queue] size=" + I2S(waves.size()) + " front=" + I2S(waves.front()) + " back=" + I2S(waves.back()));
waves.pop();
BJDebugMsg("[sys::queue] front after pop=" + I2S(waves.front()));
waves.clear();
BJDebugMsg("[sys::queue] empty after clear=" + BoolText(waves.empty()));
waves.destroy();
}
function DemoMap() {
sys::map<string, integer> scores = sys::map<string, integer>.create();
scores.insert("Warrior", 100);
scores["Mage"] = 88;
scores["Hunter"] = 92;
if (scores.contains("Warrior")) {
BJDebugMsg("[sys::map] Warrior=" + I2S(scores["Warrior"]) + " count=" + I2S(scores.count("Warrior")));
}
for (string key : scores) {
BJDebugMsg("[sys::map / range-for] " + key + "=" + I2S(scores[key]));
}
BJDebugMsg("[sys::map] key #0=" + scores.at(0));
scores.erase("Mage");
BJDebugMsg("[sys::map] size after removing Mage=" + I2S(scores.size()));
scores.clear();
BJDebugMsg("[sys::map] empty after clear=" + BoolText(scores.empty()));
scores.destroy();
}
function DemoClosureCallbacks() {
timer delay = CreateTimer();
group units = CreateGroup();
string timerText = "TimerStart can directly capture local variables";
string groupText = "ForGroup can also directly capture outer variables";
integer reward = 25;
// No need to manually save values into a hashtable,
// and no need to pass parameters through C2I/I2C.
TimerStart(delay, 0.00, false, function() {
BJDebugMsg("[Timer closure] " + timerText + " reward=" + I2S(reward));
DestroyTimer(delay);
});
GroupEnumUnitsOfPlayer(units, Player(0), null);
// The callback can still read groupText/reward,
// and can directly use GetEnumUnit().
ForGroup(units, function() {
BJDebugMsg("[ForGroup closure] " + groupText + " unitId=" + I2S(GetHandleId(GetEnumUnit())) + " reward=" + I2S(reward));
});
DestroyGroup(units);
delay = null;
units = null;
}
function DemoHashtableSugar() {
DemoTable["player-1"]["score"] = DemoGlobalScore;
BJDebugMsg("[hashtable double-index syntax] score=" + I2S((integer)DemoTable["player-1"]["score"]));
}
function DemoArrayAndRawString() {
string tips[] = {
DemoLabels[0],
DemoLabels[1],
R"(Raw string: path C:\MapDemo\war3map.j does not need double backslashes)"
};
BJDebugMsg("[array initialization] " + tips[0] + " / " + tips[1]);
BJDebugMsg("[raw string] " + tips[2]);
}
function DemoInterpreterCall() {
interpreter.("ScaffoldDemo/InterpreterBlock").InterpreterHello();
}
// Companion plugin: search for JassForge in the VSCode Marketplace.
function onInit() {
BJDebugMsg("===== JassForge Compiler Feature Demo =====");
DemoEnum(DemoMode.Boss);
DemoDefaultAndRef();
DemoStaticLocal();
DemoFunctionPointerAndLambda();
DemoSysFunction();
DemoGenericStruct();
DemoNewDeleteAndReflection();
DemoLetAutoCleanup();
DemoVectorAndRangeFor();
DemoStack();
DemoQueue();
DemoMap();
DemoClosureCallbacks();
DemoHashtableSugar();
DemoArrayAndRawString();
DemoInterpreterCall();
}
}
//! endzinc
Search the VSCode Extension Marketplace for JassForge.
It is compatible with JASS / vJASS / Zinc.