diff --git a/binaries/data/mods/public/gui/common/functions_civinfo.js b/binaries/data/mods/public/gui/common/functions_civinfo.js index 8d624e661d..3900930e47 100644 --- a/binaries/data/mods/public/gui/common/functions_civinfo.js +++ b/binaries/data/mods/public/gui/common/functions_civinfo.js @@ -3,22 +3,21 @@ NOTES : */ -// ==================================================================== - - function loadCivData() -{ // Load all JSON files containing civ data +{ + // Load all JSON files containing civ data var civData = {}; var civFiles = Engine.BuildDirEntList("civs/", "*.json", false); for each (var filename in civFiles) - { // Parse data if valid file - var data = parseJSONData(filename); + { + // Parse data if valid file + var data = Engine.ReadJSONFile(filename); + if (!data) + continue; translateObjectKeys(data, ["Name", "Description", "History", "Special"]); civData[data.Code] = data; } return civData; } - -// ==================================================================== diff --git a/binaries/data/mods/public/gui/common/functions_utility.js b/binaries/data/mods/public/gui/common/functions_utility.js index ff6c4fb65e..e9fd63f7d3 100644 --- a/binaries/data/mods/public/gui/common/functions_utility.js +++ b/binaries/data/mods/public/gui/common/functions_utility.js @@ -53,37 +53,6 @@ function getJSONFileList(pathname) return files; } - -// ==================================================================== - -// Parse JSON data -function parseJSONData(pathname) -{ - var data = {}; - - var rawData = Engine.ReadFile(pathname); - if (!rawData) - { - error(sprintf("Failed to read file: %(path)s", { path: pathname })); - } - else - { - try - { // Catch nasty errors from JSON parsing - // TODO: Need more info from the parser on why it failed: line number, position, etc! - data = JSON.parse(rawData); - if (!data) - error(sprintf("Failed to parse JSON data in: %(path)s (check for valid JSON data)", { path: pathname })); - } - catch(err) - { - error(sprintf("%(error)s: parsing JSON data in %(path)s", { error: err.toString(), path: pathname })); - } - } - - return data; -} - // ==================================================================== // A sorting function for arrays of objects with 'name' properties, ignoring case @@ -132,44 +101,17 @@ function toTitleCase (string) // ==================================================================== -// Parse and return JSON data from file in simulation/data/* -// returns valid object or undefined on error -function parseJSONFromDataFile(filename) -{ - var path = "simulation/data/"+filename; - var rawData = Engine.ReadFile(path); - if (!rawData) - error(sprintf("Failed to read file: %(path)s", { path: path })); - - try - { - // Catch nasty errors from JSON parsing - // TODO: Need more info from the parser on why it failed: line number, position, etc! - var data = JSON.parse(rawData); - return data; - } - catch(err) - { - error(sprintf("%(error)s: parsing JSON data in %(path)s", { error: err.toString(), path: path })); - } - - return undefined; -} - -// ==================================================================== - // Load default player data, for when it's not otherwise specified function initPlayerDefaults() { - var defaults = []; - - var data = parseJSONFromDataFile("player_defaults.json"); + var data = Engine.ReadJSONFile("simulation/data/player_defaults.json"); if (!data || !data.PlayerData) + { error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)"); - else - defaults = data.PlayerData; + return []; + } - return defaults; + return data.PlayerData; } // ==================================================================== @@ -184,21 +126,22 @@ function initMapSizes() "default": 0 }; - var data = parseJSONFromDataFile("map_sizes.json"); + var data = Engine.ReadJSONFile("simulation/data/map_sizes.json"); if (!data || !data.Sizes) - error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)"); - else { - translateObjectKeys(data, ["Name", "LongName"]); - for (var i = 0; i < data.Sizes.length; ++i) - { - sizes.shortNames.push(data.Sizes[i].Name); - sizes.names.push(data.Sizes[i].LongName); - sizes.tiles.push(data.Sizes[i].Tiles); + error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)"); + return sizes; + } - if (data.Sizes[i].Default) - sizes["default"] = i; - } + translateObjectKeys(data, ["Name", "LongName"]); + for (var i = 0; i < data.Sizes.length; ++i) + { + sizes.shortNames.push(data.Sizes[i].Name); + sizes.names.push(data.Sizes[i].LongName); + sizes.tiles.push(data.Sizes[i].Tiles); + + if (data.Sizes[i].Default) + sizes["default"] = i; } return sizes; @@ -215,20 +158,21 @@ function initGameSpeeds() "default": 0 }; - var data = parseJSONFromDataFile("game_speeds.json"); + var data = Engine.ReadJSONFile("simulation/data/game_speeds.json"); if (!data || !data.Speeds) - error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)"); - else { - translateObjectKeys(data, ["Name"]); - for (var i = 0; i < data.Speeds.length; ++i) - { - gameSpeeds.names.push(data.Speeds[i].Name); - gameSpeeds.speeds.push(data.Speeds[i].Speed); + error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)"); + return gameSpeeds; + } - if (data.Speeds[i].Default) - gameSpeeds["default"] = i; - } + translateObjectKeys(data, ["Name"]); + for (var i = 0; i < data.Speeds.length; ++i) + { + gameSpeeds.names.push(data.Speeds[i].Name); + gameSpeeds.speeds.push(data.Speeds[i].Speed); + + if (data.Speeds[i].Default) + gameSpeeds["default"] = i; } return gameSpeeds; diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js index 9a50c45ea3..0c57315154 100644 --- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -676,7 +676,7 @@ function loadMapData(name) // To be defined later. g_MapData[name] = { settings: { "Name": "", "Description": "" } }; else - g_MapData[name] = parseJSONData(name+".json"); + g_MapData[name] = Engine.ReadJSONFile(name+".json"); break; default: diff --git a/binaries/data/mods/public/gui/lobby/lobby.js b/binaries/data/mods/public/gui/lobby/lobby.js index fd8bd66627..c006479fac 100644 --- a/binaries/data/mods/public/gui/lobby/lobby.js +++ b/binaries/data/mods/public/gui/lobby/lobby.js @@ -498,7 +498,7 @@ function updateGameSelection() if (g_GameList[g].mapType == "random" && g_GameList[g].mapName == "random") mapData = {"settings": {"Description": translate("A randomly selected map.")}}; else if (g_GameList[g].mapType == "random" && Engine.FileExists(g_GameList[g].mapName + ".json")) - mapData = parseJSONData(g_GameList[g].mapName + ".json"); + mapData = Engine.ReadJSONFile(g_GameList[g].mapName + ".json"); else if (Engine.FileExists(g_GameList[g].mapName + ".xml")) mapData = Engine.LoadMapSettings(g_GameList[g].mapName + ".xml"); else diff --git a/binaries/data/mods/public/gui/session/messages.js b/binaries/data/mods/public/gui/session/messages.js index c21e4e5b8a..389d63d5ac 100644 --- a/binaries/data/mods/public/gui/session/messages.js +++ b/binaries/data/mods/public/gui/session/messages.js @@ -17,7 +17,9 @@ function getCheatsData() var cheatFileList = getJSONFileList("simulation/data/cheats/"); for each (var fileName in cheatFileList) { - var currentCheat = parseJSONData("simulation/data/cheats/"+fileName+".json"); + var currentCheat = Engine.ReadJSONFile("simulation/data/cheats/"+fileName+".json"); + if (!currentCheat) + continue; if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1) warn("Cheat name '" + currentCheat.Name + "' is already present"); else diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js index 267e29812f..2309073f38 100644 --- a/binaries/data/mods/public/simulation/components/TechnologyManager.js +++ b/binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -283,7 +283,7 @@ TechnologyManager.prototype.ResearchTechnology = function(tech) if (!template) { - error("Tried to research invalid techonology: " + uneval(tech)); + error("Tried to research invalid technology: " + uneval(tech)); return; } diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index c0e7ed60c1..ffeec7e2ea 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -109,8 +109,7 @@ bool CMapGeneratorWorker::Run() // Parse settings JS::RootedValue settingsVal(cx); - m_ScriptInterface->ParseJSON(m_Settings, &settingsVal); - if (settingsVal.isUndefined()) + if (!m_ScriptInterface->ParseJSON(m_Settings, &settingsVal) && settingsVal.isUndefined()) { LOGERROR(L"CMapGeneratorWorker::Run: Failed to parse settings"); return false; diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 64b230989d..a3fc85b88a 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -22,11 +22,11 @@ #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/MapReader.h" -#include "gui/GUIManager.h" +#include "graphics/scripting/JSInterface_GameView.h" #include "gui/GUI.h" +#include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" -#include "graphics/scripting/JSInterface_GameView.h" #include "i18n/L10n.h" #include "i18n/scripting/JSInterface_L10n.h" #include "lib/svn_revision.h" @@ -38,38 +38,37 @@ #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetTurnManager.h" -#include "ps/CLogger.h" #include "ps/CConsole.h" +#include "ps/CLogger.h" #include "ps/Errors.h" -#include "ps/Game.h" -#include "ps/Globals.h" // g_frequencyFilter #include "ps/GUID.h" -#include "ps/World.h" +#include "ps/Game.h" +#include "ps/GameSetup/Atlas.h" +#include "ps/GameSetup/Config.h" +#include "ps/Globals.h" // g_frequencyFilter #include "ps/Hotkey.h" #include "ps/Overlay.h" #include "ps/ProfileViewer.h" #include "ps/Pyrogenesis.h" #include "ps/SavedGame.h" +#include "ps/UserReport.h" +#include "ps/World.h" #include "ps/scripting/JSInterface_ConfigDB.h" #include "ps/scripting/JSInterface_Console.h" #include "ps/scripting/JSInterface_Mod.h" #include "ps/scripting/JSInterface_VFS.h" -#include "ps/UserReport.h" -#include "ps/GameSetup/Atlas.h" -#include "ps/GameSetup/Config.h" #include "renderer/scripting/JSInterface_Renderer.h" -#include "tools/atlas/GameInterface/GameLoop.h" - #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpAIManager.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpGuiInterface.h" #include "simulation2/components/ICmpRangeManager.h" -#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpSelectable.h" +#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/helpers/Selection.h" #include "soundmanager/SoundManager.h" #include "soundmanager/scripting/JSInterface_Sound.h" +#include "tools/atlas/GameInterface/GameLoop.h" /* * This file defines a set of functions that are available to GUI scripts, to allow @@ -834,6 +833,15 @@ std::wstring GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), i return wstring_from_utf8(buf); } +CScriptVal ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filePath) +{ + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue out(cx); + pCxPrivate->pScriptInterface->ReadJSONFile(filePath, &out); + return out.get(); +} + //----------------------------------------------------------------------------- // Timer //----------------------------------------------------------------------------- @@ -992,6 +1000,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) scriptInterface.RegisterFunction("SetPaused"); scriptInterface.RegisterFunction("GetFPS"); scriptInterface.RegisterFunction("GetBuildTimestamp"); + scriptInterface.RegisterFunction("ReadJSONFile"); // User report functions scriptInterface.RegisterFunction("IsUserReportEnabled"); diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 098aaeee74..23c1229d46 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -167,7 +167,7 @@ void CReplayPlayer::Replay(bool serializationtest) std::string line; std::getline(*m_Stream, line); JS::RootedValue attribs(cx); - game.GetSimulation2()->GetScriptInterface().ParseJSON(line, &attribs); + ENSURE(game.GetSimulation2()->GetScriptInterface().ParseJSON(line, &attribs)); game.StartGame(CScriptValRooted(cx, attribs), ""); diff --git a/source/ps/scripting/JSInterface_Mod.cpp b/source/ps/scripting/JSInterface_Mod.cpp index 88ca91d17e..ab1734198a 100644 --- a/source/ps/scripting/JSInterface_Mod.cpp +++ b/source/ps/scripting/JSInterface_Mod.cpp @@ -76,7 +76,8 @@ CScriptVal JSI_Mod::GetAvailableMods(ScriptInterface::CxPrivate* pCxPrivate) continue; JS::RootedValue json(cx); - scriptInterface->ParseJSON(modinfo.GetAsString(), &json); + if (!scriptInterface->ParseJSON(modinfo.GetAsString(), &json)) + continue; // Valid mod, add it to our structure JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json.address()); @@ -101,7 +102,8 @@ CScriptVal JSI_Mod::GetAvailableMods(ScriptInterface::CxPrivate* pCxPrivate) continue; JS::RootedValue json(cx); - scriptInterface->ParseJSON(modinfo.GetAsString(), &json); + if (!scriptInterface->ParseJSON(modinfo.GetAsString(), &json)) + continue; // Valid mod, add it to our structure JS_SetProperty(cx, obj, utf8_from_wstring(iter->string()).c_str(), json.address()); diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index 0377d9636e..d41b4f28a3 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -1298,16 +1298,36 @@ bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) return ok; } -void ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) +bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); std::wstring attrsW = wstring_from_utf8(string_utf8); utf16string string(attrsW.begin(), attrsW.end()); - if (!JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), out)) - { - LOGERROR(L"JS_ParseJSON failed!"); - return; - } + if (JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), out)) + return true; + + LOGERROR(L"JS_ParseJSON failed!"); + if (!JS_IsExceptionPending(m->m_cx)) + return false; + + JS::RootedValue exc(m->m_cx); + if (!JS_GetPendingException(m->m_cx, exc.address())) + return false; + + JS_ClearPendingException(m->m_cx); + // We expect an object of type SyntaxError + if (!exc.isObject()) + return false; + + JS::RootedValue rval(m->m_cx); + JS::RootedObject excObj(m->m_cx, &exc.toObject()); + if (!JS_CallFunctionName(m->m_cx, excObj, "toString", 0, NULL, rval.address())) + return false; + + std::wstring error; + ScriptInterface::FromJSVal(m->m_cx, rval, error); + LOGERROR(L"%ls", error.c_str()); + return false; } void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) @@ -1330,7 +1350,8 @@ void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue o std::string content(file.DecodeUTF8()); // assume it's UTF-8 - ParseJSON(content, out); + if (!ParseJSON(content, out)) + LOGERROR(L"Failed to parse '%ls'", path.string().c_str()); } struct Stringifier diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index dc24a04dba..b96ba316ff 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,8 +19,8 @@ #define INCLUDED_SCRIPTINTERFACE #include -#include #include +#include #include "ScriptTypes.h" #include "ScriptVal.h" @@ -273,9 +273,11 @@ public: std::wstring ToString(JS::MutableHandleValue obj, bool pretty = false); /** - * Parse a UTF-8-encoded JSON string. Returns the unmodified value on error and prints an error message. + * Parse a UTF-8-encoded JSON string. Returns the unmodified value on error + * and prints an error message. + * @return true on success; false otherwise */ - void ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out); + bool ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out); /** * Read a JSON file. Returns the unmodified value on error and prints an error message. diff --git a/source/scriptinterface/tests/test_ScriptInterface.h b/source/scriptinterface/tests/test_ScriptInterface.h index c0eb420517..3089ea6af2 100644 --- a/source/scriptinterface/tests/test_ScriptInterface.h +++ b/source/scriptinterface/tests/test_ScriptInterface.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2014 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -251,7 +251,7 @@ public: std::string stringified = script.StringifyJSON(&val); TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\": 1,\n \"z\": [\n 2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\": true\n}"); - script.ParseJSON(stringified, &val); + TS_ASSERT(script.ParseJSON(stringified, &val)); TS_ASSERT_WSTR_EQUALS(script.ToString(&val), L"({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})"); } };