From c1e5cc30744c259917d21c74c35cd83d23f8506b Mon Sep 17 00:00:00 2001 From: James Turner <james@flightgear.org> Date: Wed, 5 Jan 2022 15:44:50 +0000 Subject: [PATCH] Improve creating VIA segments Improve argument parsing for createViaTo, createViaFromTo to handle Airway ghosts as well as strings. As part of this, allow specification of the airway level explicitly when looking up an airway. SF-ID: https://sourceforge.net/p/flightgear/codetickets/2686/ --- src/Scripting/NasalFlightPlan.cxx | 138 ++++++++++++++---- test_suite/FGTestApi/testGlobals.cxx | 17 ++- .../unit_tests/Navaids/test_fpNasal.cxx | 33 ++++- 3 files changed, 156 insertions(+), 32 deletions(-) diff --git a/src/Scripting/NasalFlightPlan.cxx b/src/Scripting/NasalFlightPlan.cxx index d7436f213..76a2397d1 100644 --- a/src/Scripting/NasalFlightPlan.cxx +++ b/src/Scripting/NasalFlightPlan.cxx @@ -134,6 +134,33 @@ static naRef wayptFlagToNasal(naContext c, unsigned int flags) return naNil(); } +static std::optional<Airway::Level> airwayLevelFromNasal(naRef na) +{ + if (naIsString(na)) { + const char* s = naStr_data(na); + if (!strcmp(s, "both")) return {Airway::Both}; + if (!strcmp(s, "high")) return {Airway::HighLevel}; + if (!strcmp(s, "low")) return {Airway::LowLevel}; + return {}; + } + + if (!naIsNum(na)) { + return {}; + } + + const int num = static_cast<int>(na.num); + switch (num) { + case Airway::HighLevel: return {Airway::HighLevel}; + case Airway::LowLevel: return {Airway::LowLevel}; + case Airway::Both: return {Airway::Both}; + default: + break; + // fall through + } + + return {}; // fail +} + Waypt* wayptGhost(naRef r) { if (naGhost_type(r) == &WayptGhostType) @@ -1028,6 +1055,8 @@ static const char* airwayGhostGetMember(naContext c, void* g, naRef field, naRef } else if (!strcmp(fieldName, "id")) *out = stringToNasal(c, awy->ident()); else if (!strcmp(fieldName, "level")) { + // James was dumb, returning a string here since we didn't have the + // constant values exposed. const auto level = awy->level(); switch (level) { case Airway::HighLevel: *out = stringToNasal(c, "high"); break; @@ -1035,6 +1064,10 @@ static const char* airwayGhostGetMember(naContext c, void* g, naRef field, naRef case Airway::Both: *out = stringToNasal(c, "both"); break; default: *out = naNil(); } + } else if (!strcmp(fieldName, "level_code")) { + // so expose the numerical values here + const auto level = awy->level(); + *out = naNum(static_cast<int>(level)); } else { return 0; } @@ -1341,9 +1374,18 @@ static naRef f_findAirway(naContext c, naRef me, int argc, naRef* args) FGPositionedRef pos; Airway::Level level = Airway::Both; if (argc >= 2) { - pos = positionedFromArg(args[1]); - if (naIsString(args[1])) { - // level spec, + int posArgIndex = 1; + + // try next arg as a level specifier first: this means you can't use + // a string navaid ident which is 'high', 'low' or 'both'. + auto maybeLevel = airwayLevelFromNasal(args[1]); + if (maybeLevel.has_value()) { + level = maybeLevel.value_or(Airway::Both); + ++posArgIndex; // worked, so increment index + } + + if (argc > posArgIndex) { + pos = positionedFromArg(args[posArgIndex]); } } @@ -1419,28 +1461,49 @@ static naRef f_createWPFrom(naContext c, naRef me, int argc, naRef* args) static naRef f_createViaTo(naContext c, naRef me, int argc, naRef* args) { - if (argc != 2) { - naRuntimeError(c, "createViaTo: needs exactly two arguments"); + if ((argc < 2) || (argc > 3)) { + naRuntimeError(c, "createViaTo: needs two or three arguments"); + } + + AirwayRef airway = airwayGhost(args[0]); + naRef toArg = args[1]; + if (!airway && naIsString(args[0])) { + std::string airwayName = naStr_data(args[0]); + auto level = Airway::Both; + if (argc == 3) { + // this means second arg is high / low select + auto l = airwayLevelFromNasal(args[1]); + toArg = args[2]; + if (!l) { + naRuntimeError(c, "createViaTo: level argument is not accepted"); + } + + level = l.value_or(Airway::Both); + } + + + airway = Airway::findByIdent(airwayName, level); + if (!airway) { + naRuntimeError(c, "createViaTo: couldn't find airway with provided name: %s", + naStr_data(args[0])); + } } - std::string airwayName = naStr_data(args[0]); - AirwayRef airway = Airway::findByIdent(airwayName, Airway::Both); if (!airway) { - naRuntimeError(c, "createViaTo: couldn't find airway with provided name: %s", - naStr_data(args[0])); + naRuntimeError(c, "createViaTo: invalid airway"); } FGPositionedRef nav; - if (naIsString(args[1])) { - WayptRef enroute = airway->findEnroute(naStr_data(args[1])); + if (naIsString(toArg)) { + WayptRef enroute = airway->findEnroute(naStr_data(toArg)); if (!enroute) { naRuntimeError(c, "unknown waypoint on airway %s: %s", - naStr_data(args[0]), naStr_data(args[1])); + naStr_data(args[0]), naStr_data(toArg)); } nav = enroute->source(); } else { - nav = positionedGhost(args[1]); + nav = positionedGhost(toArg); if (!nav) { naRuntimeError(c, "createViaTo: arg[1] is not a navaid"); } @@ -1456,8 +1519,8 @@ static naRef f_createViaTo(naContext c, naRef me, int argc, naRef* args) static naRef f_createViaFromTo(naContext c, naRef me, int argc, naRef* args) { - if (argc != 3) { - naRuntimeError(c, "createViaFromTo: needs exactly three arguments"); + if ((argc < 3) || (argc > 4)) { + naRuntimeError(c, "createViaFromTo: needs three or four arguments"); } auto from = positionedFromArg(args[0]); @@ -1465,27 +1528,47 @@ static naRef f_createViaFromTo(naContext c, naRef me, int argc, naRef* args) naRuntimeError(c, "createViaFromTo: from wp not found"); } - std::string airwayName = naStr_data(args[1]); - AirwayRef airway = Airway::findByIdentAndNavaid(airwayName, from); + AirwayRef airway = airwayGhost(args[1]); + naRef toArg = args[2]; + if (!airway && naIsString(args[1])) { + std::string airwayName = naStr_data(args[1]); + auto level = Airway::Both; + if (argc == 4) { + // this means third arg is high / low select + auto l = airwayLevelFromNasal(args[2]); + toArg = args[3]; + if (!l) { + naRuntimeError(c, "createViaFromTo: level argument is not accepted"); + } + + level = l.value_or(Airway::Both); + } + + + airway = Airway::findByIdent(airwayName, level); + if (!airway) { + naRuntimeError(c, "createViaFromTo: couldn't find airway with provided name: %s", + naStr_data(args[1])); + } + } + if (!airway) { - naRuntimeError(c, "createViaFromTo: couldn't find airway with provided name: %s from wp %s", - naStr_data(args[0]), - from->ident().c_str()); + naRuntimeError(c, "createViaFromTo: invalid airway"); } FGPositionedRef nav; - if (naIsString(args[2])) { - WayptRef enroute = airway->findEnroute(naStr_data(args[2])); + if (naIsString(toArg)) { + WayptRef enroute = airway->findEnroute(naStr_data(toArg)); if (!enroute) { naRuntimeError(c, "unknown waypoint on airway %s: %s", - naStr_data(args[1]), naStr_data(args[2])); + naStr_data(args[1]), naStr_data(toArg)); } nav = enroute->source(); } else { - nav = positionedFromArg(args[2]); + nav = positionedFromArg(toArg); if (!nav) { - naRuntimeError(c, "createViaFromTo: arg[2] is not a navaid"); + naRuntimeError(c, "createViaFromTo: final arg is not a navaid"); } } @@ -2149,6 +2232,11 @@ naRef initNasalFlightPlan(naRef globals, naContext c) airwayPrototype = naNewHash(c); naSave(c, airwayPrototype); hashset(c, airwayPrototype, "contains", naNewFunc(c, naNewCCode(c, f_airway_contains))); + hashset(c, airwayPrototype, "HIGH", naNum(Airway::HighLevel)); + hashset(c, airwayPrototype, "LOW", naNum(Airway::LowLevel)); + hashset(c, airwayPrototype, "BOTH", naNum(Airway::Both)); + + hashset(c, globals, "Airway", airwayPrototype); for (int i = 0; funcs[i].name; i++) { hashset(c, globals, funcs[i].name, diff --git a/test_suite/FGTestApi/testGlobals.cxx b/test_suite/FGTestApi/testGlobals.cxx index dbe0003b6..92ed18a77 100644 --- a/test_suite/FGTestApi/testGlobals.cxx +++ b/test_suite/FGTestApi/testGlobals.cxx @@ -434,11 +434,20 @@ bool executeNasal(const std::string& code) if (!nasal) { throw sg_exception("Nasal not available"); } - - std::string output, errors; - bool ok = nasal->parseAndRunWithOutput(code, output, errors); + + nasal->getAndClearErrorList(); + std::string output, dummyErrors; + bool ok = nasal->parseAndRunWithOutput(code, output, dummyErrors); + if (!dummyErrors.empty()) { + SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << dummyErrors); + return false; + } + + auto errors = nasal->getAndClearErrorList(); if (!errors.empty()) { - SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << errors); + for (auto err : errors) { + SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << err); + } return false; } diff --git a/test_suite/unit_tests/Navaids/test_fpNasal.cxx b/test_suite/unit_tests/Navaids/test_fpNasal.cxx index 0a87eca66..d995b1688 100644 --- a/test_suite/unit_tests/Navaids/test_fpNasal.cxx +++ b/test_suite/unit_tests/Navaids/test_fpNasal.cxx @@ -403,16 +403,19 @@ void FPNasalTests::testAirwaysAPI() { bool ok = FGTestApi::executeNasal(R"( var airwayIdent = "L620"; - var airwayStore = airway(airwayIdent); + var airwayStore = airway(airwayIdent, "low"); unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found"); unitTest.assert(airwayStore.id == airwayIdent, "Incorrect airway found"); - + unitTest.assert_equal(airwayStore.level, 'low', "Incorrect airway found"); + unitTest.assert_equal(airwayStore.level_code, Airway.LOW, "Incorrect airway found"); + airwayIdent = "UL620"; var cln = findNavaidsByID("CLN", "VOR")[0]; airwayStore = airway(airwayIdent, cln); unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found"); unitTest.assert(airwayStore.id == airwayIdent, "Incorrect airway found"); - + unitTest.assert_equal(airwayStore.level_code, Airway.HIGH, "Incorrect airway found"); + airwayIdent = "J547"; airwayStore = airway(airwayIdent); unitTest.assert(airwayStore != nil, "Airway " ~ airwayIdent ~ " not found"); @@ -420,6 +423,30 @@ void FPNasalTests::testAirwaysAPI() )"); CPPUNIT_ASSERT(ok); + + ok = FGTestApi::executeNasal(R"( + + var airwayIdent = "L620"; + var airwayStore = airway(airwayIdent, Airway.LOW); + var cln = findNavaidsByID("CLN", "VOR")[0]; + + var v1 = createViaTo(airwayIdent, "CLN"); + unitTest.assert_equal(v1.wp_type, "via"); + unitTest.assert_equal(v1.airway.id, 'L620'); + unitTest.assert_equal(v1.airway.level_code, Airway.LOW); + + var v2 = createViaTo(airwayStore, "TULIP"); + unitTest.assert_equal(v2.wp_type, "via"); + unitTest.assert_equal(v2.airway.id, airwayIdent); + + var v3 = createViaFromTo(cln, "L620", 'low', "TULIP"); + unitTest.assert_equal(v3.airway.id, 'L620'); + + var v4 = createViaFromTo(cln, "L620", "REDFA"); + unitTest.assert_equal(v4.airway.level_code, Airway.LOW); + )"); + + CPPUNIT_ASSERT(ok); } void FPNasalTests::testTotalDistanceAPI()