Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ else
echo " WARNING: matiec binary not found at $BIN_DIR/iec2c"
fi

if [ -f "$BIN_DIR/xml2st" ]; then
echo " xml2st binary ($NODE_ARCH): OK"
else
echo " WARNING: xml2st binary not found at $BIN_DIR/xml2st"
fi

# Fix Electron sandbox for container (chrome-sandbox needs SUID root + mode 4755)
CHROME_SANDBOX="node_modules/electron/dist/chrome-sandbox"
if [ -f "$CHROME_SANDBOX" ]; then
Expand Down
1 change: 0 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@
# Identification for binary files used as compilers/transpilers
##########################
iec2c binary
xml2st binary
resources/bin/**/* binary
2 changes: 1 addition & 1 deletion .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts

- name: Download strucpp + xml2st binaries
- name: Download strucpp binaries
# tsc needs the strucpp package to resolve the types imported
# from `backend/shared/library/strucpp-runtime.ts` and friends.
# Run the binary downloader directly (skipping the rest of
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
# --ignore-scripts skips the postinstall, so strucpp (a GitHub-release
# package, not an npm dep) is never fetched — yet the TS sources import
# `strucpp/libs/iec-types.json` and its runtime headers. Install just
# strucpp (no xml2st platform binary, no native rebuild) so the suites
# strucpp (no native rebuild) so the suites
# can load and the coverage gate actually runs.
- name: Install strucpp
run: npm run setup:strucpp
Expand Down
2 changes: 1 addition & 1 deletion src/backend/editor/compiler/compiler-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,8 @@

checkStrucppAvailability(): MethodsResult<string> {
try {
const { getVersion } = loadStrucpp()

Check warning on line 490 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe array destructuring of a tuple element with an error typed value
return { success: true, data: getVersion() }

Check warning on line 491 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe call of a(n) `error` type typed value

Check warning on line 491 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe assignment of an error typed value
} catch {
throw new Error('STruC++ not available. Run "npm run setup:binaries" to install it.')
}
Expand Down Expand Up @@ -2316,7 +2316,7 @@
* Main compile entry point. Drives the full Step 0-13 flow
* through the shared `runCompilePipeline` orchestrator
* (`backend/shared/compile/pipeline.ts`); platform-specific bits
* (xml2st spawn, arduino-cli spawn, runtime upload) are abstracted
* (arduino-cli spawn, runtime upload) are abstracted
* behind `EditorCompilerPlatformPort`. Single source of truth
* for compile behaviour shared with openplc-web.
*/
Expand Down Expand Up @@ -2844,7 +2844,7 @@
_mainProcessPort.postMessage({
logLevel,
message: data,
...(compileError ? { compileError } : {}),

Check warning on line 2847 in src/backend/editor/compiler/compiler-module.ts

View workflow job for this annotation

GitHub Actions / lint / Lint Check

Unsafe assignment of an error typed value
})
},
{ hasCBlocks, pous: knownPous, libraries, missingLibraries },
Expand Down
4 changes: 2 additions & 2 deletions src/backend/editor/utils/xml-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { join } from 'path'
/**
* Create an xml file with the given params. Synchronous on
* purpose, same reason `CreateJSONFile` is: every caller chains
* the next step (running xml2st on the file) immediately after,
* the next step (reading the file) immediately after,
* and the previous async-fire-and-forget form returned success
* before libuv had actually flushed the bytes — a fast subsequent
* spawn could read 0 bytes. The compile pipeline never observed
* this in practice because xml2st spawns slowly enough that the
* this in practice because the read happened slowly enough that the
* write usually won the race, but the API contract has always
* been wrong.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* core). These tests lock that ordering in.
*
* Kept in a separate file from `pipeline.test.ts` (which is stale from
* the xml2st→JSON-transpiler migration and references the removed
* the XML→JSON-transpiler migration and references the removed
* `transpileXmlToSt` port method) so the v3 coverage compiles + runs
* against the current `transpileToSt` contract.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/backend/shared/compile/__tests__/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ describe('runCompilePipeline — simulator path', () => {
expect(result.uploaded).toBe(false)
expect(port.transpileToSt).toHaveBeenCalledTimes(1)
// The pipeline hands the in-process transpiler the project IR plus
// a log callback — no XML / xml2st flags flow through anymore.
// a log callback — no XML / transpiler flags flow through anymore.
expect(port.transpileToSt).toHaveBeenCalledWith(
expect.objectContaining({ projectData: expect.anything() }),
expect.any(Function),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Tests for the pre-compile blank-FBD-variable guard.
*
* An unnamed FBD input/output variable block becomes an empty
* `<expression/>` in the PLCopen XML, which crashes xml2st with
* `<expression/>` in the PLCopen XML, which the compiler rejects with
* `'NoneType' object has no attribute 'split'`. These tests pin the
* detector that lets the pipeline bail with a clear message — naming
* what the block is wired to, or its position when it is wired to
Expand Down
13 changes: 6 additions & 7 deletions src/backend/shared/compile/pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* Shared OpenPLC compile pipeline.
*
* Single source of truth for the full compile flow (Steps 0–13 in
* the editor's canonical pipeline). Editor and web both drive this
* function through a `CompilerPlatformPort`; the platform port
* abstracts the three places where platform truly differs (xml2st
* abstracts the three places where platform truly differs (ST transpiler
* transport, arduino-cli transport, runtime upload transport).
* Everything else — preprocessing, XML generation, strucpp compile,
* conf authoring, defines authoring, bundle composition, ordering,
Expand Down Expand Up @@ -238,7 +238,7 @@

export interface RunCompilePipelineResult {
success: boolean
/** Structured strucpp + xml2st diagnostics from this run. Carries
/** Structured strucpp diagnostics from this run. Carries
* the per-error events the renderer's navigation keys off. */
errors?: StructuredCompileError[]
Comment on lines +241 to 243

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document all pipeline error sources, not only strucpp diagnostics.

RunCompilePipelineResult.errors can receive ST-transpiler errors and Arduino compilation errors as well as strucpp errors. Calling the field “Structured strucpp diagnostics” makes the public result contract misleading; describe it as structured compile-pipeline diagnostics or split it by stage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/shared/compile/pipeline.ts` around lines 241 - 243, Update the
documentation for RunCompilePipelineResult.errors to describe structured
diagnostics from the entire compile pipeline, including ST-transpiler, Arduino
compilation, and strucpp stages, rather than attributing them only to strucpp.
Keep the existing errors field and type unchanged.

/** Compiled firmware bytes when the pipeline reached the
Expand Down Expand Up @@ -385,10 +385,9 @@
// ---------------------------------------------------------------------
// Step 0b: Reject blank FBD variable blocks before XML generation.
//
// An unnamed FBD in/out variable serialises to an empty
// `<expression/>`, which makes xml2st crash with the opaque
// `'NoneType' object has no attribute 'split'`. Catch it here and
// tell the user exactly which POU to fix.
// An unnamed FBD in/out variable has no expression for the ST
// transpiler to emit, producing invalid code downstream. Catch it
// here and tell the user exactly which POU to fix.
// ---------------------------------------------------------------------
const emptyVariables = findEmptyFbdVariables(processedData)
if (emptyVariables.length > 0) {
Expand All @@ -411,7 +410,7 @@
// so this hop never builds PLCOpen XML. Native STRUCT declarations
// are the only emission mode the transpiler supports — the legacy
// matiec struct→FB rewrite isn't ported, so there are no
// equivalents of the old `xml2stArgs` flags.
// equivalents of the old struct-rewrite flags.
// ---------------------------------------------------------------------
emit({ stage: 'st', message: 'Generating Structured Text...', level: 'info' })
// The pipeline carries the editor's schema-shape `PLCProjectData`,
Expand Down
2 changes: 1 addition & 1 deletion src/backend/shared/compile/steps/generate-defines.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/**
* Author the `defines.h` content for an OpenPLC build target.
*
Expand Down Expand Up @@ -48,7 +48,7 @@
* category (`DIN` / `AIN` / `DOUT` / `AOUT`) plus matching
* count defines (`NUM_DISCRETE_INPUT` etc.). */
devicePinMapping: DevicePin[]
/** Concatenated ST program content (the output of xml2st).
/** Concatenated ST program content (the output of the ST transpiler).
* Scanned with `String.prototype.includes` for the marker
* function-block names that toggle the Arduino-library
* `USE_*_BLOCK` defines. The set of marker strings here is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PLCProjectData } from '../../types/PLC/open-plc'
* An FBD variable block (input or output) whose name is blank.
*
* Such a block serialises to an empty `<expression/>` in the PLCopen
* XML, which makes xml2st abort the whole compile with the opaque
* XML, which would make the compiler abort the whole compile with the opaque
* `'NoneType' object has no attribute 'split'` error. We catch it
* before XML generation and report it in terms the user can act on:
* what the block is wired to, falling back to its canvas position when
Expand Down
6 changes: 3 additions & 3 deletions src/backend/shared/library/__tests__/build-pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Tests for the library build pipeline.
*
* `prepareXmlForLibraryBuild` no longer generates PLCopen XML — the
* old xml2st flow was replaced by an in-process JSON → ST transpiler.
* the legacy XML→ST flow was replaced by an in-process JSON → ST transpiler.
* The function now only validates the manifest and returns the stubbed
* project data (plus the POU inventory the splitter needs); the actual
* transpile happens later via `LibraryBuildPort.transpileToSt`.
Expand Down Expand Up @@ -422,7 +422,7 @@ describe('libraryBuildFromTranspiledSt', () => {

it('drops `_config.st` so strucpp does not error on the stub configuration', () => {
// The stub program (which the splitter recognises and the
// pipeline drops) is referenced by xml2st's emitted
// pipeline drops) is referenced by the transpiler's emitted
// CONFIGURATION block. Leaving `_config.st` in the strucpp
// inputs makes strucpp emit "Unknown program type 'MAIN'"
// diagnostics because the stub source isn't there anymore.
Expand Down Expand Up @@ -637,7 +637,7 @@ describe('libraryBuildFromTranspiledSt', () => {
expect(res.success).toBe(true)
})

it('matches POU docs case-insensitively (xml2st upper-cases identifiers)', () => {
it('matches POU docs case-insensitively (the transpiler upper-cases identifiers)', () => {
const archive = {
manifest: {
name: 'demo_lib',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe('enrichErrorWithPouContext', () => {
})

it('skips blank separator lines between END_VAR and the body', () => {
// The ST generators (`pou-text-serializer.ts` and xml2st on the
// The ST generators (`pou-text-serializer.ts` and the ST transpiler on the
// compile path) insert blank lines after END_VAR for readability.
// Those blanks live in the per-POU file the splitter feeds
// strucpp but NOT in `pou.body.value`, which is what the body
Expand Down
31 changes: 15 additions & 16 deletions src/backend/shared/library/build-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
* 1. `prepareXmlForLibraryBuild(project, manifest)` — synthesizes
* a stub main program / task / instance into a transient
* PLCProject (the on-disk project remains untouched) and runs
* the canonical XmlGenerator on it. xml2st rejects programless
* the canonical XmlGenerator on it. the ST transpiler rejects programless
* projects, so the stub is mandatory; the stub's POU body is
* intentionally non-empty (`LocalVar := 3;` against a single
* INT local) because some xml2st codepaths also reject empty
* INT local) because some ST-transpiler codepaths also reject empty
* program bodies.
*
* 2. *(caller runs xml2st on the resulting plc.xml — Electron
* spawns a local binary, web backend posts to its xml2st
* service — produces `program.st`.)*
* 2. *(the in-process ST transpiler runs on the project and
* produces `program.st`.)*
*
* 3. `libraryBuildFromTranspiledSt(programSt, knownPous, manifest)`
* — splits `program.st` per-POU via the shared splitter, drops
Expand Down Expand Up @@ -74,8 +73,8 @@ export interface LibraryBuildManifest {
* console can render through the existing diagnostic pipeline.
*
* Strucpp itself validates manifests during compile, but doing it
* here lets the build fail BEFORE running xml2st when the manifest
* is obviously broken — saves a slow xml2st spawn on every
* here lets the build fail early when the manifest
* is obviously broken — saves wasted transpile work on every
* mis-edited save.
*/
function parseLibraryManifest(json: string): ManifestParseResult {
Expand Down Expand Up @@ -128,7 +127,7 @@ function parseLibraryManifest(json: string): ManifestParseResult {
}

// ---------------------------------------------------------------------------
// Stub program — makes xml2st accept a programless library project
// Stub program — makes the ST transpiler accept a programless library project
// ---------------------------------------------------------------------------

/**
Expand All @@ -147,14 +146,14 @@ const STUB_INSTANCE_NAME = '__openplc_library_stub_instance__'
/**
* Build a transient PLCProject with a stub main program added on
* top of the library's POUs / data types. The stub is what
* satisfies xml2st (and strucpp's main-program assumption later in
* satisfies the ST transpiler (and strucpp's main-program assumption later in
* the verification path). Caller drops the stub's per-POU output
* before handing the remaining sources to compileStlib.
*
* The stub's body is non-empty (`LocalVar := 3;`) because xml2st
* The stub's body is non-empty (`LocalVar := 3;`) because the ST transpiler
* has been observed to reject programs with completely empty bodies
* — a single trivial assignment + a single INT local is the smallest
* shape that gets accepted across every xml2st version.
* shape that gets accepted across transpiler versions.
*/
function stubProgramFor(project: PLCProject): PLCProject {
return {
Expand Down Expand Up @@ -204,14 +203,14 @@ function stubProgramFor(project: PLCProject): PLCProject {
* Synthetic filename the splitter emits for the stub program. The
* splitter writes its file keys using the caller-side POU name
* verbatim (case preserved), so this matches what `splitProgramSt`
* returns regardless of how xml2st upper-cases identifiers in the
* returns regardless of how the transpiler upper-cases identifiers in the
* monolithic ST output. Caller drops this entry before feeding the
* rest to compileStlib.
*/
const STUB_SPLIT_FILENAME = `${STUB_PROGRAM_NAME}.st`

// ---------------------------------------------------------------------------
// Stage 1: pre-xml2st (pure)
// Stage 1: pre-transpile (pure)
// ---------------------------------------------------------------------------

export interface PrepareXmlResult {
Expand Down Expand Up @@ -257,7 +256,7 @@ export function prepareXmlForLibraryBuild(project: PLCProject, manifestJson: str
}

// ---------------------------------------------------------------------------
// Stage 2: post-xml2st (pure)
// Stage 2: post-transpile (pure)
// ---------------------------------------------------------------------------

export interface LibraryBuildResult {
Expand Down Expand Up @@ -337,7 +336,7 @@ export interface LibraryBuildAux {
}

/**
* Stage 2. Given xml2st's monolithic `program.st`, the POU
* Stage 2. Given the transpiler's monolithic `program.st`, the POU
* inventory from Stage 1, and the parsed manifest: split program.st
* per-POU, drop the stub, hand the remaining sources to strucpp's
* compileStlib.
Expand Down Expand Up @@ -369,7 +368,7 @@ export function libraryBuildFromTranspiledSt(
//
// - The stub program's `.st` file (the library doesn't ship
// the stub).
// - `_config.st` (xml2st's CONFIGURATION block references the
// - `_config.st` (the transpiler's CONFIGURATION block references the
// stub program, which we've just removed — leaving it in
// causes strucpp to emit "Unknown program type 'MAIN'"
// diagnostics). Libraries don't carry configurations
Expand Down
2 changes: 1 addition & 1 deletion src/backend/shared/library/program-build-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function enrichErrorWithPouContext(
if (/^\s*END_VAR\b/i.test(lines[i])) lastEndVar = i + 1 // 1-indexed
}
// Body starts after the last END_VAR, but the ST generator
// (`pou-text-serializer.ts` and xml2st on the compile path)
// (`pou-text-serializer.ts` and the ST transpiler on the compile path)
// inserts blank separator lines between END_VAR and the body
// content. Those blank lines exist in the per-POU file the
// splitter handed strucpp but NOT in `pou.body.value`, which is
Expand Down
2 changes: 1 addition & 1 deletion src/backend/shared/library/program-build-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* pre-computes the hash and passes it in; strucpp embeds it
* into the debug map for stale-layout detection.
*
* - No external-process orchestration — `xml2st` (XML→ST) and
* - No external-process orchestration — the ST transpiler and
* `arduino-cli` (firmware compile) stay in the platform-specific
* orchestrator that wraps this pipeline. This module is purely
* about the strucpp invocation slice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function leafNode(chunks: ProgramChunk[]): PathNode {
/**
* Stable structural key for a PathNode — used by `factorizePaths` to
* detect common terms. Identical to the Python `repr` output the
* original xml2st pipeline keys on.
* original PLCopen pipeline keys on.
*/
export function pythonReprNode(node: PathNode): string {
switch (node.kind) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function generateGraphicalPou(pou: TranspilePou, project: TranspileProjec
/* ────────────────────────── helpers ─────────────────────────────────────── */

// Block signatures from every graphical block instance's variant — the
// co-located equivalent of xml2st's embedded <libraryBlocks> payload. Deduped
// co-located equivalent of the embedded <libraryBlocks> payload. Deduped
// by name, first instance wins — deliberately mirroring the oracle's
// <libraryBlocks> dedup; user POUs are excluded (they resolve from their
// own interface).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function isRecord(v: unknown): v is Record<string, unknown> {
/**
* Build a block signature from a placed block's `node.data.variant`.
*
* Mirrors `collect-library-blocks.ts` / xml2st's `_pou_to_block_infos`:
* Mirrors `collect-library-blocks.ts`'s block-info derivation:
* EN/ENO are implicit control pins (dropped); inOut params appear on both
* sides; a function's return is already a class-`output` variable named `OUT`.
* Generic IEC meta-types (`ANY`, `ANY_NUM`, …) are kept verbatim and resolved
Expand Down
2 changes: 1 addition & 1 deletion src/backend/shared/transpilers/st-transpiler/walker/ld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Walks `RFBody.rungs[*].nodes/edges` directly — no PLCOpen
* intermediate. Output must match the python oracle
* (`xml2st.py --keep-structs --no-complex-parser`) byte-for-byte;
* byte-for-byte against the python oracle;
* `tests/per_case.test.ts` is the per-case validation loop, and
* `tests/golden_react_flow.test.ts` covers the larger harvested
* corpus once it lands.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Tests for collectLibraryBlocks — the pure project→<addData> collector that
* embeds used library-block signatures for the xml2st transpiler.
* embeds used library-block signatures.
*/

import type { PLCProjectData } from '@root/middleware/shared/ports/open-plc-types'
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('collectLibraryBlocks', () => {
])

const result = collectLibraryBlocks(project) as any
expect(result.data['@name']).toBe('openplc.org/xml2st/library-blocks')
expect(result.data['@name']).toBe('openplc.org/library-blocks')
const pous = result.data.libraryBlocks.pou
expect(pous).toHaveLength(1)
expect(pous[0]).toMatchObject({
Expand Down
10 changes: 5 additions & 5 deletions src/backend/shared/utils/PLC/__tests__/split-program-st.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe('splitProgramSt', () => {
expect(result!.files.get('Main.st')).toBe(source)
})

it('matches POU names case-insensitively (xml2st may upper-case)', () => {
// The editor's project model has the user-typed casing; xml2st
it('matches POU names case-insensitively (the transpiler may upper-case)', () => {
// The editor's project model has the user-typed casing; the transpiler
// sometimes upper-cases identifiers. The splitter must handle
// either direction.
const source = 'PROGRAM MAIN\n VAR x : INT; END_VAR\n x := 1;\nEND_PROGRAM\n'
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('splitProgramSt', () => {
expect(result!.files.has('State_Display.st')).toBe(false)
})

it('emits `.st` for ST and graphical POUs (xml2st renders them as ST)', () => {
it('emits `.st` for ST and graphical POUs (the transpiler renders them as ST)', () => {
const source =
'PROGRAM Main_LD\n VAR x : INT; END_VAR\n x := 1;\nEND_PROGRAM\n' +
'PROGRAM Main_ST\n VAR y : INT; END_VAR\n y := 2;\nEND_PROGRAM\n'
Expand Down Expand Up @@ -243,9 +243,9 @@ describe('splitProgramSt', () => {
})
})

describe('realistic xml2st-shaped output', () => {
describe('realistic transpiler-shaped output', () => {
it('handles a multi-POU + TYPE + CONFIGURATION program', () => {
// Mimics the shape xml2st emits for a typical project.
// Mimics the shape the transpiler emits for a typical project.
const source = `TYPE
TrafficState : (RED, YELLOW, GREEN);
END_TYPE
Expand Down
Loading
Loading