diff options
author | Daniil Baturin <daniil@baturin.org> | 2018-05-26 22:06:30 +0700 |
---|---|---|
committer | Daniil Baturin <daniil@baturin.org> | 2018-05-26 22:06:30 +0700 |
commit | c1e6da134ee9208333a4626107273c956f25d13c (patch) | |
tree | 8d381ab9f95a12553bf48d1a937f2d7fecc1cfdf /lib | |
parent | 557f45a5a606b8f8ae1630c9f267d31376912746 (diff) | |
download | libvyosconfig-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.mll | 80 | ||||
-rw-r--r-- | lib/vyos1x_parser.mly | 110 |
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) + } +; |