summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@baturin.org>2018-05-26 22:06:30 +0700
committerDaniil Baturin <daniil@baturin.org>2018-05-26 22:06:30 +0700
commitc1e6da134ee9208333a4626107273c956f25d13c (patch)
tree8d381ab9f95a12553bf48d1a937f2d7fecc1cfdf /lib
parent557f45a5a606b8f8ae1630c9f267d31376912746 (diff)
downloadlibvyosconfig-c1e6da134ee9208333a4626107273c956f25d13c.tar.gz
libvyosconfig-c1e6da134ee9208333a4626107273c956f25d13c.zip
Add a parser for the VyOS 1.x config format.
Diffstat (limited to 'lib')
-rw-r--r--lib/vyos1x_lexer.mll80
-rw-r--r--lib/vyos1x_parser.mly110
2 files changed, 190 insertions, 0 deletions
diff --git a/lib/vyos1x_lexer.mll b/lib/vyos1x_lexer.mll
new file mode 100644
index 0000000..f471ec6
--- /dev/null
+++ b/lib/vyos1x_lexer.mll
@@ -0,0 +1,80 @@
+{
+
+open Vyos1x_parser
+
+exception Error of string
+
+let vy_in_string = ref false
+
+}
+
+rule token = parse
+| [' ' '\t' '\r']
+ { token lexbuf }
+| '\n'
+ { Lexing.new_line lexbuf; if !vy_in_string then (vy_in_string := false; NEWLINE) else token lexbuf }
+| '"'
+ { vy_in_string := true; read_string (Buffer.create 16) lexbuf }
+| '''
+ { vy_in_string := true; read_single_quoted_string (Buffer.create 16) lexbuf }
+| "/*"
+ { vy_in_string := false; read_comment (Buffer.create 16) lexbuf }
+| '{'
+ { vy_in_string := false; LEFT_BRACE }
+| '}'
+ { vy_in_string := false; RIGHT_BRACE }
+| [^ ' ' '\t' '\n' '\r' '{' '}' '[' ']' ';' '#' '"' ''' ]+ as s
+ { vy_in_string := true; IDENTIFIER s}
+| eof
+ { EOF }
+| _
+{ raise (Error (Printf.sprintf "At offset %d: unexpected character.\n" (Lexing.lexeme_start lexbuf))) }
+
+and read_string buf =
+ parse
+ | '"' { STRING (Buffer.contents buf) }
+ | '\\' '/' { Buffer.add_char buf '/'; read_string buf lexbuf }
+ | '\\' '\\' { Buffer.add_char buf '\\'; read_string buf lexbuf }
+ | '\\' 'b' { Buffer.add_char buf '\b'; read_string buf lexbuf }
+ | '\\' 'f' { Buffer.add_char buf '\012'; read_string buf lexbuf }
+ | '\\' 'n' { Buffer.add_char buf '\n'; read_string buf lexbuf }
+ | '\\' 'r' { Buffer.add_char buf '\r'; read_string buf lexbuf }
+ | '\\' 't' { Buffer.add_char buf '\t'; read_string buf lexbuf }
+ | '\\' '\'' { Buffer.add_char buf '\''; read_string buf lexbuf }
+ | '\\' '"' { Buffer.add_char buf '"'; read_string buf lexbuf }
+ | '\n' { Lexing.new_line lexbuf; Buffer.add_char buf '\n'; read_string buf lexbuf }
+ | [^ '"' '\\']+
+ { Buffer.add_string buf (Lexing.lexeme lexbuf);
+ read_string buf lexbuf
+ }
+ | _ { raise (Error (Printf.sprintf "Illegal string character: %s" (Lexing.lexeme lexbuf))) }
+ | eof { raise (Error ("String is not terminated")) }
+
+and read_single_quoted_string buf =
+ parse
+ | ''' { STRING (Buffer.contents buf) }
+ | '\\' '/' { Buffer.add_char buf '/'; read_string buf lexbuf }
+ | '\\' '\\' { Buffer.add_char buf '\\'; read_string buf lexbuf }
+ | '\\' 'b' { Buffer.add_char buf '\b'; read_string buf lexbuf }
+ | '\\' 'f' { Buffer.add_char buf '\012'; read_string buf lexbuf }
+ | '\\' 'n' { Buffer.add_char buf '\n'; read_string buf lexbuf }
+ | '\\' 'r' { Buffer.add_char buf '\r'; read_string buf lexbuf }
+ | '\\' 't' { Buffer.add_char buf '\t'; read_string buf lexbuf }
+ | '\\' '\'' { Buffer.add_char buf '\''; read_string buf lexbuf }
+ | '\\' '"' { Buffer.add_char buf '"'; read_string buf lexbuf }
+ | '\n' { Lexing.new_line lexbuf; Buffer.add_char buf '\n'; read_string buf lexbuf }
+ | [^ ''' '\\']+
+ { Buffer.add_string buf (Lexing.lexeme lexbuf);
+ read_single_quoted_string buf lexbuf
+ }
+ | _ { raise (Error (Printf.sprintf "Illegal string character: %s" (Lexing.lexeme lexbuf))) }
+ | eof { raise (Error ("String is not terminated")) }
+
+and read_comment buf =
+ parse
+ | "*/"
+ { COMMENT (Buffer.contents buf) }
+ | _
+ { Buffer.add_string buf (Lexing.lexeme lexbuf);
+ read_comment buf lexbuf
+ }
diff --git a/lib/vyos1x_parser.mly b/lib/vyos1x_parser.mly
new file mode 100644
index 0000000..04f2843
--- /dev/null
+++ b/lib/vyos1x_parser.mly
@@ -0,0 +1,110 @@
+%{
+ open Config_tree
+
+ exception Duplicate_child of (string * string)
+
+ (* Used for checking if after merging immediate children,
+ any of them have duplicate children inside,
+ e.g. "interfaces { ethernet eth0 {...} ethernet eth0 {...} }" *)
+ let find_duplicate_children n =
+ let rec aux xs =
+ let xs = List.sort compare xs in
+ match xs with
+ | [] | [_] -> ()
+ | x :: x' :: xs ->
+ if x = x' then raise (Duplicate_child (Vytree.name_of_node n, x))
+ else aux (x' :: xs)
+ in
+ aux @@ Vytree.list_children n
+
+ (* When merging nodes with values, append values of subsequent nodes to the
+ first one *)
+ let merge_data l r = {l with values=(List.append l.values r.values)}
+%}
+
+%token <string> IDENTIFIER
+%token <string> STRING
+%token <string> COMMENT
+%token LEFT_BRACE
+%token RIGHT_BRACE
+%token NEWLINE
+%token EOF
+
+%start <Config_tree.t> config
+%%
+
+(* If there are multiple comments before a node, consider the last one its real comment *)
+comments:
+ cs = list(COMMENT) { match cs with [] -> None | _ -> Some (List.rev cs |> List.hd) }
+
+value:
+ | v = STRING
+ { v }
+ | v = IDENTIFIER
+ { v }
+;
+
+
+leaf_node:
+ | comment = comments;
+ name = IDENTIFIER; value = value; NEWLINE;
+ { Vytree.make_full {default_data with values=[value]; comment=comment} name []}
+ | comment = comments;
+ name = IDENTIFIER; NEWLINE (* valueless node *)
+ { Vytree.make_full {default_data with comment=comment} name [] }
+;
+
+node:
+ | comment = comments;
+ name = IDENTIFIER; LEFT_BRACE; children = list(node_content); RIGHT_BRACE;
+ {
+ let node =
+ Vytree.make_full {default_data with comment=comment} name [] in
+ let node = List.fold_left Vytree.adopt node (List.rev children) |> Vytree.merge_children merge_data in
+ try
+ List.iter find_duplicate_children (Vytree.children_of_node node);
+ node
+ with
+ | Duplicate_child (child, dup) ->
+ failwith (Printf.sprintf "Node \"%s %s\" has two children named \"%s\"" name child dup)
+ }
+;
+
+(* XXX: for the config to be loadable with the old CStore backend, what was formatted as a tag node
+ in the original config, must remain formatted that way.
+ Since fixing it there is more trouble than it's worth, and creating a separate version of
+ Config_tree just for that seems strange, I reused the ephemeral flag for that, which
+ is never used in the VyOS 1.x context anyway.
+ *)
+tag_node:
+ | comment = comments;
+ name = IDENTIFIER; tag = value; LEFT_BRACE; children = list(node_content); RIGHT_BRACE
+ {
+ let outer_node = Vytree.make_full default_data name [] in
+ let inner_node =
+ Vytree.make_full {default_data with comment=comment; ephemeral=true} tag [] in
+ let inner_node = List.fold_left Vytree.adopt inner_node (List.rev children) |> Vytree.merge_children merge_data in
+ let node = Vytree.adopt outer_node inner_node in
+ try
+ List.iter find_duplicate_children (Vytree.children_of_node inner_node);
+ node
+ with
+ | Duplicate_child (child, dup) ->
+ failwith (Printf.sprintf "Node \"%s %s %s\" has two children named \"%s\"" name tag child dup)
+ }
+
+node_content: n = node { n } | n = leaf_node { n } | n = tag_node { n };
+
+%public config:
+ | ns = list(node); EOF
+ {
+ let root = make "root" in
+ let root = List.fold_left Vytree.adopt root (List.rev ns) |> Vytree.merge_children merge_data in
+ try
+ List.iter find_duplicate_children (Vytree.children_of_node root);
+ root
+ with
+ | Duplicate_child (child, dup) ->
+ failwith (Printf.sprintf "Node \"%s\" has two children named \"%s\"" child dup)
+ }
+;