su4sml/src/codegen/tpl_parser.sml

231 lines
10 KiB
Standard ML

(*****************************************************************************
* su4sml --- an SML repository for managing (Secure)UML/OCL models
* http://projects.brucker.ch/su4sml/
*
* tpl_parser.sml --- template parser of a su4sml-gcg template
* This file is part of su4sml.
*
* Copyright (c) 2005-2007, ETH Zurich, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* * Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************)
(* $Id$ *)
(** A parser for template files. *)
signature TPL_PARSER =
sig
datatype TemplateTree
= ElseNode of TemplateTree list
| EvalLeaf of TemplateTree list
| ForEachNode of string * TemplateTree list
| IfNode of string * TemplateTree list
| OpenFileLeaf of string
| OpenFileIfNotExistsLeaf of string
| RootNode of TemplateTree list
| TextLeaf of string
val printTTree : TemplateTree -> unit
val parse : string -> TemplateTree
end
structure Tpl_Parser : TPL_PARSER =
struct
open Gcg_Helper
val tplStream = ref (TextIO.openString "@// dummy template\n");
fun opentFile file = (TextIO.closeIn (!tplStream) ;
tplStream := (TextIO.openIn file))
handle ex => Logger.error ("in Tpl_Parser.opentFile: \
\couldn't open preprocessed template file: "^
General.exnMessage ex)
fun cleanUp tplFile = (TextIO.closeIn (!tplStream);
OS.FileSys.remove tplFile)
fun readNextLine () = TextIO.inputLine (!tplStream)
(* FIXME: this currently uses a simple line-based template-file structure *)
(* (every line corresponds to exactly one node in this tree) *)
(* This should really be relaxed... *)
(* FIXME: add separate VariableLeaf *)
(* FIXME: merge If and Else Nodes *)
(* FIXME: add InfoLeaf to print informational messages during codegen *)
datatype TemplateTree = RootNode of TemplateTree list
| OpenFileLeaf of string
| OpenFileIfNotExistsLeaf of string
| EvalLeaf of TemplateTree list
| TextLeaf of string
| IfNode of string * TemplateTree list
(* FIXME: why a seperate ElseNode? should be part of IfNode *)
| ElseNode of TemplateTree list
| ForEachNode of string * TemplateTree list
(**
* replaceSafely (s,v,x) replaces every v that occurs unescaped in s with x.
* if v occurs escaped with "\" in s, then the "\" is removed from s.
* FIXME: move to stringhandling?
*)
fun replaceSafely _ _ "" = ""
| replaceSafely v x s =
let val v_size = size v
val s_size = size s
in
if String.isPrefix (str #"\\"^v) s
then v^replaceSafely v x (String.extract (s, v_size + 1, NONE))
else if String.isPrefix v s
then x^replaceSafely v x (String.extract (s, v_size, NONE))
else str (String.sub (s,0))^replaceSafely v x (String.extract (s, 1, NONE))
end
(** removes leading, trainling, and multiple consecutive whitespace chars. *)
(* FIXME: movev to StringHandling? *)
fun cleanLine s = String.concatWith " " (String.tokens Char.isSpace s)
(* debugging function
* prints ParseTree to stdOut
*)
fun printTplTree prefix (RootNode(l)) = (print (prefix^"root"^"\n"); List.app (printTplTree (prefix))l)
| printTplTree prefix (OpenFileLeaf(s))= print (prefix^"openfile:"^s^"\n")
| printTplTree prefix (OpenFileIfNotExistsLeaf(s))= print (prefix^"openfileifnotexists:"^s^"\n")
| printTplTree prefix (EvalLeaf(l)) = (print (prefix^"eval:\n"); List.app (printTplTree (prefix^"\t"))l)
| printTplTree prefix (TextLeaf(s)) = print (prefix^"text:"^s^"\n")
| printTplTree prefix (IfNode(s,l)) = (print (prefix^"if:"^s^"\n");List.app (printTplTree (prefix^"\t")) l)
| printTplTree prefix (ElseNode(l)) = (print (prefix^"else:"^"\n"); List.app (printTplTree (prefix^"\t")) l)
| printTplTree prefix (ForEachNode(s,l))=(print (prefix^"foreach:"^s^"\n");List.app (printTplTree (prefix^"\t")) l)
val printTTree = printTplTree ""
fun isComment s = (String.isPrefix "//" s)
(** returns the prefix of l up to the first element where f evaluates to true *)
fun takeUntil f [] = []
| takeUntil f (h::t) = if f h then [] else h::(takeUntil f t)
(** splits line into tokens considering handling escaped @ *)
fun tokenize line = let val l = joinEscapeSplitted "@" (fieldSplit #"@" line)
in
takeUntil isComment l
end
(**
* extracts the type of line.
* line type must be first token in line!
* if no control tag in line -> "text" returned
*)
fun getType l = let val sl = tokenize l
in
if (length sl = 1) orelse (length sl = 0)
then "text" (* rather: comment? *)
else hd (tokenSplit #" " (String.concat sl))
end
handle ex => Logger.error ("in Tpl_Parser.getType: "^General.exnMessage ex)
(**
* getContent line
* @return the content of a line
*)
fun getContent l = let val sl = tokenize l
in
if (length sl = 0) then ""
else if (length sl = 1) then hd sl
else String.concat (tl (fieldSplit #" " (String.concat (tl sl))))
end
handle ex => Logger.error ("in Tpl_Parser.getContent: "^General.exnMessage ex)
(** cleans line, replaces nl and tabs so that no space char is left out. *)
fun preprocess s = replaceSafely "@spc" " " (replaceSafely "@tab" "\t" (replaceSafely "@nl" "\n" (cleanLine s)))
(**
* builds the TemplateTree.
* @return a TemplateTree list
*)
fun buildTree (SOME line) =
(let fun getNode ("text", c) = TextLeaf c :: buildTree (readNextLine())
| getNode ("foreach", c) = ForEachNode (c, buildTree (readNextLine()))
:: buildTree (readNextLine())
| getNode ("if", c) = IfNode (c, buildTree (readNextLine()))
:: buildTree (readNextLine())
| getNode ("else", _) = [ ElseNode (buildTree (readNextLine())) ]
| getNode ("elsif", c) = [ ElseNode [ IfNode (c, buildTree (readNextLine())) ]]
| getNode ("openfile", c) = OpenFileLeaf c :: buildTree (readNextLine())
| getNode ("openfileifnotexists", c) = OpenFileIfNotExistsLeaf c
:: buildTree (readNextLine())
| getNode ("eval", "") = EvalLeaf (buildTree (readNextLine()))
:: buildTree (readNextLine())
| getNode ("eval", expr) = EvalLeaf [ TextLeaf expr ]:: buildTree (readNextLine())
| getNode ("end",_) = []
| getNode (t,c) = Logger.error ("in Tpl_Parser.buildTree: error while parsing \
\node \""^t^"\" with content \""^c^"\".")
val prLine = preprocess line
in
getNode ((getType prLine),(getContent prLine))
end
handle ex => Logger.error ("in Tpl_Parser.buildTree: error "^General.exnMessage ex))
| buildTree NONE = []
(** calls the external cpp ( C PreProcessor).
* writes merged template to a file with extension .tmp instead of .tpl
* and returns this file
*)
fun call_cpp file =
let val targetFile = OS.FileSys.tmpName ()
val _ = OS.Process.system ("cpp -P -C "^(Config.su4sml_share())^"/"^file^" "^targetFile)
in
targetFile
end
(** parse template-file
* @return the parsed template tree
*)
fun parse file = let val _ = Logger.info ("parsing template "^file)
val mergedTpl = call_cpp file;
val _ = opentFile mergedTpl;
val pt = RootNode(buildTree (readNextLine()));
val _ = cleanUp mergedTpl;
in
pt
end
end