Ladder logic
About ladder logic diagrams
Ladder logic is the graphical programming language used to describe control programs in Programmable Logic Controllers (PLCs) — the industrial computers that run factory automation, HVAC systems, conveyor lines, and process equipment worldwide. The name comes from the diagram's appearance: two vertical power rails (rails) connected by horizontal rungs, each rung expressing one condition-to-action rule. Electricians and controls engineers adopted it because it mirrors the relay-contact schematics they already knew.
Schematex follows IEC 61131-3:2013, the international standard for PLC programming languages, with Allen-Bradley (Rockwell) tag-address-name naming conventions common in North American industrial practice. This page documents what the parser accepts today.
1. Your first ladder diagram
The smallest useful ladder program: one rung, two contacts, one coil.
Four rules cover 80% of usage:
- Start with
ladder, optionally followed by a quoted title. - Each rung begins with
rung N "optional comment":on its own line. The trailing colon is optional. - Elements are listed one per line, indented under the rung — left to right means series (AND logic).
- A
parallel:/branch:block introduces OR logic. Every branch holds its own element list.
Comments may start with
#,//, or Mermaid-style%%on their own line.
2. Contacts
Contacts represent input conditions — they pass power when the associated bit matches the contact type.
| Type | Name | Passes power when… |
|---|---|---|
XIC | Examine If Closed | Tag bit = 1 (normally open) |
XIO | Examine If Open | Tag bit = 0 (normally closed) |
ONS | One-Shot Rising | Tag transitions 0 → 1 (rising edge, one scan) |
OSF | One-Shot Falling | Tag transitions 1 → 0 (falling edge, one scan) |
Syntax:
XIC(tag)
XIC(tag, "address")
XIC(tag, "address", name="Description")
XIC(tag, address="address", name="Description")tag— required. The PLC tag name (displayed below the contact symbol)."address"— optional positional second argument. The I/O address (e.g."IN 1.0","BIT 3.1").name="…"— optional key-value. Human-readable description (displayed above the symbol).
3. Coils
Coils represent output actions — they act on a tag bit when the rung has power flow.
| Type | Name | Effect on tag bit |
|---|---|---|
OTE | Output Energize | Sets bit = 1 while rung is true; clears to 0 when rung is false |
OTL | Output Latch | Sets bit = 1; retains even after rung goes false (latches) |
OTU | Output Unlatch | Clears bit = 0; retains even after rung goes false |
OTN | Output Negate | Sets bit = 0 while rung is true; sets to 1 when rung is false |
RES | Reset | Rockwell / Allen-Bradley counter or timer reset coil |
Syntax: identical to contacts — OTE(tag), OTE(tag, "address"), OTE(tag, "address", name="…").
OTL and OTU are used in pairs to build Set/Reset flip-flops. The last rung to write wins.
4. Function blocks
Function blocks perform timer, counter, math, and comparison operations. They appear inline in a rung and have keyword parameters after the mandatory tag argument.
4.1 Timers
| Type | Name | Key parameters |
|---|---|---|
TON | Timer On-Delay | PT= preset time in milliseconds |
TOFF | Timer Off-Delay | PT= preset time in milliseconds |
TP | Timer Pulse | PT= preset time in milliseconds |
TON(timer_tag, PT=5000)The timer tag stores elapsed time. The timer's Q bit (done output) is accessed by tag name in downstream contacts.
4.2 Counters
| Type | Name | Key parameters |
|---|---|---|
CTU | Count Up | PV= preset value (integer) |
CTD | Count Down | PV= preset value |
CTUD | Count Up/Down | PV= preset value |
CTU(cycle_counter, PV=100)4.3 Math
| Type | Operation |
|---|---|
ADD | Add |
SUB | Subtract |
MUL | Multiply |
DIV | Divide |
MOV | Move (copy) |
ADD(result_tag, IN1=setpoint, IN2=offset)
MOV(dest_tag, IN1=source_tag)4.4 Comparisons
| Type | Meaning |
|---|---|
EQU | Equal |
NEQ | Not equal |
GRT | Greater than |
LES | Less than |
GEQ | Greater than or equal |
LEQ | Less than or equal |
EQU(compare_tag, IN1=speed_actual, IN2=speed_setpoint)5. Parallel branches
A parallel: block introduces OR logic — the rung has power if any branch conducts. Each branch is a branch: sub-block with its elements indented below it.
parallel:
branch:
XIC(LOCAL_START)
branch:
XIC(REMOTE_START)Branches in a parallel: are evaluated simultaneously. The block closes when indentation returns to the level before parallel:.
Rules:
parallel:must appear inside a rung.branch:must appear inside aparallel:— using it alone throwsLadderParseError.- Each branch holds one or more elements.
- Elements after the
parallel:block are series with it (AND logic).
6. Labels & comments
- Title:
ladder "Motor Control"— first line only, quoted string. - Rung number: required integer after
rung. - Rung comment: optional quoted string after the rung number, before the optional colon:
rung 3 "Run indicator":orrung 3 "Run indicator". - Tag: first argument inside the parentheses — displayed below the symbol.
- Address: second positional argument (quoted):
XIC(START_PB, "IN 1.0"). - Name:
name="…"keyword argument — human-readable description displayed above the symbol. - Line comments:
#,//, or%%at the start of a line (after leading whitespace). The same markers also start trailing comments.
7. Reserved words & escaping
Reserved at line start (case-insensitive): ladder, rung, parallel:, branch:.
Element names are all uppercase ASCII: XIC, XIO, ONS, OSF, OTE, OTL, OTU, OTN, TON, TOFF, TP, CTU, CTD, CTUD, ADD, SUB, MUL, DIV, MOV, EQU, NEQ, GRT, LES, GEQ, LEQ.
Tag IDs — must match [A-Z][A-Z0-9_]* (the parser matches [A-Z][A-Z0-9_]* for the element name prefix). Lowercase tags are accepted inside the parentheses.
Quoted strings in address or name arguments must use double quotes "…".
8. Common mistakes
| You wrote | Parser says | Fix |
|---|---|---|
rung 1 (no colon) | Parsed correctly | The trailing colon is optional |
ONF(TAG) | LadderParseError: unknown element type "ONF" | Falling-edge contact is OSF, not ONF |
parallel: without branch: | Empty parallel block — rung has no element | Add at least one branch: inside the parallel: |
branch: before parallel: | LadderParseError: branch: without parallel: | Always open parallel: first |
OTE() — no tag | LadderParseError: element missing tag | Tag is required: OTE(MY_TAG) |
var StartBtn: bool (variable declaration) | LadderParseError: invalid element syntax | No variable declarations — tags are used directly |
Empty rung (no elements after rung N:) | LadderParseError: Rung N: empty rung | Add at least one element to each rung |
TON(T1, T#5s) | LadderParseError: invalid element syntax (T# not a valid number) | Use milliseconds integer: TON(T1, PT=5000) |
9. Grammar (EBNF)
document = header NEWLINE rung+
header = "ladder" ( WS quoted-string )? NEWLINE
quoted-string = '"' any-char-but-quote* '"'
rung = "rung" WS integer ( WS quoted-string )? ":"? NEWLINE
element+
element = contact-line
| coil-line
| fb-line
| parallel-block
contact-line = contact-type "(" tag ( "," arg )* ")" NEWLINE
contact-type = "XIC" | "XIO" | "ONS" | "OSF"
coil-line = coil-type "(" tag ( "," arg )* ")" NEWLINE
coil-type = "OTE" | "OTL" | "OTU" | "OTN" | "RES"
fb-line = fb-type "(" tag ( "," arg )* ")" NEWLINE
fb-type = "TON" | "TOFF" | "TP"
| "CTU" | "CTD" | "CTUD"
| "ADD" | "SUB" | "MUL" | "DIV" | "MOV"
| "EQU" | "NEQ" | "GRT" | "LES" | "GEQ" | "LEQ"
arg = quoted-string // positional (address)
| key "=" quoted-string // keyword (e.g. name="…")
| key "=" number // keyword (e.g. PT=5000)
parallel-block = INDENT≥2 "parallel:" NEWLINE
( INDENT branch-block )+
branch-block = "branch:" NEWLINE
( INDENT element )+
tag = [A-Za-z][A-Za-z0-9_]*
key = [A-Za-z][A-Za-z0-9_]*
integer = [0-9]+
number = [0-9]+ ( "." [0-9]+ )?
comment = ( "#" | "//" | "%%" ) any NEWLINEAuthoritative source: src/diagrams/ladder/parser.ts. If this diverges from the parser, the parser wins — please open an issue.
10. Standard compliance
Schematex ladder logic follows IEC 61131-3:2013 Part 3 for the Ladder Diagram language and uses Allen-Bradley (Rockwell) tag-address-name conventions common in North American PLC practice.
What is implemented today:
- ✅ Four contact types: XIC (NO), XIO (NC), ONS (rising edge), OSF (falling edge)
- ✅ Five coil types: OTE (output), OTL (latch/set), OTU (unlatch/reset), OTN (negate), RES (counter/timer reset)
- ✅ Three timer function blocks: TON, TOFF, TP with millisecond
PT=parameter - ✅ Three counter function blocks: CTU, CTD, CTUD with integer
PV=parameter - ✅ Math function blocks: ADD, SUB, MUL, DIV, MOV
- ✅ Comparison function blocks: EQU, NEQ, GRT, LES, GEQ, LEQ
- ✅ Parallel / branch blocks for OR logic
- ✅ Tag, address, and name annotations per Allen-Bradley Studio 5000 conventions
- ⏳ Retentive timer (RTO) — in the standard; not yet in the parser
- ⏳ Jump (JMP) / Label (LBL) instructions
- ⏳ Master Control Reset (MCR) zone
- ⏳ Immediate I/O instructions (IIN, IOT)
- ⏳ Structured Text or Function Block Diagram elements embedded in rungs
References:
- IEC 61131-3:2013 — Programmable controllers, Part 3: Programming languages
- NEMA ICS 1-2009 — General Standards for Industrial Control and Systems
- Rockwell Automation Studio 5000 Logix Designer — Ladder Diagram Programming Manual
11. Related examples
12. Roadmap
Planned — not yet parseable. Do not use these in generated DSL today; the parser will reject or ignore them.
- Retentive Timer On (RTO) — timer that holds elapsed time through power loss; requires a separate reset contact.
- Jump (JMP) / Label (LBL) — branch to a labeled rung to skip logic conditionally; used in large programs for performance.
- Master Control Reset (MCR) zone — a bracketed zone that de-energizes all non-retentive outputs when the MCR input is false.
- Structured text in rungs — inline expression evaluation (e.g.,
CALC(result = a * b + c)). - Immediate I/O (IIN / IOT) — force I/O scan mid-program for time-critical control.
Track in the GitHub issues if you need any of these sooner.