diff --git a/package-lock.json b/package-lock.json index a943f91e2..5ae957ff8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,6 @@ "@tailwindcss/forms": "^0.5.10", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.2", - "@xmldom/xmldom": "^0.9.10", "@xyflow/react": "^12.0.1", "auto-zustand-selectors-hook": "^2.0.0", "avr8js": "0.20.0", @@ -11037,15 +11036,6 @@ } } }, - "node_modules/@xmldom/xmldom": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", - "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", - "license": "MIT", - "engines": { - "node": ">=14.6" - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/package.json b/package.json index 9d58123f7..1c7274e2d 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "@tailwindcss/forms": "^0.5.10", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.2", - "@xmldom/xmldom": "^0.9.10", "@xyflow/react": "^12.0.1", "auto-zustand-selectors-hook": "^2.0.0", "avr8js": "0.20.0", diff --git a/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts b/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts index 74a63278d..f2e89f665 100644 --- a/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts +++ b/src/backend/shared/transpilers/st-transpiler/emit/configuration.ts @@ -1,40 +1,22 @@ /** * IR-native `CONFIGURATION … END_CONFIGURATION` block emitter. * - * JSON-direct port of `src/PLCGenerator/configuration.ts` — walks - * `TranspileProject.configuration` instead of the parsed DOM, but - * emits byte-identical chunks. Mirrors Python's - * `ProgramGenerator.GenerateConfiguration` + `GenerateResource` - * (PLCGenerator.py:334-628). + * Walks `TranspileProject.configuration` and emits byte-identical + * chunks against the python oracle's `ProgramGenerator.GenerateConfiguration` + * + `GenerateResource` (PLCGenerator.py:334-628). * * The IR carries exactly one configuration with one resource, named - * `Config0` / `Res0` — same hardcoded names the XML-based emitter - * produced. Global vars are emitted under the configuration block - * (not under the resource), matching `irToPlcOpenXml`'s layout. - * - * CTN-globals provider is honoured: `kind: 'variable'` entries - * (tuple-shape globals) are appended after the IR's - * `globalVariables`. `kind: 'varlist'` entries (raw DOM `` - * elements) are silently ignored on the IR-native path — they only - * come from the legacy DOM-injection flow which has no caller after - * Phase 1. + * `Config0` / `Res0` — same hardcoded names the python oracle + * produces. Global vars are emitted under the configuration block + * (not under the resource). */ -import type { ConfigurationExtraVariablesProvider } from '../helpers/ctn-globals' import type { ProgramChunk } from '../helpers/program' import { computeConfigurationName, computeConfigurationResourceName } from '../helpers/text-helpers' import type { TranspileProject, TranspileVariable } from '../types' import { declaredTypeName, getTypeAsText } from './type-text' import { computeValue } from './value' -export interface GenerateConfigurationOptions { - /** - * Beremiz CTN-injected globals provider. Mirrors - * `Controler.GetConfigurationExtraVariables` (PLCControler.py:1248). - */ - extraVarsProvider?: ConfigurationExtraVariablesProvider | null -} - const CONFIG_NAME = 'Config0' const RESOURCE_NAME = 'Res0' @@ -44,10 +26,7 @@ const RESOURCE_NAME = 'Res0' * globals (caller may still want the keyword shell — Python always * emits the block; we mirror that). */ -export function generateConfigurations( - project: TranspileProject, - options: GenerateConfigurationOptions = {}, -): ProgramChunk[] { +export function generateConfigurations(project: TranspileProject): ProgramChunk[] { const configTagname = computeConfigurationName(CONFIG_NAME) const resourceTagname = computeConfigurationResourceName(CONFIG_NAME, RESOURCE_NAME) @@ -57,11 +36,10 @@ export function generateConfigurations( out.push([CONFIG_NAME, [configTagname, 'name']]) out.push(['\n', []]) - // Configuration-level global variables. IR-shape globals first, - // then any CTN-injected tuple-shape globals. + // Configuration-level global variables. emitGlobalVarList( out, - collectConfigGlobals(project, options.extraVarsProvider), + project.configuration.globalVariables, configTagname, /*indent=*/ ' ', /*varIndent=*/ ' ', @@ -142,59 +120,6 @@ export function generateConfigurations( /* ────────────────────── helpers ─────────────────────────────────────────── */ -function collectConfigGlobals( - project: TranspileProject, - provider: ConfigurationExtraVariablesProvider | null | undefined, -): TranspileVariable[] { - const out: TranspileVariable[] = [...project.configuration.globalVariables] - if (!provider) return out - const entries = provider() - for (const entry of entries) { - if (entry.kind !== 'variable') continue - const tuple = entry.variable - out.push({ - name: tuple.name, - type: tupleTypeToIr(tuple.type), - ...(tuple.initial ? { initialValue: tuple.initial } : {}), - }) - } - return out -} - -function tupleTypeToIr(typeName: string): TranspileVariable['type'] { - // CTN-globals tuples carry the type as a bare string. IEC base - // types resolve to `base-type`; anything else becomes a derived - // reference — mirrors PLCControler.py:1258-1273. - const upper = typeName.toUpperCase() - const elementaryTypes = new Set([ - 'BOOL', - 'SINT', - 'INT', - 'DINT', - 'LINT', - 'USINT', - 'UINT', - 'UDINT', - 'ULINT', - 'REAL', - 'LREAL', - 'TIME', - 'DATE', - 'TOD', - 'DT', - 'STRING', - 'WSTRING', - 'BYTE', - 'WORD', - 'DWORD', - 'LWORD', - ]) - if (elementaryTypes.has(upper)) { - return { definition: 'base-type', value: upper } - } - return { definition: 'derived', value: typeName } -} - function emitGlobalVarList( out: ProgramChunk[], variables: TranspileVariable[], diff --git a/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts index a6fca3957..5f68bd18c 100644 --- a/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts +++ b/src/backend/shared/transpilers/st-transpiler/emit/pou-graphical.ts @@ -9,8 +9,8 @@ * declaration order matches what the python oracle produces. */ +import { PLC_BASE_TYPES } from '../helpers/base-types' import { resolveBlockType } from '../helpers/block-library' -import { PLC_BASE_TYPES } from '../helpers/ctn-globals' import type { ProgramChunk } from '../helpers/program' import { computePouName } from '../helpers/text-helpers' import { varTypeNames } from '../helpers/type-text' diff --git a/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts b/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts index b57700d27..37bfd1ae6 100644 --- a/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts +++ b/src/backend/shared/transpilers/st-transpiler/emit/pou-textual.ts @@ -11,7 +11,7 @@ * dispatch. */ -import { PLC_BASE_TYPES } from '../helpers/ctn-globals' +import { PLC_BASE_TYPES } from '../helpers/base-types' import type { ProgramChunk } from '../helpers/program' import { reIndentText } from '../helpers/text-helpers' import { computePouName } from '../helpers/text-helpers' diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/base-types.ts b/src/backend/shared/transpilers/st-transpiler/helpers/base-types.ts new file mode 100644 index 000000000..8f5aa670b --- /dev/null +++ b/src/backend/shared/transpilers/st-transpiler/helpers/base-types.ts @@ -0,0 +1,33 @@ +/** + * IEC 61131-3 elementary base types — mirrors python's + * `Controler.GetBaseTypes()` (derived from `TypeHierarchy_list` in + * `plcopen/definitions.py:84`). Used by the emit pipeline to + * decide whether a type name resolves to an elementary `` + * element or a `` wrapper. + * + * `WSTRING` is intentionally absent — matches python's `# TODO` + * comment at `definitions.py:118`. + */ + +export const PLC_BASE_TYPES: ReadonlySet = new Set([ + 'BOOL', + 'SINT', + 'INT', + 'DINT', + 'LINT', + 'USINT', + 'UINT', + 'UDINT', + 'ULINT', + 'REAL', + 'LREAL', + 'TIME', + 'DATE', + 'TOD', + 'DT', + 'STRING', + 'BYTE', + 'WORD', + 'DWORD', + 'LWORD', +]) diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts b/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts deleted file mode 100644 index 90539a74a..000000000 --- a/src/backend/shared/transpilers/st-transpiler/helpers/ctn-globals.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Configuration-level globals injected by host plugins (Beremiz CTN - * mechanism). Mirrors `Controler.GetConfigurationExtraVariables` - * (PLCControler.py:1248-1285): a provider callback returns either - * pre-built `varlist` entries or `(name, type, initial)` tuples that - * the configuration emitter folds into the resource's `VAR_GLOBAL` - * sections. - * - * In a standalone xml2st build the provider is stubbed to `[]`, so - * observable behaviour against the python oracle is unchanged — the - * shape is exported so editor/web hosts wiring plugins can plug in. - */ - -/** - * IEC 61131-3 elementary base types — mirrors `Controler.GetBaseTypes()` - * (derived from `TypeHierarchy_list` in `plcopen/definitions.py:84`). - * Used to decide whether a synthesised global's type is elementary - * (emit verbatim) or derived (emit as a type reference). - * - * `WSTRING` is intentionally absent — matches python's `# TODO` - * comment at `definitions.py:118`. - */ -export const PLC_BASE_TYPES: ReadonlySet = new Set([ - 'BOOL', - 'SINT', - 'INT', - 'DINT', - 'LINT', - 'USINT', - 'UINT', - 'UDINT', - 'ULINT', - 'REAL', - 'LREAL', - 'TIME', - 'DATE', - 'TOD', - 'DT', - 'STRING', - 'BYTE', - 'WORD', - 'DWORD', - 'LWORD', -]) - -export interface CtnGlobalVarTuple { - name: string - /** Elementary type name (`'BOOL'`, `'INT'`, …) or a derived-type name. */ - type: string - /** ST source for the initial value (`''` omits `:= …`). */ - initial?: string -} - -export type CtnGlobalEntry = { kind: 'variable'; variable: CtnGlobalVarTuple } - -export type ConfigurationExtraVariablesProvider = () => CtnGlobalEntry[] diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts b/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts index 1125d5fea..256758f01 100644 --- a/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts +++ b/src/backend/shared/transpilers/st-transpiler/helpers/text-helpers.ts @@ -1,12 +1,7 @@ /** - * Text-manipulation helpers used by the body dispatcher and downstream phases. - * - * Mirrors: - * - `ReIndentText` (PLCGenerator.py:66) - * - `ComputePouName` (plcopen/types_enums.py:112) - * - `ComputePouTransitionName` (plcopen/types_enums.py:117) - * - `ComputePouActionName` (plcopen/types_enums.py:122) - * - `ComputeConfigurationName` (plcopen/types_enums.py:127) + * Text-manipulation helpers — mirrors python's `ReIndentText` + * (`PLCGenerator.py:66`) and the `Compute*Name` family in + * `plcopen/types_enums.py:112-132`. */ /** @@ -71,16 +66,6 @@ export function computePouName(name: string): string { return `P::${name}` } -/** `"T::" + pou + "::" + transition`. */ -export function computePouTransitionName(pou: string, transition: string): string { - return `T::${pou}::${transition}` -} - -/** `"A::" + pou + "::" + action`. */ -export function computePouActionName(pou: string, action: string): string { - return `A::${pou}::${action}` -} - /** `"C::" + name`. */ export function computeConfigurationName(name: string): string { return `C::${name}` diff --git a/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts b/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts index 87c3c017b..7f93cbc32 100644 --- a/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts +++ b/src/backend/shared/transpilers/st-transpiler/helpers/type-hierarchy.ts @@ -59,38 +59,3 @@ export const TypeHierarchy: Readonly> = { LWORD: 'ANY_NBIT', // WSTRING intentionally absent — matches Python's `# TODO` comment. } - -/** - * Returns `true` iff `type` is `reference` or any of its subtypes (walking - * the parent chain). `null` `reference` returns `true` ("matches anything"), - * mirroring Python's `if reference is None: return True`. - * - * Unknown types (not in the hierarchy) return `false` against any - * reference except themselves. Same behavior as Python where the unknown - * type raises `KeyError` on the hierarchy lookup — we soften to `false` so - * derived user types (e.g. `Irrigation_State`) don't crash overload checks. - */ -export function isOfType(type: string, reference: string | null): boolean { - if (reference === null) return true - if (type === reference) return true - const parent = TypeHierarchy[type] - if (parent === undefined) return false // user-defined type, not in hierarchy - if (parent === null) return false // reached ANY without matching - return isOfType(parent, reference) -} - -/** - * Concrete (non-ANY-prefixed) types that are subtypes of `metaType`. - * Used by `get_standard_funtions` to expand polymorphic CSV signatures into - * concrete overload entries (e.g. `(ANY_NUM, ANY_NUM)` becomes one entry per - * `INT`, `SINT`, `DINT`, …). Catalog already pre-expanded; this stays in TS - * for completeness and future use. - */ -export function getSubTypes(metaType: string): string[] { - const out: string[] = [] - for (const typename of Object.keys(TypeHierarchy)) { - if (typename.startsWith('ANY')) continue - if (isOfType(typename, metaType)) out.push(typename) - } - return out -} diff --git a/src/backend/shared/transpilers/st-transpiler/index.ts b/src/backend/shared/transpilers/st-transpiler/index.ts index 489d493a5..2e65d3878 100644 --- a/src/backend/shared/transpilers/st-transpiler/index.ts +++ b/src/backend/shared/transpilers/st-transpiler/index.ts @@ -14,7 +14,7 @@ * `PLCProjectData` the editor's IPC delivers) — see `from-schema.ts`. */ -import { type GenerateConfigurationOptions, generateConfigurations } from './emit/configuration' +import { generateConfigurations } from './emit/configuration' import { generateDataTypes } from './emit/data-types' import { generateGraphicalPou } from './emit/pou-graphical' import { generateTextualPou } from './emit/pou-textual' @@ -22,7 +22,6 @@ import { buildPouEmissionOrder } from './pou-emission-order' import type { TranspileProject } from './types' export { fromSchemaShape, type SchemaProjectData } from './from-schema' -export type { ConfigurationExtraVariablesProvider, CtnGlobalEntry, CtnGlobalVarTuple } from './helpers/ctn-globals' export type { TranspileBody, TranspileBodyLanguage, @@ -38,8 +37,6 @@ export type { TranspileVariableType, } from './types' -export type TranspileOptions = GenerateConfigurationOptions - export interface TranspileResult { /** Concatenated Structured Text, or `null` if no POU compiled. */ programSt: string | null @@ -65,7 +62,7 @@ const TEXTUAL_LANGUAGES = new Set(['st', 'il', 'python', 'cpp']) * compile errors land in `result.errors` and the rest of the * program still emits. */ -export function transpileToSt(project: TranspileProject, options: TranspileOptions = {}): TranspileResult { +export function transpileToSt(project: TranspileProject): TranspileResult { const errors: string[] = [] const warnings: string[] = [] const pouNames: string[] = [] @@ -106,7 +103,7 @@ export function transpileToSt(project: TranspileProject, options: TranspileOptio } // Trailing CONFIGURATION block — emit IR-native. - for (const [text] of generateConfigurations(project, options)) { + for (const [text] of generateConfigurations(project)) { pieces.push(text) } diff --git a/src/backend/shared/transpilers/st-transpiler/walker/README.md b/src/backend/shared/transpilers/st-transpiler/walker/README.md index 18dba4d1d..d6f932e55 100644 --- a/src/backend/shared/transpilers/st-transpiler/walker/README.md +++ b/src/backend/shared/transpilers/st-transpiler/walker/README.md @@ -1,6 +1,6 @@ # walker -React Flow → Structured Text walker. Consumes the **React Flow** +React Flow → Structured Text walker. Consumes the **React Flow** body shape (the exact format `openplc-editor` / `openplc-web` store in `pous/s/.{ld,fbd}` on disk) and emits the body bytes that go between `END_VAR` and `END_PROGRAM` — byte-identical to the @@ -13,16 +13,16 @@ VAR sections, the body bytes returned here, and the closing ## Files -- `ld.ts` — `emitLdBody(body: RFBody): EmitResult`. Handles both +- `ld.ts` — `emitLdBody(body: RFBody): EmitResult`. Handles both LD and FBD bodies (FBD is a strict subset of LD's vocabulary). - `fbd.ts` — thin adapter that wraps `{ rung }` into the LD `{ rungs: [rung] }` shape and delegates to `emitLdBody`. - `narrow.ts` — type-safe accessors for the loosely-typed - `RFNode.data: Record` payloads. Each `as*Data` + `RFNode.data: Record` payloads. Each `as*Data` helper returns `null` when the payload doesn't match the expected shape, letting the walker decide whether to warn or skip. - `types.ts` — minimal React Flow types (`RFBody`, `RFRung`, - `RFNode`, `RFEdge`). Loose enough that the schema boundary in + `RFNode`, `RFEdge`). Loose enough that the schema boundary in `../from-schema.ts` can project the editor's Zod-inferred shapes without typecasts. @@ -31,13 +31,13 @@ VAR sections, the body bytes returned here, and the closing The walker's emission steps (contact/coil dispatch, block-call emission, parallel-branch factoring) build up `PathNode` trees, then hand them to `../core/path-tree.ts` for normalisation and chunk -serialisation. Contact / coil modifiers (negated, set, reset, edge +serialisation. Contact / coil modifiers (negated, set, reset, edge triggers) flow through `../core/modifiers.ts:extractModifier`. ## Source of truth `xml2st`'s python `PLCGenerator.py` is the canonical reference for -every emission rule. Any divergence between this walker's output +every emission rule. Any divergence between this walker's output and the oracle is a walker bug, never an oracle bug — see the fixture corpus under `xml2st/fixtures/` and the test harness under `xml2st/shared-backend/transpilers/generate-st-from-react-flow/tests/`