Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lexer for IEC 61131-3 Structured Text #2027

Merged
merged 1 commit into from
May 13, 2024
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
21 changes: 21 additions & 0 deletions lib/rouge/demos/iecst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// first-order lag derivative term
(*
Parameter K must be set to T / (T + T1)
*)
FUNCTION_BLOCK DT2

VAR_INPUT
IN : REAL;
K : REAL;
END_VAR
VAR_OUTPUT
Q : REAL;
END_VAR
VAR
H : REAL := 0.0;
END_VAR

BEGIN
Q := IN - H;
H := H + K * Q;
END_FUNCTION_BLOCK
7 changes: 6 additions & 1 deletion lib/rouge/guessers/disambiguation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,17 @@ def match?(filename)

Puppet
end

disambiguate '*.p' do
next Prolog if contains?(':-')
next Prolog if matches?(/\A\w+(\(\w+\,\s*\w+\))*\./)
next OpenEdge
end

disambiguate '*.st' do
next IecST if matches?(/^\s*END_/i)
next Smalltalk
end
end
end
end
92 changes: 92 additions & 0 deletions lib/rouge/lexers/iecst.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class IecST < RegexLexer
tag 'iecst'
title "IEC 61131-3 Structured Text"
desc 'Structured text is a programming language for PLCs (programmable logic controllers).'
filenames '*.awl', '*.scl', '*.st'

mimetypes 'text/x-iecst'

def self.keywords
blocks = %w(
PROGRAM CONFIGURATION INITIAL_STEP INTERFACE FUNCTION_BLOCK FUNCTION ACTION TRANSITION
TYPE STRUCT STEP NAMESPACE LIBRARY CHANNEL FOLDER RESOURCE
VAR_ACCESS VAR_CONFIG VAR_EXTERNAL VAR_GLOBAL VAR_INPUT VAR_IN_OUT VAR_OUTPUT VAR_TEMP VAR
CONST METHOD PROPERTY
CASE FOR IF REPEAT WHILE
)
@keywords ||= Set.new %w(
AT BEGIN BY CONSTANT CONTINUE DO ELSE ELSIF EXIT EXTENDS FROM GET GOTO IMPLEMENTS JMP
NON_RETAIN OF PRIVATE PROTECTED PUBLIC RETAIN RETURN SET TASK THEN TO UNTIL USING WITH
__CATCH __ENDTRY __FINALLY __TRY
) + blocks + blocks.map {|kw| "END_" + kw}
end

def self.types
@types ||= Set.new %w(
ANY ARRAY BOOL BYTE POINTER STRING
DATE DATE_AND_TIME DT TIME TIME_OF_DAY TOD
INT DINT LINT SINT UINT UDINT ULINT USINT
WORD DWORD LWORD
REAL LREAL
)
end

def self.literals
@literals ||= Set.new %w(TRUE FALSE NULL)
end

def self.operators
@operators ||= Set.new %w(AND EQ EXPT GE GT LE LT MOD NE NOT OR XOR)
end

state :whitespace do
# Spaces
rule %r/\s+/m, Text
# // Comments
rule %r((//).*$\n?), Comment::Single
# (* Comments *)
rule %r(\(\*.*?\*\))m, Comment::Multiline
# { Comments }
rule %r(\{.*?\})m, Comment::Special
end

state :root do
mixin :whitespace

rule %r/'[^']+'/, Literal::String::Single
rule %r/"[^"]+"/, Literal::String::Symbol
rule %r/%[IQM][XBWDL][\d.]*|%[IQ][\d.]*/, Name::Variable::Magic
rule %r/\b(?:D|DT|T|TOD)#[\d_shmd:]*/i, Literal::Date
rule %r/\b(?:16#[\d_a-f]+|0x[\d_a-f]+)\b/i, Literal::Number::Hex
rule %r/\b2#[01_]+/, Literal::Number::Bin
rule %r/(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i, Literal::Number::Float
rule %r/\b[\d.,_]+/, Literal::Number

rule %r/\b[A-Z_]+\b/i do |m|
name = m[0].upcase
if self.class.keywords.include?(name)
token Keyword
elsif self.class.types.include?(name)
token Keyword::Type
elsif self.class.literals.include?(name)
token Literal
elsif self.class.operators.include?(name)
token Operator
else
token Name
end
end

rule %r/S?R?:?=>?|&&?|\*\*?|<[=>]?|>=?|[-:^\/+#]/, Operator
rule %r/\b[a-z_]\w*(?=\s*\()/i, Name::Function
rule %r/\b[a-z_]\w*\b/i, Name
rule %r/[()\[\].,;]/, Punctuation
end
end
end
end
20 changes: 20 additions & 0 deletions spec/lexers/iecst_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::IecST do
let(:subject) { Rouge::Lexers::IecST.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.awl'
assert_guess :filename => 'foo.scl'
assert_guess :filename => 'foo.st', :source => "VAR;\n END_VAR;\n"
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-iecst'
end
end
end
2 changes: 1 addition & 1 deletion spec/lexers/smalltalk_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.st'
assert_guess :filename => 'foo.st', :source => ''
end

it 'guesses by mimetype' do
Expand Down
53 changes: 53 additions & 0 deletions spec/visual/samples/iecst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// single line comment
(*
Block comment
*)
FUNCTION_BLOCK SAMPLE
TITLE='Sample function block'
VERSION : '1.0'
AUTHOR : 'Rouge'
FAMILY: 'sample'

CONST
Thousand : INT := 1_000;
TwoFiftyFive : BYTE := BYTE#255;
InHex : BYTE := 16#FF;
WithType : BYTE := BYTE#16#FF;
InBinary : BYTE := 2#1111_1111;

PI : REAL := 3.141592;
OneBillion : REAL := 1_000_000_000;
WithExponent : REAL := REAL#1e+9;
END_CONST

VAR_INPUT
a : REAL;
b : REAL;
limit : REAL;
END_VAR
VAR_OUTPUT
valid : BOOL;
END_VAR

VAR
last: REAL := 0.0;
END_VAR

VAR_TEMP
value: REAL;
END_VAR

BEGIN
IF a > 0 AND b > 0 THEN
value := sum / 2;
ELSIF a > 0 THEN
value := a;
ELSIF b > 0 THEN
value := b;
ELSE
value := last;
END_IF
last := value;

valid := value < limit;
END_FUNCTION_BLOCK