Files
gf-core/src/GF/Speech/GrammarToVoiceXML.hs

252 lines
7.5 KiB
Haskell

----------------------------------------------------------------------
-- |
-- Module : GrammarToVoiceXML
-- Maintainer : Bjorn Bringert
-- Stability : (stable)
-- Portability : (portable)
--
-- Create VoiceXML dialogue system from a GF grammar.
-----------------------------------------------------------------------------
module GF.Speech.GrammarToVoiceXML (grammar2vxml) where
import qualified GF.Canon.GFC as GFC
import GF.Grammar.Macros hiding (assign)
import GF.Infra.Modules
import GF.Data.Operations
import GF.Data.XML
import Data.List (isPrefixOf, find, intersperse)
-- | the main function
grammar2vxml :: String -> GFC.CanonGrammar -> String
grammar2vxml startcat gr = showsXMLDoc (skel2vxml name startcat gr') ""
where (name, gr') = vSkeleton gr
type VIdent = String
type VSkeleton = [(VIdent, [(VIdent, [VIdent])])]
vSkeleton :: GFC.CanonGrammar -> (String,VSkeleton)
vSkeleton gr = (name,collectR rules [(c,[]) | c <- cats]) where
collectR rr hh =
case rr of
(fun,typ):rs -> case catSkeleton typ of
Ok (cats,cat) ->
collectR rs (updateSkeleton (symid (snd cat)) hh (fun,
map (symid . snd) cats))
_ -> collectR rs hh
_ -> hh
cats = [symid cat | (cat,GFC.AbsCat _ _) <- defs]
rules = [(symid fun, typ) | (fun,GFC.AbsFun typ _) <- defs]
defs = concat [tree2list (jments m) | im@(_,ModMod m) <- modules gr, isModAbs m]
name = ifNull "UnknownModule" (symid . last) [n | (n,ModMod m) <- modules gr, isModAbs m]
updateSkeleton :: VIdent -> VSkeleton -> (VIdent, [VIdent]) -> VSkeleton
updateSkeleton cat skel rule =
case skel of
(cat0,rules):rr | cat0 == cat -> (cat0, rule:rules) : rr
(cat0,rules):rr -> (cat0, rules) : updateSkeleton cat rr rule
skel2vxml :: String -> VIdent -> VSkeleton -> XML
skel2vxml name start skel =
vxml (prelude ++ [startForm] ++ concatMap (uncurry (catForms gr)) skel)
where
gr = grammarURI name
prelude = scriptLib
startForm = Tag "form" [] [subdialog "sub" [("srcexpr","'#'+"++string start)] []]
grammarURI :: String -> String
grammarURI name = name ++ ".grxml"
scriptLib :: [XML]
scriptLib = [script (unlines s)]
where
s = ["function dump(r, p) {",
" if (isUndefined(p)) { p = 0 }",
" if (isUndefined(r)) {",
" return 'undefined';",
" } else if (isArray(r)) {",
" var s = '[';",
" for (var i = 0; i < r.length; r++) {",
" s += dump(r[0], 0);",
" if (i < r.length-1) { s += ',' }",
" }",
" s += ']';",
" return s;",
" } else if (r == '?') {",
" return '?';",
" } else {",
" var s = r.name;",
" var i;",
" for (i = 0; ; i++) {",
" var c = r['arg'+i];",
" if (c == undefined) { break; }",
" s += ' ' + dump(c, 1);",
" }",
" if (i > 0 && p > 0) { s = '(' + s + ')'; }",
" return s;",
" }",
"}",
"function isArray(a) { return a && typeof a == 'object' && a.constructor == Array; }",
"function isUndefined(a) { return typeof a == 'undefined'; }"
]
catForms :: String -> VIdent -> [(VIdent, [VIdent])] -> [XML]
catForms gr cat fs =
comments [cat ++ " category."]
++ [cat2form gr cat fs]
++ map (uncurry (fun2form gr)) fs
cat2form :: String -> VIdent -> [(VIdent, [VIdent])] -> XML
cat2form gr cat fs =
form cat [var "value" (Just "'?'"), formDebug cat,
block [if_ "value != '?'" [assign cat "value"]],
field cat [] [promptString (catQuestion cat),
grammar (gr++"#"++cat),
nomatch [Data "I didn't understand you.", reprompt],
help [Data ("help_"++cat)],
filled [] [if_else (cat ++ " == '?'") [reprompt] feedback]],
block [prompt [Data (cat ++ " = "), value ("dump("++cat++")")]],
subdialog "sub" [("srcexpr","'#'+"++cat++".name")]
[param "value" cat, filled [] subDone]]
where subDone = [assign cat "sub.value", return_ [cat]]
feedback = []
catQuestion :: VIdent -> String
catQuestion cat = questFun
where questFun = "quest_"++cat
fun2form :: String -> VIdent -> [VIdent] -> XML
fun2form gr fun args =
form fun ([var "value" Nothing] ++ [formDebug fun]
++ ss
++ [ret])
where
argNames = zip ["arg"++show n | n <- [0..]] args
ss = map (uncurry mkSub) argNames
mkSub a t = subdialog a [("srcexpr","'#'+"++string t)]
[param "value" ("value."++a),
filled [] [assign ("value."++a) (a++"."++t)]]
ret = block [return_ ["value"]]
formDebug id = block [prompt [Data ("Entering form " ++ id ++ ". value = "), value "dump(value)"]]
--
-- * VoiceXML stuff
--
vxml :: [XML] -> XML
vxml = Tag "vxml" [("version","2.0"),("xmlns","http://www.w3.org/2001/vxml")]
form :: String -> [XML] -> XML
form id xs = Tag "form" [("id", id)] xs
field :: String -> [(String,String)] -> [XML] -> XML
field name attrs = Tag "field" ([("name",name)]++attrs)
subdialog :: String -> [(String,String)] -> [XML] -> XML
subdialog name attrs = Tag "subdialog" ([("name",name)]++attrs)
filled :: [(String,String)] -> [XML] -> XML
filled = Tag "filled"
grammar :: String -> XML
grammar uri = Tag "grammar" [("src",uri)] []
prompt :: [XML] -> XML
prompt = Tag "prompt" []
promptString :: String -> XML
promptString p = prompt [Data p]
reprompt :: XML
reprompt = Tag "reprompt" [] []
assign :: String -> String -> XML
assign n e = Tag "assign" [("name",n),("expr",e)] []
value :: String -> XML
value expr = Tag "value" [("expr",expr)] []
if_ :: String -> [XML] -> XML
if_ c b = if_else c b []
if_else :: String -> [XML] -> [XML] -> XML
if_else c t f = cond [(c,t)] f
cond :: [(String,[XML])] -> [XML] -> XML
cond ((c,b):rest) els = Tag "if" [("cond",c)] (b ++ es)
where es = [Tag "elseif" [("cond",c')] b' | (c',b') <- rest]
++ if null els then [] else (Tag "else" [] []:els)
goto_item :: String -> XML
goto_item nextitem = Tag "goto" [("nextitem",nextitem)] []
return_ :: [String] -> XML
return_ names = Tag "return" [("namelist", unwords names)] []
block :: [XML] -> XML
block = Tag "block" []
throw :: String -> String -> XML
throw event msg = Tag "throw" [("event",event),("message",msg)] []
nomatch :: [XML] -> XML
nomatch = Tag "nomatch" []
help :: [XML] -> XML
help = Tag "help" []
param :: String -> String -> XML
param name expr = Tag "param" [("name",name),("expr",expr)] []
var :: String -> Maybe String -> XML
var name expr = Tag "var" ([("name",name)]++e) []
where e = maybe [] ((:[]) . (,) "expr") expr
script :: String -> XML
script s = Tag "script" [] [CData s]
scriptURI :: String -> XML
scriptURI uri = Tag "script" [("uri", uri)] []
--
-- * ECMAScript stuff
--
string :: String -> String
string s = "'" ++ concatMap esc s ++ "'"
where esc '\'' = "\\'"
esc c = [c]
--
-- * List stuff
--
isListCat :: (VIdent, [(VIdent, [VIdent])]) -> Bool
isListCat (cat,rules) = "List" `isPrefixOf` cat && length rules == 2
&& ("Base"++c) `elem` fs && ("Cons"++c) `elem` fs
where c = elemCat cat
fs = map fst rules
-- | Gets the element category of a list category.
elemCat :: VIdent -> VIdent
elemCat = drop 4
isBaseFun :: VIdent -> Bool
isBaseFun f = "Base" `isPrefixOf` f
isConsFun :: VIdent -> Bool
isConsFun f = "Cons" `isPrefixOf` f
baseSize :: (VIdent, [(VIdent, [VIdent])]) -> Int
baseSize (_,rules) = length bs
where Just (_,bs) = find (("Base" `isPrefixOf`) . fst) rules