1
0
forked from GitHub/gf-core

Adding the prototype GF editor for simple multilingual grammars

This commit is contained in:
hallgren
2011-02-17 14:38:46 +00:00
parent a533b68c55
commit 60980e40e4
15 changed files with 1289 additions and 0 deletions

BIN
src/editor/simple/P/w1s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/editor/simple/P/w2s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/editor/simple/P/w3s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
src/editor/simple/P/w4s.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

28
src/editor/simple/TODO Normal file
View File

@@ -0,0 +1,28 @@
+ Safety question before deleting a grammar
+ Check identifier syntax
+ Allow lincat for deleted cat to be deleted
+ Allow lin for deleted fun to be deleted
+ Apply category alpha conversion in concrete syntax
+ Remove a concrete syntax
+ Apply function alpha conversion in concrete syntax
+ Change lhs of lins when function type is changed
- Allow languages other than the ones in the given list to be added
+ Export as plain text
- Allow definitions to be reordered
+ 1. possibility to compile the grammar set, returning a URL to a translator app
- 2. possibility to import modules - both resource libraries and user's own
auxiliaries
- 3. possibility to upload own modules
+ 4. access to the created files in an on-line shell (making testing possible)
- 5. rule-to-rule type checking and guidance (e.g. with library oper
suggestions)
- Try grammars in the Translation Quiz
+ compile only the uploaded grammar even if other grammars are present
+ 'flags startcat' is needed for grammars with only one category (since the
default startcat is S, even if it doesn't exist)
- Bug! After adding a 2nd def of a fun with a different type and then deleting
the old fun, the corresponding lin will have the wrong lhs.

View File

@@ -0,0 +1,157 @@
<!DOCTYPE HTML>
<html>
<head>
<title>About: GF online editor for simple multilingual grammars</title>
<link rel=stylesheet href="editor.css">
<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
<meta name = "viewport" content = "width = device-width">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta charset="UTF-8">
<script type="text/javascript" src="slideshow.js"></script>
</head>
<body>
<h1>GF online editor for simple multilingual grammars</h1>
<div class=right>
<div class=slideshow>
<img onload="start_slideshow(this,{delay:5,fade:0.3})" src="P/w1s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w2s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w3s.jpg" alt="[GF online editor screen shoot]">
<img class=hidden src="P/w4s.jpg" alt="[GF online editor screen shoot]">
</div>
</div>
<h2>About</h2>
Traditionally, <a href="http://www.grammaticalframework.org/">GF</a>
grammars are created in a text editor and tested in the
GF shell. Text editors know very little (if anything) about the syntax of
GF grammars, and thus provide little guidance for novice GF users. Also, the
grammar author has to download and install the GF software on his/her own
computer.
<p>
In contrast, the
<a href="."><em>GF online editor for simple multilingual grammars</em></a>
is available online, making it easier to get started. All that
is needed is a reasonably modern web browser. Even Android and iOS devices
can be used.
<p>
The editor
also guides the grammar author by showing a skeleton grammar file and
hinting how the parts should be filled in. When a new part is added to the
grammar, it is immediately checked for errors.
<p>
Editing operations are accessed by clicking on editing symbols embedded
in the grammar display:
<span class=more>+</span>=Add an item,
<span class=delete>×</span>=Delete an item,
<span class=edit>%</span>=Edit an item.
These are revealed when hovering over items. On touch devices, hovering is
in some cases simulated by tapping, but there is also a button at the bottom
of the display to "Enabile editing on touch devices" that reveals all editing
symbols.
<p>
In spite of its name, the editor runs entierly in the web
browser, so once you have opened the web page, you can
<strong>continue editing</strong> grammars even while you are
<strong>offline</strong>.
<h3>Limitations</h3>
<p>
At the moment, the editor supports only a small subset of the GF grammar
notation.
Proper error checking is done for abstract syntax, but not (yet) for concrete
syntax. The resource grammar library is not available.
<p>
The grammars created with this editor always consists of one file for the
abstract syntax, and one file for each concrete syntax.
<h4>Abstract syntax</h4>
The supported abstract syntax corresponds to context-free grammars
(no dependent types). The definition of an abstract syntax is limited to
<ul>
<li>a list of <em>category names</em>,
<var>Cat<sub>1</sub></var> ; ... ; <var>Cat<sub>n</sub></var>,
<li>a list of <em>functions</em> of the form
<var>Fun</var> : <var>Cat<sub>1</sub></var> -> ... ->
<var>Cat<sub>n</sub></var>,
<li>and a <em>start category</em>.
</ul>
Available editing operations:
<ul>
<li>Categories can be added, removed and renamed. When renaming a category,
occurences of it in function types will be updated accordingly.
<li>Functions can be added, removed and edited. Concrete syntaxes are updated
to reflect changes.
</ul>
Error checks:
<ul>
<li>Syntactically incorrect function definitions are refused.
<li>Semantic problem such as duplicated definitions or references to
undefined categories, are highlighted.
</ul>
<h4>Concrete syntax</h4>
At the moment, concrete syntax definitions are limited to
<ul>
<li><em>parameter types</em>,
<var>P</var> = <var>C<sub>1</sub></var> | ... |<var>C<sub>n</sub></var>,
<li><em>linearization types</em> for the categories in the abstract syntax,
<li><em>operation definitions</em>, <var>op</var> = <var>expr</var>,
<li>and <em>linearizations</em> for the functions in the abstract syntax.
</ul>
Available editing operations:
<ul>
<li>The LHSs of the linearization types and linearizations are determined by
the abstract syntax and do not need to be entered manually.
The RHSs can
be edited.
<li>Parameter types can be added, removed and edited.
<li>Operation definitons can be added, removed and edited.
</ul>
Error checks:
<ul>
<li>The RHSs in the concrete syntax are not checked for errors. Arbitrary
strings can be entered.
</ul>
<h3>Future work</h3>
This prototype gives an idea of how a web based GF grammar editor could work.
We do not expect to create a full implementation of GF that runs in the
web browser, but let the editor communicate with a server running GF.
<p>
By developing a GF server with an appropriate API, it should
be possible to extend the editor to support a larger fragment of GF,
to do proper error checking, and to allow grammars to be tested in the GF
shell or the minibar.
<p>
Grammars are currently stored locally in the browser, but a future version
could allow grammars to be stored "in the cloud", allowing the same grammars
to be accessed from multiple devices.
<hr>
<div class=modtime><small>
<!-- hhmts start --> Last modified: Mon Jan 24 17:20:37 CET 2011 <!-- hhmts end -->
</small></div>
<address>
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt="">
</address>
</body> </html>

View File

@@ -0,0 +1,72 @@
body { background: #eee; }
h1 { font-size: 175%; }
h1,h2,h3,h4,small { font-family: sans-serif; }
h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
#editor { max-width: 50em; }
div.grammar { border: 1px solid black; background: white; background: #9df; }
div.files { margin: 0 8px 8px 8px; }
div#file { border: 2px solid #009; border-top-width: 0; }
pre.plain { border: 2px solid #009; }
div#file, pre.plain { background: white; padding: 0.6ex; }
.slideshow .hidden { display: none; }
img.right, div.right, div.modtime { float: right; }
.modtime { color: #999; white-space: nowrap; }
div.namebar { background: #9df; }
div.namebar table { width: 100%; }
.namebar h3 { margin: 0; color: #009; }
td.right { text-align: right; }
.kw { font-weight: bold; font-family: sans-serif; color: #009; }
.sep { font-weight: bold; color: #009; }
div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
.more, .delete { font-weight: bold; font-family: sans-serif; }
.more, .delete, .edit { cursor: pointer; }
.hover .more, .hover .delete, .hover .edit { visibility: hidden }
.hover .hidden, .nohover .ifhover { display: none; }
.editable:hover, .deletable:hover { background: #ff9; }
.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete
{ visibility: visible; }
.more { color: green; }
.edit { color: orange; }
.delete { color: red; }
.error_message,.inError { color: red; }
.template, .template .sep { color: #999; }
form { display: inline-block; }
table.tabs {
width: 100%;
border-width: 0; border-spacing: 0; empty-cells: show;
}
table.tabs td { text-align: center; border: 2px solid #009; padding: 2px; }
table.tabs td.active { background: white; border-bottom-width: 0; }
table.tabs td.inactive {
background: #cef;
border-top-color: #66c; border-left-color: #66c; border-right-color: #66c;
}
table.tabs td.gap
{ border-top-width: 0; border-left-width: 0; border-right-width: 0; }
table.tabs input[type=button] {
border: 0;
background: inherit;
color: #009;
font-size: inherit;
font-weight: bold;
/*text-decoration: underline;*/
}

789
src/editor/simple/editor.js Normal file
View File

@@ -0,0 +1,789 @@
var editor=element("editor");
/* -------------------------------------------------------------------------- */
function div_id(id,cs) { return node("div",{id:id},cs); }
function div_class(cls,cs) { return node("div",{"class":cls},cs); }
function a(url,linked) { return node("a",{href:url},linked); }
function ul(lis) { return node("ul",{},lis); }
function li(xs) { return node("li",{},xs); }
function table(rows) { return node("table",{},rows); }
function td_right(cs) { return node("td",{"class":"right"},cs); }
function jsurl(js) { return "javascript:"+js; }
function insertAfter(el,ref) {
ref.parentNode.insertBefore(el,ref.nextSibling);
}
/* -------------------------------------------------------------------------- */
function initial_view() {
var current=local.get("current");
if(current>0) open_grammar(current-1);
else draw_grammar_list();
//debug(local.get("dir","no server directory yet"));
}
function draw_grammar_list() {
local.put("current",0);
editor.innerHTML="";
editor.appendChild(node("h3",{},[text("Your grammars")]));
var gs=ul([]);
function del(i) { return function () { delete_grammar(i); } }
for(var i=0;i<local.count;i++) {
var grammar=local.get(i,null);
if(grammar && grammar.basename) {
var link=a(jsurl("open_grammar("+i+")"),[text(grammar.basename)]);
gs.appendChild(
li([deletable(del(i),link,"Delete this grammar")]))
}
}
if(local.get("count",null)==null)
editor.appendChild(text("You have not created any grammars yet."));
else if(local.count==0)
editor.appendChild(text("Your grammar list is empty."));
editor.appendChild(gs);
editor.appendChild(
ul([li([a(jsurl("new_grammar()"),[text("New grammar")])])]));
//editor.appendChild(text(local.count));
}
function new_grammar() {
var g={basename:"Unnamed",
abstract:{cats:[],funs:[]},
concretes:[]}
edit_grammar(g);
}
function delete_grammar(i) {
var g=local.get(i);
var ok=confirm("Do you really want to delete the grammar "+g.basename+"?")
if(ok) {
local.remove(i);
while(local.count>0 && !local.get(local.count-1))
local.count--;
initial_view();
}
}
function open_grammar(i) {
var g=local.get(i);
g.index=i;
local.put("current",i+1);
edit_grammar(g);
}
function close_grammar(g) { save_grammar(g); draw_grammar_list(); }
function reload_grammar(g) { save_grammar(g); edit_grammar(g); }
function save_grammar(g) {
if(g.index==null) g.index=local.count++;
local.put(g.index,g);
}
function edit_grammar(g) {
editor.innerHTML="";
editor.appendChild(draw_grammar(g));
}
function draw_grammar(g) {
var files=div_class("files",[draw_filebar(g),draw_file(g)]);
return div_class("grammar",[draw_namebar(g,files),files])
}
function draw_namebar(g,files) {
return div_class("namebar",
[table([tr([td(draw_name(g)),
td_right([draw_plainbutton(g,files),
upload_button(g),
draw_closebutton(g)])])])])
}
function draw_name(g) {
return editable("h3",text(g.basename),g,edit_name,"Rename grammar");
}
function draw_closebutton(g) {
var b=button("X",function(){close_grammar(g);});
b.title="Save and Close this grammar";
return b;
}
function draw_plainbutton(g,files) {
var b2;
function show_editor() { edit_grammar(g); }
function show_plain() {
files.innerHTML="<pre class=plain>"+show_grammar(g)+"</pre>"
b.style.display="none";
if(b2) b2.style.display="";
else {
b2=button("Show editor",show_editor);
insertAfter(b2,b);
}
}
var b=button("Show plain",show_plain);
b.title="Show plain text representaiton of the grammar";
return b;
}
function upload_button(g) {
var b=button("Upload",function(){upload(g);});
b.title="Upload the grammar to the server to check it in GF and test it in the minibar";
return b;
}
function lang(code,name) { return { code:code, name:name} }
function lang1(name) {
var ws=name.split("/");
return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]);
}
var languages =
map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" "));
//languages.push(lang("Other","Other"));
var langname={};
//for(var i=0;i<languages.length;i++)
for(var i in languages)
langname[languages[i].code]=languages[i].name
function add_concrete(g,el) {
var file=element("file");
file.innerHTML="";
var dc={};
// for(var i=0;i<g.concretes.length;i++)
for(var i in g.concretes)
dc[g.concretes[i].langcode]=true;
var list=[]
// for(var i=0;i<languages.length;i++) {
for(var i in languages) {
var l=languages[i], c=l.code;
if(!dc[c])
list.push(li([a(jsurl("add_concrete2("+g.index+",'"+c+"')"),
[text(l.name)])]));
}
file.appendChild(text("Pick a language for the new concrete syntax:"));
file.appendChild(node("ul",{},list));
}
function new_concrete(code) {
return { langcode:code,params:[],lincats:[],opers:[],lins:[] };
}
function add_concrete2(ix,code) {
var g=local.get(ix);
var cs=g.concretes;
var i;
for(var i=0;i<cs.length;i++) if(cs[i].langcode==code) break;
if(i==cs.length) cs.push(new_concrete(code))
save_grammar(g);
open_concrete(g,i);
}
function open_abstract(g) { g.current=0; reload_grammar(g); }
function open_concrete(g,i) { g.current=i+1; reload_grammar(g); }
function td_gap(c) {return wrap_class("td","gap",c); }
function gap() { return td_gap(text(" ")); }
function tab(active,link) {
return wrap_class("td",active ? "active" : "inactive",link);
}
function delete_concrete(g,ci) {
var c=g.concretes[ci];
var ok=c.params.length==0 && c.lincats.length==0 && c.opers.length==0
&& c.lins.length==0
|| confirm("Do you really want to delete the concrete syntax for "+
langname[c.langcode]+"?");
if(ok) {
g.concretes=delete_ix(g.concretes,ci)
if(g.current && g.current-1>=ci) g.current--;
reload_grammar(g);
}
}
function draw_filebar(g) {
var cur=(g.current||0)-1;
var filebar = empty_class("tr","extensible")
filebar.appendChild(gap());
filebar.appendChild(
tab(cur== -1,button("Abstract",function(){open_abstract(g);})));
var cs=g.concretes;
function del(ci) { return function() { delete_concrete(g,ci); }}
function open_conc(i) { return function() {open_concrete(g,1*i); }}
// for(var i=0;i<cs.length;i++)
for(var i in cs) {
filebar.appendChild(gap());
filebar.appendChild(
tab(i==cur,deletable(del(i),button(langname[cs[i].langcode],open_conc(i)),"Delete this concrete syntax")));
}
filebar.appendChild(td_gap(more(g,add_concrete,"Add a concrete syntax")));
return wrap_class("table","tabs",filebar);
}
function draw_file(g) {
return g.current>0 // && g.current<=g.concretes.length
? draw_concrete(g,g.current-1)
: draw_abstract(g);
}
function draw_startcat(g) {
var abs=g.abstract;
var startcat = abs.startcat || abs.cats[0];
function opt(cat) { return option(cat,cat); }
var m= node("select",{},map(opt,abs.cats));
m.value=startcat;
m.onchange=function() { abs.startcat=m.value; save_grammar(g); }
return indent([kw("flags startcat"),sep(" = "),m]);
}
function draw_abstract(g) {
var kw_cat = kw("cat");
kw_cat.title = "The categories (nonterminals) of the grammar are enumerated here.";
var kw_fun = kw("fun");
kw_fun.title = "The functions (productions) of the grammar are enumerated here.";
var flags=g.abstract.startcat || g.abstract.cats.length>1
? draw_startcat(g)
: text("");
return div_id("file",
[kw("abstract "),ident(g.basename),sep(" = "),
flags,
indent([extensible([kw_cat,
indent(draw_cats(g))]),
extensible([kw_fun,
indent(draw_funs(g))])])]);
}
function add_cat(g,el) {
function add(s) {
var cats=s.split(/\s*(?:\s|[;])\s*/); // allow separating spaces or ";"
if(cats.length>0 && cats[cats.length-1]=="") cats.pop();
for(var i in cats) {
var err=check_name(cats[i],"Category");
if(err) return err;
}
for(var i in cats) g.abstract.cats.push(cats[i]);
reload_grammar(g);
return null;
}
string_editor(el,"",add);
}
function delete_ix(old,ix) {
var a=[];
// for(var i=0;i<old.length;i++) if(i!=ix) a.push(old[i]);
for(var i in old) if(i!=ix) a.push(old[i]);
return a;
}
function delete_cat(g,ix) {
with(g.abstract) cats=delete_ix(cats,ix);
reload_grammar(g);
}
function rename_cat(g,el,cat) {
function ren(newcat) {
if(newcat!="" && newcat!=cat) {
var err=check_name(newcat,"Category");
if(err) return err;
var dc=defined_cats(g);
if(dc[newcat]) return newcat+" is already in use";
g=rename_category(g,cat,newcat);
reload_grammar(g);
}
return null;
}
string_editor(el,cat,ren);
}
function draw_cats(g) {
var cs=g.abstract.cats;
var es=[];
var defined={};
function eident(cat) {
function ren(g,el) { rename_cat(g,el,cat); }
return editable("span",ident(cat),g,ren,"Rename category");
}
function check(cat,el) {
return ifError(defined[cat],"Same category named twice",el);
}
function del(i) { return function() { delete_cat(g,i); }}
// for(var i=0; i<cs.length;i++) {
for(var i in cs) {
es.push(deletable(del(i),check(cs[i],eident(cs[i])),"Delete this category"));
defined[cs[i]]=true;
es.push(sep("; "));
}
es.push(more(g,add_cat,"Add more categories"));
return es;
}
function add_fun(g,el) {
function add(s) {
var p=parse_fun(s);
if(p.ok) {
g.abstract.funs.push(p.ok);
reload_grammar(g);
return null;
}
else
return p.error
}
string_editor(el,"",add);
}
function edit_fun(i) {
return function (g,el) {
function replace(s) {
var p=parse_fun(s);
if(p.ok) {
var old=g.abstract.funs[i];
g.abstract.funs[i]=p.ok;
if(p.ok.name!=old.name) g=rename_function(g,old.name,p.ok.name);
if(show_type(p.ok.type)!=show_type(old.type))
g=change_lin_lhs(g,p.ok);
reload_grammar(g);
return null;
}
else
return p.error;
}
string_editor(el,show_fun(g.abstract.funs[i]),replace);
}
}
function delete_fun(g,ix) {
with(g.abstract) funs=delete_ix(funs,ix);
reload_grammar(g);
}
function draw_funs(g) {
var funs=g.abstract.funs;
var es=[];
var dc=defined_cats(g);
var df={};
function del(i) { return function() { delete_fun(g,i); }}
function draw_efun(i,df) {
return editable("span",draw_fun(funs[i],dc,df),g,edit_fun(i),"Edit this function");
}
// for(var i=0;i<funs.length;i++) {
for(var i in funs) {
es.push(div_class("fun",[deletable(del(i),draw_efun(i,df),"Delete this function")]));
df[funs[i].name]=true;
}
es.push(more(g,add_fun,"Add a new function"));
return es;
}
function draw_fun(fun,dc,df) {
function check(el) {
return ifError(dc[fun.name],
"Function names must be distinct from category names",
ifError(df[fun.name],"Same function defined twice",el));
}
return node("span",{},
[check(ident(fun.name)),sep(" : "),draw_type(fun.type,dc)]);
}
function draw_type(t,dc) {
var el=empty("span");
function check(t,el) {
return ifError(!dc[t],"Undefined category",el);
}
// for(var i=0;i<t.length;i++) {
for(var i in t) {
if(i>0) el.appendChild(sep(" → "));
el.appendChild(check(t[i],ident(t[i])));
}
return el;
}
function edit_name(g,el) {
function change_name(name) {
if(name!=g.basename && name!="") {
var err=check_name(name,"Grammar");
if(err) return err;
g.basename=name
reload_grammar(g);
}
return null;
}
string_editor(el,g.basename,change_name)
}
/* -------------------------------------------------------------------------- */
function draw_concrete(g,i) {
var conc=g.concretes[i];
return div_id("file",
[kw("concrete "),ident(g.basename+conc.langcode),
kw(" of "),ident(g.basename),sep(" = "),
indent([extensible([kw("param"),draw_params(g,i)])]),
indent([kw("lincat"),draw_lincats(g,i)]),
indent([extensible([kw("oper"),draw_opers(g,i)])]),
indent([kw("lin"),draw_lins(g,i)])
])
}
function draw_param(p,dp) {
function check(el) {
return ifError(dp[p.name],"Same parameter type defined twice",el);
}
return node("span",{},[check(ident(p.name)),sep(" = "),text(p.rhs)]);
}
function add_param(g,ci,el) {
function add(s) {
var p=parse_param(s);
if(p.ok) {
g.concretes[ci].params.push(p.ok);
reload_grammar(g);
return null;
}
else
return p.error
}
string_editor(el,"",add);
}
function edit_param(ci,i) {
return function (g,el) {
function replace(s) {
var p=parse_param(s);
if(p.ok) {
g.concretes[ci].params[i]=p.ok;
reload_grammar(g);
return null;
}
else
return p.error;
}
string_editor(el,show_param(g.concretes[ci].params[i]),replace);
}
}
function delete_param(g,ci,ix) {
with(g.concretes[ci]) params=delete_ix(params,ix);
reload_grammar(g);
}
function draw_params(g,ci) {
var conc=g.concretes[ci];
conc.params || (conc.params=[]);
var params=conc.params;
var es=[];
var dp={};
function del(i) { return function() { delete_param(g,ci,i); }}
function draw_eparam(i,dp) {
return editable("span",draw_param(params[i],dp),g,edit_param(ci,i),"Edit this parameter type");
}
for(var i in params) {
es.push(div_class("param",[deletable(del(i),draw_eparam(i,dp),"Delete this parameter type")]));
dp[params[i].name]=true;
}
es.push(more(g,function(g,el) { return add_param(g,ci,el)},
"Add a new parameter type"));
return indent(es);
}
function delete_lincat(g,ci,cat) {
var i;
var c=g.concretes[ci];
for(i=0;i<c.lincats.length && c.lincats[i].cat!=cat;i++);
if(i<c.lincats.length) c.lincats=delete_ix(c.lincats,i);
reload_grammar(g);
}
function draw_lincats(g,i) {
var conc=g.concretes[i];
function edit(c) {
return function(g,el) {
function ok(s) {
if(c.template) conc.lincats.push({cat:c.cat,type:s});
else c.type=s;
reload_grammar(g);
return null;
}
string_editor(el,c.type,ok)
}
}
function del(c) { return function() { delete_lincat(g,i,c); } }
function dlc(c,cls) {
var t=editable("span",text(c.type),g,edit(c),"Edit lincat for "+c.cat);
return node("span",{"class":cls},
[ident(c.cat),sep(" = "),t]);
}
var dc=defined_cats(g);
function draw_lincat(c) {
var cat=c.cat;
var err=!dc[cat];
var l1=dlc(c,"lincat");
var l2= err ? deletable(del(cat),l1,"Delete this lincat") : l1;
var l=ifError(err,"lincat for undefined category",l2);
delete dc[cat];
return wrap("div",l);
}
function dtmpl(c) {
return wrap("div",dlc({cat:c,type:"",template:true},"template")); }
var lcs=map(draw_lincat,conc.lincats);
for(var c in dc)
lcs.push(dtmpl(c));
return indent(lcs);
}
/* -------------------------------------------------------------------------- */
function draw_oper(p,dp) {
function check(el) {
return ifError(dp[p.name],"Same operator definition defined twice",el);
}
return node("span",{},[check(ident(p.name)),text(" "),text(p.rhs)]);
}
function add_oper(g,ci,el) {
function add(s) {
var p=parse_oper(s);
if(p.ok) {
g.concretes[ci].opers.push(p.ok);
reload_grammar(g);
return null;
}
else
return p.error
}
string_editor(el,"",add);
}
function edit_oper(ci,i) {
return function (g,el) {
function replace(s) {
var p=parse_oper(s);
if(p.ok) {
g.concretes[ci].opers[i]=p.ok;
reload_grammar(g);
return null;
}
else
return p.error;
}
string_editor(el,show_oper(g.concretes[ci].opers[i]),replace);
}
}
function delete_oper(g,ci,ix) {
with(g.concretes[ci]) opers=delete_ix(opers,ix);
reload_grammar(g);
}
function draw_opers(g,ci) {
var conc=g.concretes[ci];
conc.opers || (conc.opers=[]);
var opers=conc.opers;
var es=[];
var dp={};
function del(i) { return function() { delete_oper(g,ci,i); }}
function draw_eoper(i,dp) {
return editable("span",draw_oper(opers[i],dp),g,edit_oper(ci,i),"Edit this operator definition");
}
for(var i in opers) {
es.push(div_class("oper",[deletable(del(i),draw_eoper(i,dp),"Delete this operator definition")]));
dp[opers[i].name]=true;
}
es.push(more(g,function(g,el) { return add_oper(g,ci,el)},
"Add a new operator definition"));
return indent(es);
}
function delete_lin(g,ci,fun) {
var i;
var c=g.concretes[ci];
for(i=0;i<c.lins.length && c.lins[i].fun!=fun;i++);
if(i<c.lins.length) c.lins=delete_ix(c.lins,i);
reload_grammar(g);
}
/* -------------------------------------------------------------------------- */
function arg_names(type) {
function lower(s) { return s.toLowerCase(); }
var names=map(lower,type);
names.pop(); // remove result type
var n,count={},use={};
for(var i in names) n=names[i],count[n]=0,use[n]=0;
for(var i in names) count[names[i]]++;
function unique(n) {
return count[n]>1 ? n+(++use[n]) : n;
}
return map(unique,names);
}
function draw_lins(g,i) {
var conc=g.concretes[i];
function edit(f) {
return function(g,el) {
function ok(s) {
if(f.template)
conc.lins.push({fun:f.fun,args:f.args,lin:s});
else f.lin=s;
reload_grammar(g);
return null;
}
string_editor(el,f.lin,ok)
}
}
function del(fun) { return function () { delete_lin(g,i,fun); } }
function dl(f,cls) {
var l=[ident(f.fun)]
for(var i in f.args) {
l.push(text(" "));
l.push(ident(f.args[i]));
}
l.push(sep(" = "));
var t=editable("span",text(f.lin),g,edit(f),"Edit lin for "+f.fun);
l.push(t);
return node("span",{"class":cls},l);
}
var df=defined_funs(g);
function draw_lin(f) {
var fun=f.fun;
var err= !df[fun];
var l= err ? deletable(del(fun),dl(f,"lin"),"Delete this function") : dl(f,"lin")
var l=ifError(err,"Function "+fun+" is not part of the abstract syntax",l);
delete df[fun];
return wrap("div",l);
}
function largs(f) {
var funs=g.abstract.funs;
for(var i=0;i<funs.length && funs[i].name!=f;i++);
return arg_names(funs[i].type);
}
function dtmpl(f) {
return wrap("div",
dl({fun:f,args:largs(f),lin:"",template:true},"template"));
}
var ls=map(draw_lin,conc.lins);
for(var f in df)
ls.push(dtmpl(f));
return indent(ls);
}
/* -------------------------------------------------------------------------- */
function upload(g) {
var dir=local.get("dir","");
if(dir) upload2(g,dir);
else ajax_http_get("upload.cgi?dir",
function(dir) {
local.put("dir",dir);
upload2(g,dir);
});
}
function upload2(g,dir) {
var form=node("form",{method:"post",action:"upload.cgi"+dir},
[hidden(g.basename,show_abstract(g))])
for(var i in g.concretes)
form.appendChild(hidden(g.basename+g.concretes[i].langcode,
show_concrete(g.basename)(g.concretes[i])));
editor.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
}
function hidden(name,value) {
return node("input",{type:"hidden",name:name,value:value},[])
}
/* -------------------------------------------------------------------------- */
function string_editor(el,init,ok) {
var p=el.parentNode;
function restore() {
e.parentNode.removeChild(e);
el.style.display="";
}
function done() {
var edited=e.it.value;
restore();
var msg=ok(edited);
if(msg) start(msg);
return false;
}
function start(msg) {
el.style.display="none";
m.innerHTML=msg;
p.insertBefore(e,el);
e.it.focus();
}
var m=empty_class("span","error_message");
var i=node("input",{name:"it",value:init},[]);
if(init.length>10) i.size=init.length+5;
var e=node("form",{},
[i,
node("input",{type:"submit",value:"OK"},[]),
button("Cancel",restore),
text(" "),
m])
e.onsubmit=done
start("");
}
function ifError(b,msg,el) { return b ? inError(msg,el) : el; }
function inError(msg,el) {
return node("span",{"class":"inError",title:msg},[el]);
}
function kw(txt) { return wrap_class("span","kw",text(txt)); }
function sep(txt) { return wrap_class("span","sep",text(txt)); }
function ident(txt) { return wrap_class("span","ident",text(txt)); }
function indent(cs) { return div_class("indent",cs); }
function extensible(cs) { return div_class("extensible",cs); }
function more(g,action,hint) {
var b=node("span",{"class":"more","title":hint || "Add more"},
[text(" + ")]);
b.onclick=function() { action(g,b); }
return b;
}
function editable(tag,cs,g,f,hint) {
var b=edit_button(function(){f(g,e)},hint);
var e=node(tag,{"class":"editable"},[cs,b]);
return e;
}
function edit_button(action,hint) {
var b=node("span",{"class":"edit","title":hint || "Edit"},[text("%")]);
b.onclick=action;
return b;
}
function deletable(del,el,hint) {
var b=node("span",{"class":"delete",title:hint || "Delete"},[text("×")])
b.onclick=del;
return node("span",{"class":"deletable"},[b,el])
}
function touch_edit() {
var b=node("input",{type:"checkbox"},[]);
function touch() {
document.body.className=b.checked ? "nohover" : "hover";
}
b.onchange=touch;
insertAfter(b,editor);
insertAfter(wrap("small",text("Enable editing on touch devices. ")),b);
}
/* --- Initialization ------------------------------------------------------- */
//document.body.appendChild(empty_id("div","debug"));
initial_view();
touch_edit();

View File

@@ -0,0 +1 @@
/* Abstract syntax for a small subset of GF grammars in JavaScript */

View File

@@ -0,0 +1,4 @@
CACHE MANIFEST
# 5
NETWORK:
*

View File

@@ -0,0 +1,42 @@
<!DOCTYPE HTML>
<html manifest="gfse.manifest">
<head>
<title>GF online editor for simple multilingual grammars</title>
<link rel=stylesheet href="editor.css">
<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
<meta name = "viewport" content = "width = device-width">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta charset="UTF-8">
</head>
<body class=hover>
<h2>GF online editor for simple multilingual grammars</h2>
<div id=editor>
</div>
<small class="hidden">
<span class=more>+</span>=Add an item,
<span class=delete>×</span>=Delete item,
<span class=edit>%</span>=Edit item.
</small>
<small class="ifhover">Hover over items for hints and editing options.</small>
<noscript>
This page does not work without JavaScript.
</noscript>
<hr>
<div class=modtime><small>
HTML
<!-- hhmts start --> Last modified: Wed Feb 16 15:10:52 CET 2011 <!-- hhmts end -->
</small></div>
<a href="about.html">About</a>
<script type="text/javascript" src="../../runtime/javascript/minibar/support.js"></script>
<script type="text/javascript" src="localstorage.js"></script>
<script type="text/javascript" src="gf_abs.js"></script>
<script type="text/javascript" src="editor.js"></script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
// We use localStorage to store the grammars,
// see http://diveintohtml5.org/storage.html
var local={
prefix:"gf.editor.simple.grammar",
get: function (name,def) {
var id=this.prefix+name
return localStorage[id] ? JSON.parse(localStorage[id]) : def;
},
put: function (name,value) {
var id=this.prefix+name;
localStorage[id]=JSON.stringify(value);
},
remove: function(name) {
var id=this.prefix+name;
localStorage.removeItem(id);
},
get count() { return this.get("count",0); },
set count(v) { this.put("count",v); }
}

12
src/editor/simple/save.hs Normal file
View File

@@ -0,0 +1,12 @@
import Monad(zipWithM_)
import System(getArgs)
main = save =<< getArgs
save [dir] =
do fs@[ns,_] <- readIO =<< getContents
save_all fs
putStrLn $ unwords [n++".gf"|n<-ns]
where
save_all [ns,cs] = zipWithM_ write1 ns cs
write1 n = writeFile (dir++"/"++n++".gf")

View File

@@ -0,0 +1,86 @@
var internet_explorer=navigator.appName=="Microsoft Internet Explorer";
/* How to change opacity in IE:
http://joseph.randomnetworks.com/archives/2006/08/16/css-opacity-in-internet-explorer-ie/
*/
var set_opacity =
internet_explorer
? function(el,o) { el.style.filter="alpha(opacity="+Math.round(o*100)+")";}
: function(el,o) { el.style.opacity=o; };
function start_slideshow(img,options) {
var p=img.parentNode;
if(p.tagName=="A") p=p.parentNode;
var is=p.getElementsByTagName("img");
if(is.length>1) {
var cur=0;
var w=img.width;
var h=img.height;
//p.style.position="relative";
p.style.minWidth=w+"px";
p.style.minHeight=h+"px";
var images=[];
for(var i=0;i<is.length;i++) {
images[i]=is[i];
var c=images[i];
if(internet_explorer) c.style.zoom=1;
c.style.position="absolute";
}
var timeout=1000*(options.delay || 5);
var ft=options.fade==null ? 1 : options.fade;
var tick=function() {
var c=images[cur];
cur= (cur+1) % images.length;
var n=images[cur];
set_opacity(n,0);
//n.style.position="static";
n.style.zIndex=1;
n.className="";
if(n.width>w) { w=n.width; p.style.minWidth=w+"px"; }
if(n.height>h) { h=n.height; p.style.minHeight=h+"px"; }
c.style.position="absolute";
c.style.zIndex=0;
fade(n,0,1,ft,function() {
if(c.width>n.width || c.height>n.height) fade(c,1,0,ft,null);
else set_opacity(c,0); });
//debug.innerHTML=w+"x"+h;
//for(var i=0;i<images.length;i++)
//debug.appendChild(text(" "+images[i].style.position));
}
//var debug=document.createElement("div");
//p.parentNode.insertBefore(debug,p);
//debug.innerHTML=w+"x"+h;
setInterval(tick,timeout);
}
//else alert("No slideshow!");
}
function fade(el,start,stop,t,after) {
// el: which element to fade
// start: starting opacity [0..1]
// stop: ending opacity [0..1]
// t: duration of fade (in seconds), default 1s
// after: function to call when done fading, optional
var dt=40; // Animation granularity, 1/40ms = 25fps
el.step=(stop-start)*dt/(1000*(t==null ? 1 : t));
el.stop=stop;
//alert("fade "+start+" "+stop+" "+el.step);
var done=function() {
clearInterval(el.timer);
el.timer=null;
if(after) after();
}
var f=function() {
var next=el.current+el.step;
if(next>=1) { next=1; done(); }
if(next<=0) { next=0; done(); }
set_opacity(el,next);
el.current=next
}
if(!el.timer) {
el.current=start;
el.timer=setInterval(f,dt);
}
}

View File

@@ -0,0 +1,77 @@
#!/bin/bash
bin=/Users/hallgren/www/bin
charset="UTF-8"
AUTOHEADER=no
. $bin/cgistart.sh
PATH=$PATH:/Users/hallgren/.cabal/bin
export LC_CTYPE="UTF-8"
style_url="editor.css"
make_dir() {
dir="$(mktemp -d ../tmp/gfse.XXXXXXXX)"
# chmod a+rxw "$dir"
chmod a+rx "$dir"
ln "$dir/../../grammars/grammars.cgi" "$dir"
}
check_grammar() {
pagestart "Uploaded"
# echo "$PATH_INFO"
files=$(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir")
cd $dir
begin pre
if gf -s -make $files 2>&1 ; then
end
h3 OK
begin ul
[ -z "$minibar" ] || { li; link "$minibar?/tmp/${dir##*/}/" "Minibar"; }
[ -z "$gfshell" ] || { li; link "$gfshell?dir=${dir##*/}" "GF Shell"; }
end
begin pre
ls -l *.pgf
else
end
begin h3 class=error_message; echo Error; end
for f in *.gf ; do
h4 "$f"
begin pre class=plain
cat -n "$f"
end
done
fi
hr
date
# begin pre ; env
endall
}
case "$REQUEST_METHOD" in
POST)
case "$PATH_INFO" in
/tmp/gfse.*)
style_url="../../$style_url"
dir="../tmp/${PATH_INFO##*/}"
check_grammar
;;
*)
make_dir
check_grammar
rm -rf "$dir"
esac
;;
GET)
case "$QUERY_STRING" in
dir) make_dir
ContentType="text/plain"
cgiheaders
echo "/tmp/${dir##*/}"
;;
*) pagestart "Error"
echo "What do you want?"
endall
esac
esac