Community discussions

MikroTik App
 
juaco
just joined
Topic Author
Posts: 15
Joined: Tue Nov 09, 2010 12:15 am

config parser

Sat Feb 26, 2011 5:18 am

It's somewhat a proof of concept, (and not like a real *LR parser), but so far i get the correct output from a config file and error checking is in place. Hope i can make it a bit more solid in the near time.

Most syntax elements are tweakeable, (it's not universal though). So play with the code. The current defined rules are:
  • sections are called "contexts", they are not nestable
  • a context begins with <contextname> and ends with </contextname>
  • inside a context you can use name=value pairs, called "directives"
  • "\" can be used to break a directive in multiple lines
  • spaces, tabs and enters are free (won't be taken into account by the parser)
  • ";" is both used as a comment mark or optional line delimiter, that is, "everything after a semicolon is ignored, wherever it appears"
There is in place a blueprint for further rules applicable to contexts and directives, whether they are required, must be unique, defaults, types, where to store it, etc. This still isn't coded. Everything the code does for now is running through the config file, checking for grammar errors and detecting context start and end. It then puts the parsed text to the console.

Perhaps its not very usable at this point but who knows, maybe someone finds it useful. I should warn that its "play" code, IOW can contain any kind of bugs, its awful to read, and all that stuff.

code:
# config parser
# name: parseini 
# policy: read

# LOCALS -----------------------------------------------------------
# name, size and contents of config script
:local CFName "my.ini"
:local CFContents [/system script get [find name=$CFName] source];
:local CFLen [:len $CFContents];

# line elements
:local EOL 0;
:local LastEOL 0;
:local EOLMark "\n";
:local EOF false;
:local CFLine "";
:local CFLineNumber -1;
:local CFLinetemp "";
:local CFLineBuffer "";

# delimiters
:local ContextStartOpen "<";
:local ContextStartClose ">";
:local ContextEndOpen "</";
:local ContextEndClose ">";
:local SentenceDelimiter ";";
:local SentenceContinuation "\\";
:local Assignment "=";
:local ListDelimiter ",";
:local Delimiters "$ContextStartOpen$ContextStartClose$ContextEndOpen$ContextEndClose$SentenceDelimiter$SentenceContinuation$Assignment$ListDelimiter";

# character sets
:local Lowercase "abcdefghijklmnopqrstuvwxyz";
:local Uppercase "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
:local Numbers "0123456789";
:local ExtraChars "-_.";
:local ValidChars "$Lowercase$Uppercase$Numbers$Delimiters$ExtraChars";
:local IgnoreChars "\t\_\r\n";

# context / directives config
:local ValidContexts "global,monitor,chain,debug";
:local CurrentContext "";

# name, mandatory, multiple
:local ContextsConfig {\
"global, true, false";\
"monitor, false, false";\
"chain, false, true";\
"debug, false, false";\
};

# context, name, mandatory, multiple, value_type, value_default
:local DirectivesConfig {\
"global, default_route_selector, false, false, str, nth";\
"global, default_l7_persistence, false, false, str, no";\
"global, default_pcc_persistence, false, false, str, src-address";\
"global, default_l7_entry_expiration, false, false, str, 30m";\
"monitor, enabled, false, false, bool, false";\
"monitor, tests_per_route, false, false, num, 10";\
"monitor, public_hosts, false, false, array, ";\
"monitor, dynamic_scheduling, false, false, bool, false";\
"chain, name, true, false, string, ";\
"chain, local_interfaces, true, false, array, ";\
"chain, lists, false, false, array, ";\
"chain, primary_routes, true, false, array, ";\
"chain, secondary_routes, false, false, array, ";\
"chain, route_selector, false, false, str, ";\
"chain, l7_persistence, false, false, str, ";\
"chain, l7_entry_expiration, false, false, str, ";\
"debug, entry_exit, false, false, bool, false";\
"debug, locking, false, false, bool, false";\
"debug, route_info, false, false, bool, false";\
"debug, misc, false, false, bool, false";\
"debug, rtmon_dynsched, false, false, bool, false";\
"debug, rtmon_tests, false, false, bool, false";\
};

# START -----------------------------------------------------------
:do {
# get line and update markers
    :set CFLineNumber ($CFLineNumber+1);
    :set EOL [:find $CFContents $EOLMark $LastEOL];
    :set EOF ($EOL >= $CFLen);

# have into account that the last line could not end with EOLMark
    :if (!$EOF) do={
        :set CFLinetemp [:pick $CFContents $LastEOL $EOL];
        :set LastEOL ($EOL+[:len $EOLMark]);
    } else={
        :set CFLinetemp [:pick $CFContents $LastEOL $CFLen];
# this is b/c an apparent bug in ROS
        :set EOF true;
    };

# trim ignored chars and end of sentence
# in case of invalid char stop
    :set CFLine "";
    :local charpos 0;
    :do {
        :local character [:pick $CFLinetemp $charpos ($charpos+1)]
        :if ([:typeof [:find $ValidChars $character]]="num") do={
            :if ($character=$SentenceDelimiter) do={
                :set charpos ([:len $CFLinetemp]-1);
            } else={
                :set CFLine "$CFLine$character";
            };
        } else={
            :if ([:typeof [:find $IgnoreChars $character]]="nil") do={
                :error "parseini: invalid char, config file \"$CFName\", line $($CFLineNumber+1), column $($charpos+1)";
            };
        };
        :set charpos ($charpos+1);
    } while ($charpos < [:len $CFLinetemp]);

# context analysis
# valid lines will go to config vars
# if error detected stop
    :if ($EOF || [:len $CFLine]>0) do={
        :if ($CurrentContext="") do={
# null context: expect a context start
            if ([:pick $CFLine 0 [:len $ContextStartOpen]]!=$ContextStartOpen || [:pick $CFLine ([:len $CFLine]-[:len $ContextStartClose]) [:len $CFLine]]!=$ContextStartClose) do={
                :error "parseini: \"$CFLine\" context start expected, config file \"$CFName\", line $($CFLineNumber+1)";
            };
# validate context name
            :set CurrentContext [:pick $CFLine [:len $ContextStartOpen] ([:len $CFLine]-[:len $ContextStartClose])];
            :if ([:typeof [:find $ValidContexts $CurrentContext]]="nil") do={
                :error "parseini: \"$CurrentContext\" is not a valid context, config file \"$CFName\", line $($CFLineNumber+1)";
            };

# uncomment to print context start
                :put $CFLine;

            :set CFLine "";
        } else={
# valid context
# if we found a context end validate it
            if ([:pick $CFLine 0 [:len $ContextEndOpen]]=$ContextEndOpen) do={
                if ([:pick $CFLine [:len $ContextEndOpen] ([:len $CFLine]-[:len $ContextEndClose])] != $CurrentContext) do={
                    :error "parseini: expected \"$ContextEndOpen$CurrentContext$ContextEndClose\" or valid directive, config file \"$CFName\", line $($CFLineNumber+1)";
                };

# uncomment to print context end
                :put $CFLine;

# if we had a continuation buffer close it, otherwise clean line
                if ($CFLineBuffer!="") do={
                    :set CFLine $CFLineBuffer;
                    :set CFLineBuffer "";
                } else={
                    :set CFLine "";
                };
# close context
                :set CurrentContext "";
            } else={
# if we hit EOF and the context isn't closed exit with error
                if ($EOF) do={
                    :error "parseini: EOF when a \"$ContextEndOpen$CurrentContext$ContextEndClose\" was expected, config file \"$CFName\", line $($CFLineNumber+1)";
                };
# invalidate nested context start
                if ([:pick $CFLine 0 [:len $ContextStartOpen]]=$ContextStartOpen) do={
                    :error "parseini: invalid directive \"$CFLine\" (apparent nested context), config file \"$CFName\", line $($CFLineNumber+1)";
                };
# detect if we have to continue a line or start a multiple line directive
                if ($CFLineBuffer!="") do={
                    :set CFLine "$CFLineBuffer$CFLine";
                    :set CFLineBuffer "";
                };
                if ([:pick $CFLine ([:len $CFLine]-[:len $SentenceContinuation]) [:len $CFLine]]=$SentenceContinuation) do={
                    :set CFLineBuffer [:pick $CFLine 0 ([:len $CFLine]-[:len $SentenceContinuation])];
                };
            };
# if the continuation buffer is empty we have a directive for validation
            if ($CFLineBuffer="" && $CFLine!="") do={
                :put "$CFLine"
            };
        };
    };
} while (!$EOF);
example config:
;===============================================================================
; config global
;===============================================================================
<global>
; definiciones de rutas a usar como primaria o secundaria
    system-routes = vboxnet0, vboxnet1, vboxnet2, vboxnet3

; defaults para directivas en definiciones de cadenas
; si no estan definidos, los valores son los que se indican aqui
;    default-route-selector = nth
;    default-l7-persistence = no
;    default-pcc-persistence = src-address
;    default-l7-entry-expiration = 30m
</global>

;===============================================================================
; configuracion del monitor
;===============================================================================
<monitor>
; si el monitor se habilita (o sea se incluye en el scheduler)
    enabled = yes

; si se habilita scheduler dinamico (default: no)
    dynamic-scheduling = yes

; cantidad de hosts a pingear x ruta (default 10)
    tests-per-route = 7

; hosts publicos para realizar tests de ruta
    public-hosts =  198.41.0.4,192.228.79.201,192.33.4.12,128.8.10.90,192.203.230.10,\
                    192.5.5.241,192.112.36.4,128.63.2.53,192.36.148.17,192.58.128.30,\
                    193.0.14.129,199.7.83.42,202.12.27.33,192.43.244.18,91.189.94.4;
</monitor>

;===============================================================================
; diversos tipos de mensaje a mostrar en consola y logs
;===============================================================================
<debug>
    entry-exit = true           ; entrada y salida de scripts
    locking = true              ; info de locks de ejecucion
    route-info = true           ; info de estado de rutas
    rtmon-dynsched = true       ; info de scheduling dinamico del monitor
    rtmon-tests = true          ; info de tests de ruta del monitor
    misc = true                 ; mensajes varios
</debug>

;===============================================================================
; definiciones de cadenas
;===============================================================================
<chain>
    name = vboxnet0
    local-interfaces = internalnet
    lists = vboxnet0
    primary-routes = vboxnet0
    secondary-routes = any
</chain>

<chain>
    name = vboxnet1
    local-interfaces = internalnet
    lists = vboxnet1
    primary-routes = vboxnet1
    secondary-routes = any
</chain>

<chain>
    name = vboxnet2
    local-interfaces = internalnet
    lists = vboxnet2
    primary-routes = vboxnet2
    secondary-routes = any
</chain>

<chain>
    name = vboxnet3
    local-interfaces = internalnet
    lists = vboxnet3
    primary-routes = vboxnet3
    secondary-routes = any
</chain>

<chain>
    name = balance-round-robin
    local-interfaces = internalnet
    lists = balance-round-robin
    primary-routes = vboxnet0, vboxnet1
    secondary-routes = any
    route-selector = nth
    l7-persistence = no
</chain>

<chain>
    name = balance-src-random
    local-interfaces = internalnet
    lists = balance-src-random
    primary-routes = vboxnet2, vboxnet3, vboxnet0
    secondary-routes = any
    route-selector = random
    l7-persistence = src-address
    l7-entry-expiration = 15m
</chain>

<chain>
    name = balance-pcc-dstsrc
    local-interfaces = internalnet
    lists = balance-pcc-dstsrc
    primary-routes = vboxnet1, vboxnet0
    secondary-routes = vboxnet2
    route-selector = random
    l7-persistence = both-addresses-and-ports
</chain>

Who is online

Users browsing this forum: No registered users and 21 guests