diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index ed49a10f..e7082454 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1243,6 +1243,36 @@ jobs: Compress-Archive -Path (Join-Path $outRoot '*') -DestinationPath (Join-Path $env:GITHUB_WORKSPACE 'nlpengine-compile-libs.zip') -Force + - name: Compiled analyzer calls interpreter-only builtins + shell: bash + run: | + set -eo pipefail + # A builtin added to the interpreter without a compiled Arun:: twin made + # every compiled analyzer that called it fail to build: 23 of them, among + # them jsonwrite, which the KB template calls. The generator now routes + # them through Arun::fnbridge. Build this fixture against the compile + # libraries just packaged, run it, and compare with the interpreted run. + FIX=.github/workflows/tests/compiled-bridge + LIBS="$(cygpath -m "$GITHUB_WORKSPACE")/compile-libs" + rm -rf "$FIX/logs" "$FIX/input/text.txt_log" "$FIX/output" "$FIX/run" "$FIX/build" + mkdir -p "$FIX/logs" + ./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" + tr -d '\r' < "$FIX/input/text.txt_log/output.txt" > bridge-interpreted.txt + n=$(wc -l < bridge-interpreted.txt) + if [ "$n" -ne 23 ]; then + echo "FAIL(compiled-bridge): interpreted run printed $n lines, wanted 23:"; cat bridge-interpreted.txt; exit 1 + fi + rm -rf "$FIX/input/text.txt_log" "$FIX/output" + ./bin/Release/nlp.exe -COMPILEANA -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" + cmake -S "$FIX" -B "$FIX/build" -A x64 -DENGINE_LIBS="$LIBS" + cmake --build "$FIX/build" --config Release + rm -rf "$FIX/input/text.txt_log" "$FIX/output" + ./bin/Release/nlp.exe -COMPILED -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" + if ! diff bridge-interpreted.txt <(tr -d '\r' < "$FIX/input/text.txt_log/output.txt"); then + echo "FAIL(compiled-bridge): compiled output differs from interpreted"; exit 1 + fi + echo "[compiled-bridge] interpreter-only builtins work in a compiled analyzer" + - name: Upload nlpengine-compile-libs.zip uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/tests/compiled-bridge/.gitignore b/.github/workflows/tests/compiled-bridge/.gitignore new file mode 100644 index 00000000..24088e45 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/.gitignore @@ -0,0 +1,10 @@ +run/ +kb/ +build/ +logs/ +output/ +input/*_log/ +*.dll +*.exp +*.lib +*.pdb diff --git a/.github/workflows/tests/compiled-bridge/CMakeLists.txt b/.github/workflows/tests/compiled-bridge/CMakeLists.txt new file mode 100644 index 00000000..c5ecee0a --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/CMakeLists.txt @@ -0,0 +1,50 @@ +# Builds this fixture as a compiled analyzer (analyzer.dll) against the +# packaged compile libraries, as the VS Code extension and the compile service +# do (their generated CMakeLists.txt is the model). Used by the Windows CI step +# "Compiled analyzer calls interpreter-only builtins". +# +# nlp -COMPILEANA -ANA ... (writes run/*.cpp) +# cmake -S -B /build -A x64 -DENGINE_LIBS= +# cmake --build /build --config Release +cmake_minimum_required(VERSION 3.16) +project(nlp_generated_library LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT ENGINE_LIBS) + message(FATAL_ERROR "Pass -DENGINE_LIBS=.") +endif() + +set(ANALYZER_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${ANALYZER_DIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANALYZER_DIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${ANALYZER_DIR}") +foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${ANALYZER_DIR}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${ANALYZER_DIR}") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} "${ANALYZER_DIR}") +endforeach() + +file(GLOB GENERATED_CPP "${ANALYZER_DIR}/run/*.cpp" "${ANALYZER_DIR}/kb/*.cpp") +if(NOT GENERATED_CPP) + message(FATAL_ERROR "No generated C++ in run/ or kb/. Run nlp -COMPILEANA first.") +endif() + +add_library(nlp_generated SHARED ${GENERATED_CPP}) +set_target_properties(nlp_generated PROPERTIES OUTPUT_NAME "analyzer" PREFIX "") + +target_include_directories(nlp_generated PRIVATE + "${ANALYZER_DIR}" + "${ANALYZER_DIR}/run" + "${ANALYZER_DIR}/kb" + "${ENGINE_LIBS}/include/Api" + "${ENGINE_LIBS}/include/cs" + "${ENGINE_LIBS}/include" +) + +file(GLOB ENGINE_LIBS_ALL "${ENGINE_LIBS}/lib/*.lib") +target_link_libraries(nlp_generated PRIVATE ${ENGINE_LIBS_ALL}) +target_compile_definitions(nlp_generated PRIVATE _CRT_SECURE_NO_WARNINGS) +target_compile_options(nlp_generated PRIVATE /wd4005 /FI"StdAfx.h" /MP) diff --git a/.github/workflows/tests/compiled-bridge/StdAfx.h b/.github/workflows/tests/compiled-bridge/StdAfx.h new file mode 100644 index 00000000..b1e5195d --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/StdAfx.h @@ -0,0 +1,13 @@ +// Auto-generated stub. Engine-generated .cpp files include "StdAfx.h" by convention. +#pragma once +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#endif +#include "my_tchar.h" diff --git a/.github/workflows/tests/compiled-bridge/input/text.txt b/.github/workflows/tests/compiled-bridge/input/text.txt new file mode 100644 index 00000000..3b18e512 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/input/text.txt @@ -0,0 +1 @@ +hello world diff --git a/.github/workflows/tests/compiled-bridge/spec/abs.nlp b/.github/workflows/tests/compiled-bridge/spec/abs.nlp new file mode 100644 index 00000000..bdf65cd9 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/abs.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: abs.nlp +# SUBJ: abs() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = abs(-5); +"output.txt" << "abs: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/analyzer.seq b/.github/workflows/tests/compiled-bridge/spec/analyzer.seq new file mode 100644 index 00000000..c0c68f7a --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/analyzer.seq @@ -0,0 +1,24 @@ +tokenize nil # +nlp abs # +nlp arrayreverse # +nlp arrayslice # +nlp arraysort # +nlp arrayunique # +nlp push # +nlp strindexof # +nlp strlastindexof # +nlp striscaps # +nlp strjoin # +nlp rematch # +nlp refind # +nlp resubst # +nlp fileexists # +nlp direxists # +nlp filesize # +nlp readfile # +nlp readlines # +nlp dirlist # +nlp deletefile # +nlp randomint # +nlp jsonwrite # +nlp jsonparse # diff --git a/.github/workflows/tests/compiled-bridge/spec/arrayreverse.nlp b/.github/workflows/tests/compiled-bridge/spec/arrayreverse.nlp new file mode 100644 index 00000000..3ab4c454 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/arrayreverse.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: arrayreverse.nlp +# SUBJ: arrayreverse() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = 1; L("a")[1] = 2; L("a")[2] = 3; +L("b") = arrayreverse(L("a")); +L("r") = str(L("b")[0]) + str(L("b")[1]) + str(L("b")[2]); +"output.txt" << "arrayreverse: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/arrayslice.nlp b/.github/workflows/tests/compiled-bridge/spec/arrayslice.nlp new file mode 100644 index 00000000..1fe031ae --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/arrayslice.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: arrayslice.nlp +# SUBJ: arrayslice() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = "p"; L("a")[1] = "q"; L("a")[2] = "r"; +L("b") = arrayslice(L("a"), 1); +L("r") = L("b")[0] + L("b")[1]; +"output.txt" << "arrayslice: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/arraysort.nlp b/.github/workflows/tests/compiled-bridge/spec/arraysort.nlp new file mode 100644 index 00000000..86aff8f0 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/arraysort.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: arraysort.nlp +# SUBJ: arraysort() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = 30; L("a")[1] = 10; L("a")[2] = 20; +L("b") = arraysort(L("a"), 1); +L("r") = str(L("b")[0]) + "," + str(L("b")[1]) + "," + str(L("b")[2]); +"output.txt" << "arraysort: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/arrayunique.nlp b/.github/workflows/tests/compiled-bridge/spec/arrayunique.nlp new file mode 100644 index 00000000..035e07fd --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/arrayunique.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: arrayunique.nlp +# SUBJ: arrayunique() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = "x"; L("a")[1] = "x"; L("a")[2] = "y"; +L("b") = arrayunique(L("a")); +L("r") = str(arraylength(L("b"))); +"output.txt" << "arrayunique: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/deletefile.nlp b/.github/workflows/tests/compiled-bridge/spec/deletefile.nlp new file mode 100644 index 00000000..7ea30dd8 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/deletefile.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: deletefile.nlp +# SUBJ: deletefile() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(deletefile("no-such-file.txt")); +"output.txt" << "deletefile: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/direxists.nlp b/.github/workflows/tests/compiled-bridge/spec/direxists.nlp new file mode 100644 index 00000000..81151f3b --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/direxists.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: direxists.nlp +# SUBJ: direxists() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(direxists("no-such-dir")); +"output.txt" << "direxists: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/dirlist.nlp b/.github/workflows/tests/compiled-bridge/spec/dirlist.nlp new file mode 100644 index 00000000..71cd2a78 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/dirlist.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: dirlist.nlp +# SUBJ: dirlist() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(arraylength(dirlist("no-such-dir"))); +"output.txt" << "dirlist: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/fileexists.nlp b/.github/workflows/tests/compiled-bridge/spec/fileexists.nlp new file mode 100644 index 00000000..fd9dc876 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/fileexists.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: fileexists.nlp +# SUBJ: fileexists() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(fileexists("no-such-file.txt")); +"output.txt" << "fileexists: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/filesize.nlp b/.github/workflows/tests/compiled-bridge/spec/filesize.nlp new file mode 100644 index 00000000..96a1a01b --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/filesize.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: filesize.nlp +# SUBJ: filesize() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = filesize("no-such-file.txt"); +"output.txt" << "filesize: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/jsonparse.nlp b/.github/workflows/tests/compiled-bridge/spec/jsonparse.nlp new file mode 100644 index 00000000..508c62d4 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/jsonparse.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: jsonparse.nlp +# SUBJ: jsonparse() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("c") = makeconcept(findroot(), "jp"); +L("ok") = jsonparse("{\"a\": 1}", L("c")); +L("r") = str(L("ok")) + " " + str(numval(L("c"), "a")); +"output.txt" << "jsonparse: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/jsonwrite.nlp b/.github/workflows/tests/compiled-bridge/spec/jsonwrite.nlp new file mode 100644 index 00000000..1be4ce66 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/jsonwrite.nlp @@ -0,0 +1,14 @@ +############################################### +# FILE: jsonwrite.nlp +# SUBJ: jsonwrite() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("c") = makeconcept(findroot(), "jw"); +addstrval(L("c"), "k", "v"); +L("f") = G("$apppath") + "/output/bridge.json"; +jsonwrite(L("f"), L("c")); +L("r") = str(fileexists(L("f"))) + " " + str(strlength(readfile(L("f"))) > 10); +"output.txt" << "jsonwrite: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/push.nlp b/.github/workflows/tests/compiled-bridge/spec/push.nlp new file mode 100644 index 00000000..ad472baf --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/push.nlp @@ -0,0 +1,12 @@ +############################################### +# FILE: push.nlp +# SUBJ: push() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = 2; L("a")[1] = 3; +L("b") = push(1, L("a")); +L("r") = str(L("b")[0]) + str(L("b")[1]) + str(L("b")[2]); +"output.txt" << "push: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/randomint.nlp b/.github/workflows/tests/compiled-bridge/spec/randomint.nlp new file mode 100644 index 00000000..384e68fb --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/randomint.nlp @@ -0,0 +1,11 @@ +############################################### +# FILE: randomint.nlp +# SUBJ: randomint() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("n") = randomint(5, 5); +L("r") = L("n"); +"output.txt" << "randomint: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/readfile.nlp b/.github/workflows/tests/compiled-bridge/spec/readfile.nlp new file mode 100644 index 00000000..8c6d4181 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/readfile.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: readfile.nlp +# SUBJ: readfile() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = "[" + readfile("no-such-file.txt") + "]"; +"output.txt" << "readfile: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/readlines.nlp b/.github/workflows/tests/compiled-bridge/spec/readlines.nlp new file mode 100644 index 00000000..92d41203 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/readlines.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: readlines.nlp +# SUBJ: readlines() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(arraylength(readlines("no-such-file.txt"))); +"output.txt" << "readlines: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/refind.nlp b/.github/workflows/tests/compiled-bridge/spec/refind.nlp new file mode 100644 index 00000000..3e9272a2 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/refind.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: refind.nlp +# SUBJ: refind() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = refind("abcabc", "c"); +"output.txt" << "refind: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/rematch.nlp b/.github/workflows/tests/compiled-bridge/spec/rematch.nlp new file mode 100644 index 00000000..2942009f --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/rematch.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: rematch.nlp +# SUBJ: rematch() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(rematch("abc", "a.c")) + str(rematch("abc", "x")); +"output.txt" << "rematch: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/resubst.nlp b/.github/workflows/tests/compiled-bridge/spec/resubst.nlp new file mode 100644 index 00000000..4f6d2d23 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/resubst.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: resubst.nlp +# SUBJ: resubst() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = resubst("abcabc", "b", "X"); +"output.txt" << "resubst: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/strindexof.nlp b/.github/workflows/tests/compiled-bridge/spec/strindexof.nlp new file mode 100644 index 00000000..df4dc057 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/strindexof.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: strindexof.nlp +# SUBJ: strindexof() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = strindexof("hello", "l"); +"output.txt" << "strindexof: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/striscaps.nlp b/.github/workflows/tests/compiled-bridge/spec/striscaps.nlp new file mode 100644 index 00000000..daa9a5ed --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/striscaps.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: striscaps.nlp +# SUBJ: striscaps() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = str(striscaps("Hello")) + str(striscaps("hello")); +"output.txt" << "striscaps: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/strjoin.nlp b/.github/workflows/tests/compiled-bridge/spec/strjoin.nlp new file mode 100644 index 00000000..8b18b897 --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/strjoin.nlp @@ -0,0 +1,11 @@ +############################################### +# FILE: strjoin.nlp +# SUBJ: strjoin() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("a")[0] = "x"; L("a")[1] = "y"; L("a")[2] = "z"; +L("r") = strjoin(L("a"), "-"); +"output.txt" << "strjoin: " << L("r") << "\n"; +@@CODE diff --git a/.github/workflows/tests/compiled-bridge/spec/strlastindexof.nlp b/.github/workflows/tests/compiled-bridge/spec/strlastindexof.nlp new file mode 100644 index 00000000..450da5de --- /dev/null +++ b/.github/workflows/tests/compiled-bridge/spec/strlastindexof.nlp @@ -0,0 +1,10 @@ +############################################### +# FILE: strlastindexof.nlp +# SUBJ: strlastindexof() in interpreted and compiled analyzers. A compiled +# analyzer calls it through Arun::fnbridge. +############################################### + +@CODE +L("r") = strlastindexof("hello", "l"); +"output.txt" << "strlastindexof: " << L("r") << "\n"; +@@CODE diff --git a/include/Api/lite/Arun.h b/include/Api/lite/Arun.h index e89bdd38..ac2f4704 100644 --- a/include/Api/lite/Arun.h +++ b/include/Api/lite/Arun.h @@ -15,6 +15,7 @@ All rights reserved. #include #include +#include #include "lite.h" typedef void DELTS; // Doubly-linked list. // 07/07/03 AM. @@ -1269,6 +1270,8 @@ class LITE_API Arun static RFASem *sem(float); // 03/07/02 AM. static RFASem *sem(_TCHAR*); // 03/07/02 AM. static RFASem *sem(RFASem*); // Noop. // 03/07/02 AM. + // Builtins with no compiled twin run through the interpreter (arunbridge.cpp). + static RFASem *fnbridge(Nlppp *, _TCHAR *name, std::initializer_list args); static _TCHAR *strtotitle(Nlppp*,_TCHAR*); // 08/22/00 AM. static _TCHAR *strtotitle(Nlppp*,RFASem*); // 08/22/00 AM. diff --git a/lite/arunbridge.cpp b/lite/arunbridge.cpp new file mode 100644 index 00000000..e7ed8d7d --- /dev/null +++ b/lite/arunbridge.cpp @@ -0,0 +1,82 @@ +/******************************************************************************* +Copyright (c) 2026 by VisualText. +All rights reserved. +******************************************************************************** +* +* NAME: ARUNBRIDGE.CPP +* FILE: lite/arunbridge.cpp +* SUBJ: Compiled-analyzer bridge to builtins that exist only in the +* interpreter. +* NOTE: The code generator writes a call to a builtin as Arun::(...), +* so a builtin added to Fn:: without a compiled Arun:: twin made any +* compiled analyzer that called it fail to build. For those builtins +* the generator now writes Arun::fnbridge(nlppp, "name", {args}), +* which runs the interpreter's own implementation: compiled and +* interpreted behave the same, and nothing is reimplemented. +* +*******************************************************************************/ + +#include "StdAfx.h" +#include "machine.h" +#include "u_out.h" +#include "lite/global.h" +#include "inline.h" +#include "dlist.h" +#include "node.h" +#include "lite/iarg.h" +#include "str.h" +#include "io.h" +#include "tree.h" +#include "lite/nlppp.h" +#include "gen.h" +#include "parse.h" +#include "iaction.h" +#include "rfasem.h" +#include "fn.h" +#include "lite/Arun.h" + + +/******************************************** +* FN: FNBRIDGE +* SUBJ: Call the interpreter's builtin NAME with already-evaluated ARGS. +* RET: The builtin's value (never 0: an error yields 0 as a number). +* NOTE: Mirrors Iaction::eval: the arguments become IASEM args of a +* temporary action, Fn::fnCall runs the builtin, and the argument +* list is freed with Iarg::delete_top, as Iaction::eval frees its +* evaluated arguments. The bridge owns ARGS, as an Arun:: builtin +* owns the RFASem* args the generated code hands it. +********************************************/ + +RFASem *Arun::fnbridge( + Nlppp *nlppp, + _TCHAR *name, + std::initializer_list args + ) +{ +Dlist *iargs = new Dlist(); +for (RFASem *sem : args) + iargs->rpush(new Iarg(sem ? sem : new RFASem(0LL))); + +Iaction action(name, iargs, 0); +bool badname = false; +RFASem *val = 0; +bool ok = Fn::fnCall(&action, nlppp, /*UP*/ badname, val); +action.setArgs(0); // The action doesn't own the list. +Iarg::delete_top(iargs); + +if (!ok && val) + { + delete val; + val = 0; + } +if (!ok) + { + std::_t_strstream gerrStr; + if (badname) + gerrStr << _T("[Fncall: Error: Unknown fn/action name=") << name << _T("]") << std::ends; + else + gerrStr << _T("[Fncall: Error executing fn/action=") << name << _T("]") << std::ends; + nlppp->parse_->errOut(&gerrStr,false); + } +return val ? val : new RFASem(0LL); +} diff --git a/lite/iaction.cpp b/lite/iaction.cpp index 3a6c633a..8c9b8eac 100644 --- a/lite/iaction.cpp +++ b/lite/iaction.cpp @@ -795,6 +795,47 @@ return true; } +// Builtins that exist only in the interpreter (Fn::), with no compiled +// Arun:: twin. A compiled analyzer calls them through Arun::fnbridge, +// which runs the interpreter's implementation. Before, the generated +// Arun::(...) call didn't compile. A new builtin without an Arun:: +// function belongs here. +static const _TCHAR *bridgedFns[] = { + _T("abs"), + _T("arrayreverse"), + _T("arrayslice"), + _T("arraysort"), + _T("arrayunique"), + _T("deletefile"), + _T("direxists"), + _T("dirlist"), + _T("fileexists"), + _T("filesize"), + _T("jsonparse"), + _T("jsonwrite"), + _T("push"), + _T("randomint"), + _T("readfile"), + _T("readlines"), + _T("refind"), + _T("rematch"), + _T("resubst"), + _T("strindexof"), + _T("striscaps"), + _T("strjoin"), + _T("strlastindexof"), + 0 + }; + +static bool isBridged(const _TCHAR *name) +{ +for (const _TCHAR **p = bridgedFns; *p; ++p) + if (!strcmp_i((_TCHAR *) *p, (_TCHAR *) name)) + return true; +return false; +} + + /******************************************** * FN: GENEVAL * CR: 05/07/00 AM. @@ -940,10 +981,13 @@ else _TCHAR callPrefix[MAXSTR + 64]; callPrefix[0] = '\0'; bool userdef = false; // User-defined in NLP++ // 03/11/02 AM. +bool bridged = false; // Interpreter-only builtin, via Arun::fnbridge. if (!scope_ || !*scope_) // 02/13/01 AM. { // if (VTRun_Ptr->isBuiltin(fnname)) // 08/28/02 AM. // [DEGLOB] // 10/15/20 AM. - if (vtrun->isBuiltin(fnname)) // [DEGLOB] // 10/15/20 AM. + if (vtrun->isBuiltin(fnname) && isBridged(fnname)) + bridged = true; // No Arun:: twin: Arun::fnbridge. + else if (vtrun->isBuiltin(fnname)) // [DEGLOB] // 10/15/20 AM. _stprintf(callPrefix, _T("Arun::%s"), fnname); else // 12/24/01 AM. { @@ -1056,24 +1100,39 @@ if (wrapLambda) for (Delt *d = args_->getFirst(); d; d = d->Right(), ++idx) { *fcode << _T("auto _a") << idx << _T(" = "); - if (userdef) *fcode << _T("Arun::sem("); + if (userdef || bridged) *fcode << _T("Arun::sem("); if (!emitArgExpr(d->getData())) return false; - if (userdef) *fcode << _T(")"); + if (userdef || bridged) *fcode << _T(")"); *fcode << _T("; "); } + if (bridged) + { + *fcode << _T("return Arun::fnbridge(") << firstArg + << _T(", (_TCHAR*)_T(\"") << fnname << _T("\"), {"); + for (int i = 0; i < argCount; ++i) + *fcode << (i ? _T(", _a") : _T("_a")) << i; + *fcode << _T("}); }())"); + } + else + { *fcode << _T("return ") << callPrefix << _T("(") << firstArg; for (int i = 0; i < argCount; ++i) *fcode << _T(", _a") << i; *fcode << _T("); }())"); + } } else { // Legacy inline path: preserved for 0/1-arg calls and for calls that // contain IAREF or IAOSTREAM args (the latter intentionally early- // returns from Iaction::genEval — that quirk lives here). + if (bridged) + *fcode << _T("Arun::fnbridge(") << firstArg + << _T(", (_TCHAR*)_T(\"") << fnname << _T("\"), {"); + else *fcode << callPrefix << _T("(") << firstArg; Delt *darg = args_ ? args_->getFirst() : 0; - if (darg) *fcode << _T(", "); + if (darg && !bridged) *fcode << _T(", "); for (; darg; darg = darg->Right()) { Iarg *iarg = darg->getData(); @@ -1083,12 +1142,14 @@ else return true; return false; } - if (userdef) *fcode << _T("Arun::sem("); + if (userdef || bridged) *fcode << _T("Arun::sem("); if (!emitArgExpr(iarg)) return false; - if (userdef) *fcode << _T(")"); + if (userdef || bridged) *fcode << _T(")"); if (darg->Right()) *fcode << _T(", "); } + if (bridged) + *fcode << _T("}"); *fcode << _T(")"); } diff --git a/nlp/main.cpp b/nlp/main.cpp index 8717f9a4..0b015e40 100644 --- a/nlp/main.cpp +++ b/nlp/main.cpp @@ -15,7 +15,7 @@ All rights reserved. #include "lite/nlp_engine.h" #include "version.h" -#define NLP_ENGINE_VERSION "4.2.24" +#define NLP_ENGINE_VERSION "4.2.25" bool cmdReadArgs(int, _TCHAR *argv[], _TCHAR *&, _TCHAR *&, _TCHAR *&, _TCHAR *&, bool &, bool &, bool &, bool &, bool &, bool &, int &, bool &); void cmdHelpargs(_TCHAR *);