Skip to Content

Implementing language plugin for syntax highlighter evolved

In one of his posts  Maciej Anisierowicz recommended SyntaxHighlighter Evolved plugin, and indeed it’s a great one if you want to highlight one of popular languages syntax. In case of niche languages, like ones defined in IEC 61131-3, one has to implement his own language extension for SyntaxHighlighter.

IEC 61131-3 Structured Text

Due to #dajsiepoznac project there will appear Structured Text samples. Even though it resembles Pascal on which is based, included pascal syntax falls short.

(* Note samples based on ones available at http://infosys.beckhoff.com/ *)

TYPE TSimpleStruct :
STRUCT
	lrealVal: LREAL := 1.23;
	dintVal1: DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
	intVal : INT:=1200;
	dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4;
	boolVal: BOOL := FALSE;
	byteVal: BYTE:=10;
	stringVal : STRING(5) := 'hallo';
	simpleStruct1: TSimpleStruct;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR

  REAL32_1 AT %MB0 : REAL;  (* 1 *)
  REAL32_2 AT %MB4 : REAL;  (* 2 *)
  REAL32_3 AT %MB8 : REAL;  (* 3 *)
  REAL32_4 AT %MB12: REAL;  (* 4 *)
  REAL32_5 AT %MB16: REAL;  (* 5 *)

  PLCVar : ARRAY [0..99] OF INT;
  Index: BYTE;
  text : STRING[30] := 'hello';
  Time1:TIME := T#21h33m23s231ms;
  Time2:TIME := T#21H;
  Time3:TIME := T#33M21MS;
  DateTime1:DT:=DT#1993-06-12-15:36:55.40;
  tod1:TOD:=TIME_OF_DAY#15:36:55.40;
  Date1:DATE:=DATE#1993-06-12;
  Bool1:BOOL := FALSE;
  int1:INT := 30000;
  dint1:DINT:=125000;
  usint1:USINT:=200;
  real1:REAL:= 1.2;
  lreal1:LREAL:=3.5;

  str1:STRING := 'this is a test string';
  str2:STRING(5) := 'hallo';

  complexStruct1 : TComplexStruct;

END_VAR

FOR Index := 0 TO 99 DO
  PLCVar[Index] := 3500 + INDEX;
END_FOR
(* Note samples based on ones available at http://infosys.beckhoff.com/ *)

TYPE TSimpleStruct :
STRUCT
	lrealVal: LREAL := 1.23;
	dintVal1: DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
	intVal : INT:=1200;
	dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4;
	boolVal: BOOL := FALSE;
	byteVal: BYTE:=10;
	stringVal : STRING(5) := 'hallo';
	simpleStruct1: TSimpleStruct;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR

  REAL32_1 AT %MB0 : REAL;  (* 1 *)
  REAL32_2 AT %MB4 : REAL;  (* 2 *)
  REAL32_3 AT %MB8 : REAL;  (* 3 *)
  REAL32_4 AT %MB12: REAL;  (* 4 *)
  REAL32_5 AT %MB16: REAL;  (* 5 *)

  PLCVar : ARRAY [0..99] OF INT;
  Index: BYTE;
  text : STRING[30] := 'hello';
  Time1:TIME := T#21h33m23s231ms;
  Time2:TIME := T#21H;
  Time3:TIME := T#33M21MS;
  DateTime1:DT:=DT#1993-06-12-15:36:55.40;
  tod1:TOD:=TIME_OF_DAY#15:36:55.40;
  Date1:DATE:=DATE#1993-06-12;
  Bool1:BOOL := FALSE;
  int1:INT := 30000;
  dint1:DINT:=125000;
  usint1:USINT:=200;
  real1:REAL:= 1.2;
  lreal1:LREAL:=3.5;

  str1:STRING := 'this is a test string';
  str2:STRING(5) := 'hallo';

  complexStruct1 : TComplexStruct;

END_VAR

FOR Index := 0 TO 99 DO
  PLCVar[Index] := 3500 + INDEX;
END_FOR

Extending syntax highlighter

WP plugin SyntaxHighlighter Evolved is based on SyntaxHighlighter JavaScript package by Alex Gorbatchev, which is OSS available on GitHub. Thus we’ll be developing plugin for both. Best way to start is to find a supported language that most resembles yours.

JS extension

var BrushBase = require('brush-base');
var regexLib = require('syntaxhighlighter-regex').commonRegExp;

function Brush() {
  var keywords =  'ABS ACOS ACTION ADD AND ANDN ANY ANY_BIT ANY_DATE ANY_INT ANY_NUM ANY_REAL ARRAY ASIN AT ATAN ' +
                  'BOOL BY BYTE ' +
                  'CAL CALC CALCN CASE CD CDT CLK CONCAT CONFIGURATION CONSTANT COS CTD CTU CTUD CU CV ' +
                  'D DATE DATE_AND_TIME DELETE DINT DIV DO DS DT DWORD '+
                  'ELSE ELSIF END_ACTION END_CASE END_CONFIGURATION END_FOR END_FUNCTION END_FUNCTION_BLOCK END_IF END_PROGRAM END_REPEAT END_RESOURCE END_STEP END_STRUCT END_TRANSITION END_TYPE END_VAR END_WHILE EN ENO EQ ET EXIT EXP EXPT '+
                  'FALSE F_EDGE F_TRIG FIND FOR FROM FUNCTION FUNCTION_BLOCK '+
                  'GE GT'+
                  'IF IN INITIAL_STEP INSERT INT INTERVAL '+
                  'JMP JMPC JMPCN '+
                  'L LD LDN LE LEFT LEN LIMIT LINT LN LOG LREAL LT LWORD '+
                  'MAX MID MIN MOD MOVE MUL MUX '+
                  'N NE NEG NOT '+
                  'OF ON OR OEN '+
                  'P PRIORITY PROGRAM PT PV '+
                  'Q Q1 QU QD '+
                  'R R1 R_TRIG READ_ONLY READ_WRITE REAL RELEASE REPEAT REPLACE RESOURCE RET RETAIN RETC RTCN RETURN RIGHT ROL ROR RS RTC R_EDGE '+
                  'S S1 SD SEL SEMA SHL SHR SIN SINGLE SINT SL SQRT SR ST STEP STN STRING STRUCT SUB '+
                  'TAN TASK THEN TIME TIME_OF_DAY TO TOD TOF TON TP TRANSITION TRUE TYPE '+
                  'UDINT UINT ULINT UNTIL USINT '+
                  'VAR VAR_ACCESS VAR_EXTERNAL VAR_GLOBAL VAR_INPUT VAR_IN_OUT VAR_OUTPUT '+
                  'WHILE WITH WORD '+
                  'XOR XORN';

  this.regexList = [
    {
      //time literal
      regex: /(T|t|TIME|time)(?=.*([hms]|[HMS]))#(\d+(h|H))?(\d+(m|M))?(\d+(s|s))?(\d+(ms|MS))?/g,
      css: 'color2'
    },
    {
      // date and time literal
      regex: /(DT|dt|date_and_time|DATE_AND_TIME)#\d{4}-\d{2}-\d{2}-\d{2}:\d{2}:\d{2}\.\d{2}/g,
      css: 'color2'
    },
    {
      // time of day literal
      regex: /(TOD|tod|time_of_day|TIME_OF_DAY)#\d+:\d+(:\d+)?((\.\d+)|(\.?))/g,
      css: 'color2'
    },
    {
      //date literal
      regex: /(D|d|DATE|date)#\d{4}-\d{2}-\d{2}/g,
      css: 'color2'
    },
    {
      //direct memory adressing
      regex: /%[A-Z]{1,2}\d+(\.\d+)*/g,
      css: 'color2'
    },
    {
      //multiline comment (* *)
      regex: /\(\*[\s\S]*?\*\)/gm,
      css: 'comments'
    },
    {
      //string literal 'myvalue'
      regex: regexLib.singleQuotedString,
      css: 'string'
    },
    {
      //number integers, floating point with dot or exponential
      regex: /\b\d+([\.eE]\d+)?\b/g,
      css: 'value'
    },
    {
      //keywords
      regex: new RegExp(this.getKeywords(keywords), 'gmi'),
      css: 'keyword'
    }];
};

Brush.prototype = new BrushBase();
Brush.aliases = ['structuredtext', 'ST', 'IEC61131','st','iec61131'];
module.exports = Brush;

on lines 5-26 you may find keywords defined by IEC 61131-3 standard, later used to construct regex, in order to be highlighted, at line 76  which are later used at line. Beside using predefined single quoted string (line 61), we define regexes for language specific futures like TIME/DATE and direct memory addressing.

Make it work with WordPress

To use it at WordPress based site, we need to make it a plugin, extending SyntaxHighlighter Evolved. Separate plugin, as we don’t want to lose our changes on update of SH.

SyntaxHighlighter.brushes.IEC61131 = function()

  //keywords as specified by IEC 61131-3
  var keywords =  'ABS ACOS ACTION ADD AND ANDN ANY ANY_BIT ANY_DATE ANY_INT ANY_NUM ANY_REAL ARRAY ASIN AT ATAN ';
  /*rest of keywords*/
  this.regexList = [
    {
      //time literal
      regex: /(T|t|TIME|time)(?=.*([hms]|[HMS]))#(\d+(h|H))?(\d+(m|M))?(\d+(s|s))?(\d+(ms|MS))?/g,
      css: 'color2'
    },
    /* other custom regexes */
    {
      //string literal 'myvalue'
      regex: SyntaxHighlighter.regexLib.singleQuotedString,
      css: 'string'
    }
  ];
};
SyntaxHighlighter.brushes.IEC61131.prototype     = new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.IEC61131.aliases = ['structuredtext', 'ST', 'IEC61131', 'iec61131', 'st'];

Highlighted are main differences, note that we’re not using Brush, as before but SyntaxHighlighter.brushes.IEC61131 instead.

<?php
/*put the usual WordPress plugin info here*/

//hook to init
add_action( 'init', 'syntaxhighlighter_IEC61131_regscript' );

// register brushe
function syntaxhighlighter_IEC61131_regscript() {
	wp_register_script( 'syntaxhighlighter-brush-iec61131', plugins_url( 'IEC61131-brush.js', __FILE__ ), array('syntaxhighlighter-core'), '1.2.3' );
}

// add filter for aliases
add_filter( 'syntaxhighlighter_brushes', 'syntaxhighlighter_IEC61131_brush' );

function syntaxhighlighter_IEC61131_brush($brushes) {
	$brushes['iec61131'] = 'iec61131';
   	$brushes['IEC61131'] = $brushes['iec61131'];
   	$brushes['structuredtext'] = $brushes['iec61131'];
   	$brushes['ST'] = $brushes['iec61131'];
   	$brushes['st'] = $brushes['iec61131'];
   
   return $brushes;
}
?>

Things we do in our litle plugin:

  • on line 5 we hook to WordPress init in order to
  • register our js script and it’s dependencies on line 9
  • on line 13 we register to SyntaxHighlighter Evolved filter for brushes in order to make it aware of our existance
  • finally we register our aliases (remember last line of our js plugin?) so that we could use them as shortcodes

And that’s pretty it when it comes to implement custom syntax plugin. It takes less time to create plugin than to write about it…

 

PS. I always wanted to release something under WTF PL 🙂

F# ADS Toolkit

Blog registered for “Daj się poznać” contest so I can start countdown until it begins as well as work on project.

To begin with let me explain what I’ll be working on and why I find it useful.

Background

I won’t go into detail what ADS (Automation Device System) is, you can read that at Infosys. What matters is, that it is a protocol that allows you to communicate with TwinCAT. Note that I’ll only focus on access via symbolic variable names.

If we were to read/write, let’s say uint16 variable, it would look like

use client = new TcAdsClient()

//connecto do ADS device
client.Connect(amsNetId,801)

//create variable handle
let myuintHandle = client.CreateVariableHandle ".myuint"

//read value and cast to short
let myuintValue = client.ReadAny(myuintHandle, Operators.typedefof<uint16>) :?> uint16

//write incremented value
client.WriteAny(myuintHandle,myuintValue + 1us)

(* some other logic *)

//remove handle
client.DeleteVariableHandle myuintHandle

//dispose client
client.Dispose()

If you look at highlighted lines, you’ll notice that this code is prone to errors. First of all we pass symbolic name as a string, if we make typo, in runtime we end up with “0x710 symbol not found” exception. Sure we can make it more convenient by wrapping it into generic read methods, but still we have to pay a lot of attention to naming and types. Yep, type names in TwinCAT follow IEC61131-3 specification . Our .NET uint16 is WORD/UINT in TwinCAT, for other types refer to Infosys.

Another tricky thing we can encounter are structures. Sure we can marshal whole structures, but as always, layout is important.

STRUCT
	lrealVal: LREAL := 1.23;
	dintVal1: DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
	intVal : INT:=1200;
	dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4;
	boolVal: BOOL := FALSE;
	byteVal: BYTE:=10;
	stringVal : STRING(5) := 'hallo';
	simpleStruct1: TSimpleStruct;
END_STRUCT
END_TYPE
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type SimpleStruct =
  struct
    val lrealVal: double
    val dintVal1: int
  end

[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type ComplexStruct =
  struct
    val intVal: int16
    [<MarshalAs(UnmanagedType.ByValArray, SizeConst=4)>]
    val dintArr: int array
    [<MarshalAs(UnmanagedType.I1)>]
    val boolVal: bool
    val byteVal: byte
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)>]
    val stringVal: string
    val simpleStruct1: SimpleStruct
  end

Beside basically repeating same code on different sides, imagine situation where PLC developer slightly refactors his code and changes order of fields, no exceptions thrown but invalid values as a result.

Notice highlighted lines, when declaring .NET counterparts, you have to declare length 1 byte longer then TwinCAT due to string null terminator. That’s yet another thing you have to keep in mind when doing things manually.

I could go on with things we can mess up and end up debugging if we do things manually, but above are main problems I aim to address with this project.

First milestone – Convenient syntax and base classes

As a first milestone we should end up with set of base classes that we can extend manually to suit particular project we work on, and use it in convenient way

Second milestone – Type provider based on *.tpy file

Second milestone is to generate ADS client based on *.tpy file, it is generated when TwinCAT project is compiled, at least that’s default setting. It contains custom types definitions and variables. Format is XML based.

About me

My name is Paweł Paluch. I run my own self-owned company developing software mostly for Automotive and Industrial Automation industries.

This blog has started as part of “Daj się poznać” contest, and I hope it will last longer than that.

Background

I worked for few companies as a webdev in PHP and Python before I started own business, in which primary development platform is .NET.

Afterhours

I’m a big lover of books, my private facebook wall is often spammed with Goodreads notifications. Also motorcyclist, at a time of writing of this post working on camshaft installation in ‘84 Magna V30.

Other

You can find me on facebook and linekdin.

 

 

PS do not confuse anarch with anarchist