svghmi/parse_labels.ysl2
author Edouard Tisserant
Tue, 23 Aug 2022 12:19:44 +0200
changeset 3594 30f7eade322f
parent 3593 122b1094b8e6
child 3595 375626e60b63
permissions -rw-r--r--
SVGHMI: add support for "enable expressions" with arbitrary variable name assignment.

HMI tree paths can be prefixed with a variable name "@name=/MY/HMI/VAR"
Widget declarations can end with a "#" followed by a JS expression that refers to name given to variables.
Widget is disabled if expression's result is false.

Commit includes some more-or-less related generated code refactoring, that should simplify extending widget's variables attributes.
// parse_labels.ysl2


//  Parses:
//  "HMI:WidgetType|freq:param1:param2@a=path1,path1min,path1max@b=path2#a+b>3"
//
//  Into:
//  widget type="WidgetType" id="blah456" {
//      arg value="param1";
//      arg value="param2";
//      path value=".path1" index=".path1" min="path1min" max="path1max" type="PAGE_LOCAL";
//      path value="/path1" index="348" type="HMI_INT";
//      path value="path4" index="path4" type="HMI_LOCAL";
//  }
//
const "pathregex",!"'^(\w+=)?([^,=]+)([-.\d,]*)$'"!;

const "newline" |
const "twonewlines", "concat($newline,$newline)";

template "*", mode="parselabel"
{
    const "label","@inkscape:label";
    const "desc", "svg:desc";

    // add svg:desc field if continuation "\" marker is found at the end of label
    const "len","string-length($label)";
    const "has_continuation", "substring($label,$len,1)='\\'";
    const "full_decl" choose{
        when "$has_continuation" {
           const "_continuation", "substring-before($desc, $twonewlines)";
           const "continuation" choose {
               when "$_continuation" value "$_continuation";
               otherwise value "$desc";
           }
           value "concat(substring($label,1,$len - 1),translate($continuation,$newline,''))";
        }
        otherwise value "$label";
    }

    const "id","@id";

    const "declaration", "substring-after($full_decl,'HMI:')";

    const "_args", "substring-before($declaration,'@')";
    const "args" choose {
        when "$_args" value "$_args";
        otherwise value "$declaration";
    }

    const "_typefreq", "substring-before($args,':')";
    const "typefreq" choose {
        when "$_typefreq" value "$_typefreq";
        otherwise value "$args";
    }

    const "freq", "substring-after($typefreq,'|')";

    const "_type", "substring-before($typefreq,'|')";
    const "type" choose {
        when "$_type" value "$_type";
        otherwise value "$typefreq";
    }
    if "$type" widget {
        attrib "id" > «$id»
        attrib "type" > «$type»
        if "$freq" {
            if "not(regexp:test($freq,'^[0-9]*(\.[0-9]+)?[smh]?'))" {
                error > Widget id:«$id» label:«$full_decl» has wrong syntax of frequency forcing «$freq»
            }
            attrib "freq" > «$freq»
        }
        foreach "str:split(substring-after($args, ':'), ':')" {
            arg {
                attrib "value" > «.»
            }
        }
        // find "#" + JS expr at the end
        const "tail", "substring-after($declaration,'@')";
        const "taillen","string-length($tail)";
        const "has_enable", "contains($tail, '#')";
        const "paths" choose{
            when "$has_enable" {
               value "substring-before($tail,'#')";
            }
            otherwise value "$tail";
        }
        if "$has_enable" {
            const "enable_expr", "substring-after($tail,'#')";
            attrib "enable_expr" value "$enable_expr";
        }

        // for stricter syntax checking, this should make error
        // if $paths contains "@@" or ends with "@" (empty paths)

        foreach "str:split($paths, '@')" {
            if "string-length(.) > 0" path {
                // 1 : global match
                // 2 : assign=
                // 2 : /path
                // 3 : min,max
                const "path_match", "regexp:match(.,$pathregex)";
                const "pathassign", "substring-before($path_match[2],'=')";
                const "pathminmax", "str:split($path_match[4],',')";
                const "path", "$path_match[3]";
                const "pathminmaxcount", "count($pathminmax)";
                if "not($path)"
                    error > Widget id:«$id» label:«$full_decl» has wrong syntax

                attrib "value" value "$path";
                if "$pathassign"
                    attrib "assign" value "$pathassign";
                choose {
                    when "$pathminmaxcount = 2" {
                        attrib "min" > «$pathminmax[1]»
                        attrib "max" > «$pathminmax[2]»
                    }
                    when "$pathminmaxcount = 1 or $pathminmaxcount > 2" {
                        error > Widget id:«$id» label:«$full_decl» has wrong syntax of path section «$pathminmax»
                    }
                }
                if "$indexed_hmitree" choose {
                    when "regexp:test($path,'^\.[a-zA-Z0-9_]+$')" {
                        attrib "type" > PAGE_LOCAL
                    }
                    when "regexp:test($path,'^[a-zA-Z0-9_]+$')" {
                        attrib "type" > HMI_LOCAL
                    }
                    otherwise {
                        const "item", "$indexed_hmitree/*[@hmipath = $path]";
                        const "pathtype", "local-name($item)";
                        if "$pathminmaxcount = 3 and not($pathtype = 'HMI_INT' or $pathtype = 'HMI_REAL')" {
                            error > Widget id:«$id» label:«$full_decl» path section «$pathminmax» use min and max on non mumeric value
                        }
                        if "count($item) = 1" {
                            attrib "index" > «$item/@index»
                            attrib "type" > «$pathtype»
                        }
                    }
                }
            }
        }
        choose{
            when "$has_continuation" {
               const "_continuation", "substring-after($desc, $twonewlines)";
               if "$_continuation"
                       desc value "$_continuation";
            }
            otherwise
                if "$desc" desc value "$desc/text()";
        }
    }
}


// Templates to generate label back from parsed tree
template "arg", mode="genlabel" > :«@value»

template "path", mode="genlabel" {
    > @«@value»
    if "string-length(@min)>0 or string-length(@max)>0"  > ,«@min»,«@max»
}

template "widget", mode="genlabel" {
    > HMI:«@type»
    apply "arg", mode="genlabel";
    apply "path", mode="genlabel";
}