summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/editor/simple/P/w1s.jpgbin0 -> 24313 bytes
-rw-r--r--src/editor/simple/P/w2s.jpgbin0 -> 26674 bytes
-rw-r--r--src/editor/simple/P/w3s.jpgbin0 -> 28192 bytes
-rw-r--r--src/editor/simple/P/w4s.jpgbin0 -> 25066 bytes
-rw-r--r--src/editor/simple/TODO28
-rw-r--r--src/editor/simple/about.html157
-rw-r--r--src/editor/simple/editor.css72
-rw-r--r--src/editor/simple/editor.js789
-rw-r--r--src/editor/simple/gf_abs.js1
-rw-r--r--src/editor/simple/gfse.manifest4
-rw-r--r--src/editor/simple/index.html42
-rw-r--r--src/editor/simple/localstorage.js21
-rw-r--r--src/editor/simple/save.hs12
-rw-r--r--src/editor/simple/slideshow.js86
-rw-r--r--src/editor/simple/upload.cgi77
15 files changed, 1289 insertions, 0 deletions
diff --git a/src/editor/simple/P/w1s.jpg b/src/editor/simple/P/w1s.jpg
new file mode 100644
index 000000000..f91359584
--- /dev/null
+++ b/src/editor/simple/P/w1s.jpg
Binary files differ
diff --git a/src/editor/simple/P/w2s.jpg b/src/editor/simple/P/w2s.jpg
new file mode 100644
index 000000000..23683a2bb
--- /dev/null
+++ b/src/editor/simple/P/w2s.jpg
Binary files differ
diff --git a/src/editor/simple/P/w3s.jpg b/src/editor/simple/P/w3s.jpg
new file mode 100644
index 000000000..88e2f3451
--- /dev/null
+++ b/src/editor/simple/P/w3s.jpg
Binary files differ
diff --git a/src/editor/simple/P/w4s.jpg b/src/editor/simple/P/w4s.jpg
new file mode 100644
index 000000000..8c904cdd9
--- /dev/null
+++ b/src/editor/simple/P/w4s.jpg
Binary files differ
diff --git a/src/editor/simple/TODO b/src/editor/simple/TODO
new file mode 100644
index 000000000..b9a684518
--- /dev/null
+++ b/src/editor/simple/TODO
@@ -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.
diff --git a/src/editor/simple/about.html b/src/editor/simple/about.html
new file mode 100644
index 000000000..337c453b0
--- /dev/null
+++ b/src/editor/simple/about.html
@@ -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>
diff --git a/src/editor/simple/editor.css b/src/editor/simple/editor.css
new file mode 100644
index 000000000..2c09897a0
--- /dev/null
+++ b/src/editor/simple/editor.css
@@ -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;*/
+} \ No newline at end of file
diff --git a/src/editor/simple/editor.js b/src/editor/simple/editor.js
new file mode 100644
index 000000000..6420b5eac
--- /dev/null
+++ b/src/editor/simple/editor.js
@@ -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();
diff --git a/src/editor/simple/gf_abs.js b/src/editor/simple/gf_abs.js
new file mode 100644
index 000000000..468ed91c7
--- /dev/null
+++ b/src/editor/simple/gf_abs.js
@@ -0,0 +1 @@
+/* Abstract syntax for a small subset of GF grammars in JavaScript */
diff --git a/src/editor/simple/gfse.manifest b/src/editor/simple/gfse.manifest
new file mode 100644
index 000000000..60c6f3757
--- /dev/null
+++ b/src/editor/simple/gfse.manifest
@@ -0,0 +1,4 @@
+CACHE MANIFEST
+# 5
+NETWORK:
+*
diff --git a/src/editor/simple/index.html b/src/editor/simple/index.html
new file mode 100644
index 000000000..ef3843379
--- /dev/null
+++ b/src/editor/simple/index.html
@@ -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>
diff --git a/src/editor/simple/localstorage.js b/src/editor/simple/localstorage.js
new file mode 100644
index 000000000..31201998c
--- /dev/null
+++ b/src/editor/simple/localstorage.js
@@ -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); }
+}
diff --git a/src/editor/simple/save.hs b/src/editor/simple/save.hs
new file mode 100644
index 000000000..01d3ce270
--- /dev/null
+++ b/src/editor/simple/save.hs
@@ -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")
diff --git a/src/editor/simple/slideshow.js b/src/editor/simple/slideshow.js
new file mode 100644
index 000000000..307bcd98f
--- /dev/null
+++ b/src/editor/simple/slideshow.js
@@ -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);
+ }
+}
diff --git a/src/editor/simple/upload.cgi b/src/editor/simple/upload.cgi
new file mode 100644
index 000000000..fbc5a0be1
--- /dev/null
+++ b/src/editor/simple/upload.cgi
@@ -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