From 253fe29bb97e27af88118ffd3a0bdc0793d9f6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20H=C3=B6glinger?= Date: Sun, 26 Apr 2026 20:05:38 +0200 Subject: [PATCH] Add documentation, fix README.md --- README.md | 10 +-- doc/01_syntax.md | 76 +++++++++++++++++ doc/02_functions.md | 204 ++++++++++++++++++++++++++++++++++++++++++++ doc/03_bangs.md | 53 ++++++++++++ 4 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 doc/01_syntax.md create mode 100644 doc/02_functions.md create mode 100644 doc/03_bangs.md diff --git a/README.md b/README.md index fe22a99..04b7c9a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ such as modules in an ESI file. ## Language Specification -The XNeedle language consists of three distinct parts. +The XN language consists of three distinct parts. 1. Expressions @@ -59,16 +59,16 @@ This example adds, removes and swaps nodes. $("RxPdo/Index[.="#x1600"]/..") { # Modify name - $(Name) { text("FSoE Outputs"); } + $("Name") { text("FSoE Outputs"); } # Select the Entry/Index #x7000 and change it to #x9001 - $(Entry/Index[.="#x7000"]) { text("#x9001"); } + $("Entry/Index[.='#x7000']") { text("#x9001"); } # Select the Entry with Index #x7001 and SubIndex 4, remove it - $(Entry/Index[.="#x7001"]/../SubIndex[.="4"]/..) { remove(); } + $("Entry/Index[.='#x7001']/../SubIndex[.="4"]/..") { remove(); } # Select the 3rd Entry - $(Entry[3]) + $("Entry[3]") { # Add a tag before the above selection !before("Entry") diff --git a/doc/01_syntax.md b/doc/01_syntax.md new file mode 100644 index 0000000..b217e87 --- /dev/null +++ b/doc/01_syntax.md @@ -0,0 +1,76 @@ +# XN Syntax + +## Comments + +Comments are single line only prefixed with the pound sign ('#'). + +## Rules + +Rules consists of two parts, a selector, and a block containing statements. +A selector is an XPath string surrounded with braces prefixed with a dollar sign. +The XPath selects zero or more document nodes which will serve as the context for the statement block. + +## Bangs + +Bangs are function calls combined with a statement block. +They are prefixed with an exclaimation mark. +A bang function always produces context nodes for the statement block. + +## Statement Block +A statement block are multiple statements terminated with a semicolon, +surrounded by curly braces. + +## Statements + +A statement can be an expression, a function call, a rule or a bang. +To note is that a nested rule will use its parents context to refine the selection further. + +## Expressions + +An expression can consist of function calls, operators, strings or numbers. + +Operators supported: + +| Operator | Function | +|----------|----------------------| +| + | addition | +| - | subtraction | +| * | multiplication | +| / | division | +| ** | power | +| & | bitwise and | +| | | bitwise or | +| ^ | bitwise exclusive or | +| .. | string concatenate | + +## Example + +``` +# Select the RxPdo with Index 0x1600 +$("RxPdo/Index[.="#x1600"]/..") +{ + # Function call + swap("Entry[3]", "Entry[4]"); + + # Of that RxPdo, select the 3rd Entry via a nested rule + $("Entry[3]") + { + # This bang has "RxPdo/Index[.="#x1600"]/../Entry[3]" as its context + !before("Entry") + { + # Function call contains an expression, which evaluates here to "!#xFFFF" + !add("Index") { text("!"..hexlit(2**16-1)); }; + !add("SubIndex") { text("3"); } + !add("BitLen") { text("256"); } + !add("Name") { text("Test"); } + !add("DataType") { text("UINT"); } + } + } +} + +# One .xn can contain multiple rules +$("//Module[@Crc32]") +{ + attr("Crc32", hexlit(crc32())); +}; +``` \ No newline at end of file diff --git a/doc/02_functions.md b/doc/02_functions.md new file mode 100644 index 0000000..3ce9cf6 --- /dev/null +++ b/doc/02_functions.md @@ -0,0 +1,204 @@ +# Function Reference + +## `num(value:string, numbase:number = 10):number` + +Converts a string to a number in respect of the input base. + +Example: +```xn +# Add 1 to all LcId +$("//Name[@LcId]") +{ + attr("LcId", str(num(attr("LcId")) + 1)); +}; +``` + +## `str(value:number, numbase:number = 10):string` + +Converts a number to a string in any base. + +Example: +```xn +# Set ProfileNo to "#o377" +$("//ProfileNo") +{ + text("#o"..str(2**8-1, 8)); +}; + +``` + +## `hexlit(value:number):string` + +Produces a hex literal from an integer with lowercase digits. + +Example: +```xn +# Set all module Crc32 to #x00c0ffee +$("//Module[@Crc32]") +{ + attr("Crc32", hexlit(0xC0FFEE)); +}; +``` + + +## `parselit(literal:string, base:number = 16):string` + +Parses a literal number to an integer. + +Example: +```xn +# Increase all RevisionNo by 1 +$("//Device/Type[@RevisionNo]") +{ + attr("RevisionNo", hexlit(parselit(attr("RevisionNo")) + 1)); +}; +``` + +## `lower(text:string):string` + +Converts a string to all lower case text. + +Example: +```xn +# Convert all device names to lower case +$("//Device/Type") +{ + $("Name") { cdata(lower(cdata())); }; +} +``` + +## `upper(text:string):string` + +Converts a string to all upper case text. + +Example: +```xn +# Convert all Name tag text in Index nodes from RxPdo to upper case. +$("//RxPdo/Index/Name") +{ + $("Name") { text(upper(text())); }; +} +``` + +## `crc32(poly:number = 0x04C11DB7, init:number = 0, xorOut:number = 0, reflected:bool = false):number` + +Calculates the CRC32 checksum from the rendered document starting after the tag close of the context +node up to and including the closing tag. + +Example: + +For this example `[IND]` is whitespace for indent, +`[CR]` for carriage return and `[LF]` for line feed. + +Rendered document: +```xml +[CR][LF] +[IND][CR][LF] +[IND][IND] +[IND][IND]Text +[IND] + + +XN +```xn +# Calculate CRC of node and store in "crc" attribute as uppercase hexlit +$("//Node") { attr("crc", upper(hexlit(crc32())); } +``` + +Digested fragment: +```txt +[CR][LF] +[IND][IND] +[IND][IND]Text +[IND] +``` + +## `text():string` / `text(value:string):string` + +Get or set the text of the context node. + +Example: +```xn +# Prefix all Name tag text in Index nodes from TxPdo with "Fault". +$("//TxPdo/Index/Name") +{ + $("Name") { text("Fault" .. text()); }; +} +``` + +## `cdata():string` / `cdata(value:string):string` + +Get or set the CDATA of the context node. + +Example: +```xn +# For Device with this ProductCode and Revision, modify its CDATA text +$("//Device/Type[@ProductCode='#x0B583052' and @RevisionNo='#x00110000']/..") +{ + $("Name[@LcId='1033']") { cdata("LE2904, 4 Ch. Danger Output 24V, 0.5A, TwinUNSAFE"); }; +} +``` + +## `attr(name:string):string` / `attr(name:string, value:string):string` + +Get or set an attribute of the context node by name. + +Example: +```xn +# For Device with this ProductCode and Revision, set the revision to #xc0deaffe +$("//Device/Type[@ProductCode='#x0B583052' and @RevisionNo='#x00110000']") +{ + attr("RevisionNo", hexlit(0xC0DEAFFE)); +} +``` + +## `swap(xpath_a:string, xpath_b:string):string = ""` + +Swaps two nodes by XPath in the current cotext node. + +Example: +```xn +# Swap first and second Entry in RxPdo of Index 0x1600 +$("//RxPdo/Index[.='#x1600']/..") { swap("Entry[1]", "Entry[2]"); }; + +``` + +## `remove():string = ""` + +Removes the context node from the document. + +Example: +```xn +# Remove all modules +$("//Module") { remove(); }; +``` + +## `rename(name:string):string = ""` + +Renames the context node. + +Example: +```xn +# Rename all Device nodes to DeviceX +$("//Device") { rename("DeviceX"); }; +``` + +## `rename(xpath:string, name:string):string = ""` + +Renames any nodes matching the XPath in the context node. + +Example: +```xn +# Rename all Device nodes Name tags to Label +$("//Device") { rename("Name", "Label"); }; +``` + +## `require(message:string):string = ""` + +If the context node is not present, emit an error message. + +Example: +```xn +# Emit an error if no Module tags are in the document +$("//Module") { require("No Module definitions!"); }; +``` diff --git a/doc/03_bangs.md b/doc/03_bangs.md new file mode 100644 index 0000000..def1648 --- /dev/null +++ b/doc/03_bangs.md @@ -0,0 +1,53 @@ +# Bang Reference + +## `!add(name:string)` + +Appends a new node into the context. + +Example: +```xn +# Add 1 to all LcId +$("//RxPdo/Index[.="#x1600"]/..") +{ + # Add a new Entry with attribute "added" set to "true" + !add("Entry") + { + attr("added", "true"); + } +}; +``` + +## `!before(name:string)` + +Appends a new node before the context. + +Example: +```xn +# Add 1 to all LcId +$("//RxPdo/Index[.="#x1600"]/..") +{ + # Add a new TxPdo with attribute "added" set to "true" before the above node selection + !before("TxPdo") + { + attr("added", "true"); + } +}; +``` + +## `!after(name:string)` + +Appends a new node after the context. + +Example: +```xn +# Add 1 to all LcId +$("//RxPdo/Index[.="#x1600"]/..") +{ + # Add a new TxPdo with attribute "added" set to "true" after the above node selection + !after("TxPdo") + { + attr("added", "true"); + } +}; +``` +