1
0
forked from GitHub/gf-core

gfse: edit abstract syntax in text mode with instant syntax error reporting

This is an experimental feature. It requires server support for parsing and is
thus not available while offline, unlike most other editing functionality.
This commit is contained in:
hallgren
2012-02-21 16:58:18 +00:00
parent 8c00c7bcd6
commit b35b48a701
6 changed files with 236 additions and 11 deletions

View File

@@ -21,10 +21,9 @@ import Network.URI(URI(..),parseURI)
import Network.Shed.Httpd(initServer,Request(..),Response(..),queryToArguments,
noCache)
--import qualified Network.FastCGI as FCGI -- from hackage direct-fastcgi
--import qualified Data.ByteString.Char8 as BS(pack,unpack,length)
import Network.CGI(handleErrors,liftIO)
import FastCGIUtils(outputJSONP,handleCGIErrors,stderrToFile)
import Text.JSON(encode,showJSON,toJSObject)
import Text.JSON(encode,showJSON,makeObj)
import System.IO.Silently(hCapture)
import System.Process(readProcessWithExitCode)
import System.Exit(ExitCode(..))
@@ -35,6 +34,7 @@ import qualified ExampleService as ES
import Data.Version(showVersion)
import Paths_gf(getDataDir,version)
import GF.Infra.BuildInfo (buildInfo)
import SimpleEditor.Convert(parseModule)
import RunHTTP(cgiHandler)
--logFile :: FilePath
@@ -105,6 +105,7 @@ handle state0 cache execute1
-- "/stop" ->
-- "/start" ->
"/gfshell" -> inDir qs $ look "command" . command
"/parse" -> parse qs
"/cloud" -> inDir qs $ look "command" . cloud
'/':rpath ->
case (takeDirectory path,takeFileName path,takeExtension path) of
@@ -157,6 +158,8 @@ handle state0 cache execute1
let state' = maybe state (flip (M.insert dir) state) st'
return (state',ok200 output)
parse qs = return (state,json200 (makeObj (map parseModule qs)))
cloud dir cmd qs =
case cmd of
"make" -> make dir qs
@@ -226,14 +229,12 @@ handle state0 cache execute1
-- * Dynamic content
jsonresult cwd dir cmd (ecode,stdout,stderr) files =
toJSObject [
field "errorcode" (if ecode==ExitSuccess then "OK" else "Error"),
field "command" cmd,
field "output" (unlines [rel stderr,rel stdout]),
field "minibar_url" ("/minibar/minibar.html?"++dir++pgf)]
makeObj [
prop "errorcode" (if ecode==ExitSuccess then "OK" else "Error"),
prop "command" cmd,
prop "output" (unlines [rel stderr,rel stdout]),
prop "minibar_url" ("/minibar/minibar.html?"++dir++pgf)]
where
field n v = (n,showJSON v)
pgf = case files of
(abstract,_):_ -> "%20"++dropExtension abstract++".pgf"
_ -> ""
@@ -390,4 +391,6 @@ inputs = queryToArguments . fixplus
decode '+' = "%20" -- httpd-shed bug workaround
decode c = [c]
mapFst f xys = [(f x,y)|(x,y)<-xys]
mapFst f xys = [(f x,y)|(x,y)<-xys]
prop n v = (n,showJSON v)

View File

@@ -0,0 +1,71 @@
{-# LANGUAGE NoMonomorphismRestriction #-}
module SimpleEditor.Convert where
import Control.Monad(unless,foldM,ap)
import qualified Data.Map as Map
import qualified Data.ByteString.Char8 as BS(pack)
import Text.JSON(encode,makeObj)
--import GF.Compile.GetGrammar (getSourceModule)
--import GF.Infra.Option(noOptions)
import GF.Infra.Ident(showIdent)
--import GF.Infra.UseIO(appIOE)
import GF.Grammar.Grammar
import GF.Grammar.Parser(runP,pModDef)
import GF.Grammar.Lexer(Posn(..))
import GF.Data.ErrM
import SimpleEditor.Syntax
import SimpleEditor.JSON
parseModule (path,source) =
prop path $
case runP pModDef (BS.pack source) of
Left (Pn l c,msg) ->
makeObj [prop "error" msg,
prop "location" (show l++":"++show c)]
Right mod -> case convAbstract mod of
Ok g -> makeObj [prop "converted" g]
Bad msg -> makeObj [prop "parsed" msg]
{-
convAbstractFile path =
appIOE (fmap encode . convAbstract =<< getSourceModule noOptions path)
-}
convAbstract (modid,src) =
do unless (isModAbs src) $ fail "Abstract syntax expected"
unless (isCompleteModule src) $ fail "A complete abstract syntax expected"
extends <- convExtends (mextend src)
(cats,funs) <- convJments (jments src)
let startcat = head (cats++["-"]) -- !!!
return $ Grammar (convId modid) extends (Abstract startcat cats funs) []
convExtends = mapM convExtend
convExtend (modid,MIAll) = return (convId modid)
convExtend _ = fail "unsupported module extension"
convJments jments = foldM convJment ([],[]) (Map.toList jments)
convJment (cats,funs) (name,jment) =
case jment of
AbsCat octx -> do unless (null (maybe [] unLoc octx)) $
fail "category with context"
let cat = convId name
return (cat:cats,funs)
AbsFun (Just lt) _ oeqns _ -> do unless (null (maybe [] id oeqns)) $
fail "function with equations"
let f = convId name
typ <- convType (unLoc lt)
let fun = Fun f typ
return (cats,fun:funs)
_ -> fail $ "unsupported judgement form: "++show jment
convType (Prod _ _ t1 t2) = (:) `fmap` convSimpleType t1 `ap` convType t2
convType t = (:[]) `fmap` convSimpleType t
convSimpleType (Vr id) = return (convId id)
convSimpleType t = fail "unsupported type"
convId = showIdent

View File

@@ -0,0 +1,47 @@
module SimpleEditor.JSON where
import Text.JSON
import SimpleEditor.Syntax
instance JSON Grammar where
showJSON (Grammar name extends abstract concretes) =
makeObj [prop "basename" name,
prop "extends" extends,
prop "abstract" abstract,
prop "concretes" concretes]
instance JSON Abstract where
showJSON (Abstract startcat cats funs) =
makeObj [prop "startcat" startcat,
prop "cats" cats,
prop "funs" funs]
instance JSON Fun where showJSON (Fun name typ) = signature name typ
instance JSON Param where showJSON (Param name rhs) = definition name rhs
instance JSON Oper where showJSON (Oper name rhs) = definition name rhs
signature name typ = makeObj [prop "name" name,prop "type" typ]
definition name rhs = makeObj [prop "name" name,prop "rhs" rhs]
instance JSON Concrete where
showJSON (Concrete langcode opens params lincats opers lins) =
makeObj [prop "langcode" langcode,
prop "opens" opens,
prop "params" params,
prop "lincats" lincats,
prop "opers" opers,
prop "lins" lins]
instance JSON Lincat where
showJSON (Lincat cat lintype) =
makeObj [prop "cat" cat,prop "type" lintype]
instance JSON Lin where
showJSON (Lin fun args lin) =
makeObj [prop "fun" fun,
prop "args" args,
prop "lin" lin]
prop name v = (name,showJSON v)

View File

@@ -0,0 +1,39 @@
{-
Abstract syntax for the small subset of GF grammars supported
in gfse, the JavaScript-based simple grammar editor.
-}
module SimpleEditor.Syntax where
type Id = String -- all sorts of identifiers
type ModId = Id -- module name
type Cat = Id -- category name
type FunId = Id -- function name
type Type = [Cat] -- [Cat_1,...,Cat_n] means Cat_1 -> ... -> Cat_n
data Grammar = Grammar { basename :: ModId,
extends :: [ModId],
abstract :: Abstract,
concretes:: [Concrete] }
deriving Show
data Abstract = Abstract { startcat:: Cat, cats:: [Cat], funs:: [Fun] }
deriving Show
data Fun = Fun { fname:: FunId, ftype:: Type }
deriving Show
data Concrete = Concrete { langcode:: Id,
opens:: [ModId],
params:: [Param],
lincats:: [Lincat],
opers:: [Oper],
lins:: [Lin] }
deriving Show
data Param = Param {pname:: Id, prhs:: String} deriving Show
data Lincat = Lincat {cat :: Cat, lintype:: Term} deriving Show
data Oper = Oper {oname:: Lhs, orhs:: Term} deriving Show
data Lin = Lin {fun :: FunId, args:: [Id], lin:: Term} deriving Show
type Lhs = String -- name and type of oper,
-- e.g "regN : Str -> { s:Str,g:Gender} ="
type Term = String -- arbitrary GF term (not parsed by the editor)

View File

@@ -193,6 +193,12 @@ function gfshell(cmd,cont) {
})
}
// Check the syntax of a source module
function check_module(path,source,cont) {
var enc=encodeURIComponent;
http_get_json("/parse?"+enc(path)+"="+enc(source),cont)
}
// Check the syntax of an expression
function check_exp(s,cont) {
function check(gf_message) {

View File

@@ -453,7 +453,7 @@ function draw_abstract(g) {
timestamp(g.abstract);
save_grammar(g);
}
return div_id("file",
var file=div_id("file",
[kw("abstract "),ident(g.basename),sep(" = "),
draw_timestamp(g.abstract),
draw_extends(g),
@@ -462,6 +462,65 @@ function draw_abstract(g) {
indent(draw_cats(g))]),
extensible([kw_fun,
indent_sortable(draw_funs(g),sort_funs)])])]);
if(navigator.onLine) {
var mode_button=text_mode(g,file);
insertBefore(mode_button,file.firstChild)
}
return file;
}
function text_mode(g,file) {
var path=g.basename+".gf"
function switch_to_guided_mode() {
edit_grammar(g); // !!
}
function store_parsed(parse_results) {
var dst=compiler_output;
var msg=parse_results[path];
if(dst) dst.innerHTML=""
console.log(msg)
if(dst && msg.error)
dst.appendChild(span_class("error_message",
text(msg.location+": "+msg.error)))
else if(dst && msg.parsed)
dst.innerHTML=
"Accepted by GF, but not by this editor ("+msg.parsed+")"
else if(msg.converted) {
var gnew=msg.converted;
g.abstract=gnew.abstract;
g.extends=gnew.extends;
timestamp(g.abstract);
save_grammar(g);
}
else if(dst) dst.innerHTML="unexpected parse result";
}
var last_source=show_abstract(g);
function parse(source) {
if(source!=last_source) {
if(navigator.onLine) {
//compiler_output.innerHTML="";
last_source=source;
check_module(path,source,store_parsed)
}
else if(compiler_output)
compiler_output.innerHTML="Offline, edits will not be saved"
}
}
function switch_to_text_mode() {
var ta=node("textarea",{class:"text_mode",rows:25,cols:80},
[text(show_abstract(g))])
var timeout;
ta.onkeyup=function() {
if(timeout) clearTimeout(timeout);
timeout=setTimeout(function(){parse(ta.value)},400)
}
var mode_button=div_class("right",[button("Guided mode",switch_to_guided_mode)])
file.innerHTML="";
appendChildren(file,[mode_button,ta])
ta.focus();
}
var mode_button=div_class("right",[button("Text mode",switch_to_text_mode)])
return mode_button;
}
function add_cat(g,el) {