mirror of
https://github.com/GrammaticalFramework/gf-core.git
synced 2026-05-06 01:32:50 -06:00
Improvements of "gf -server" mode and related setup
"gf -server" mode now contains everything needed to run the minibar and the grammar editor (including example-based grammar writing). The Setup.hs script installs the required files where gf -server can find them. These files have been moved to a new directory: src/www. The separate server program pgf-http is now obsolete.
This commit is contained in:
7
src/www/gfse/Makefile
Normal file
7
src/www/gfse/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
save: save.hs
|
||||
ghc --make save.hs
|
||||
|
||||
install::
|
||||
@make save
|
||||
rsync -avz --exclude .DS_Store P *.html *.css *.js ../../runtime/javascript/minibar/support.js *.cgi *.manifest save www.grammaticalframework.org:/usr/local/www/GF/demos/gfse
|
||||
BIN
src/www/gfse/P/1306856253_weather_06.png
Normal file
BIN
src/www/gfse/P/1306856253_weather_06.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/www/gfse/P/1307545089_weather_04.png
Normal file
BIN
src/www/gfse/P/1307545089_weather_04.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/www/gfse/P/w1s.jpg
Normal file
BIN
src/www/gfse/P/w1s.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
src/www/gfse/P/w2s.jpg
Normal file
BIN
src/www/gfse/P/w2s.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
src/www/gfse/P/w3s.jpg
Normal file
BIN
src/www/gfse/P/w3s.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
src/www/gfse/P/w4s.jpg
Normal file
BIN
src/www/gfse/P/w4s.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
43
src/www/gfse/TODO
Normal file
43
src/www/gfse/TODO
Normal file
@@ -0,0 +1,43 @@
|
||||
+ 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
|
||||
+ 2a. possibility to import modules - resource libraries
|
||||
- 2b. possibility to import modules - 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
|
||||
+ Show lincat and lin before params and opers below
|
||||
+ Create a new concrete syntax by copying an existing one.
|
||||
- Easy access to compute_concrete from the editor
|
||||
- Instead of starting with an empty grammar, start a new grammar by copying
|
||||
an example.
|
||||
+ Cloning grammars
|
||||
- Allow grammars to contain a resoure module. Create the resource module by
|
||||
factoring out common parts of the concrete syntaxes.
|
||||
- Integrate example-based concrete syntax construction (using Ramona's tool)
|
||||
- Open lexicon modules? They are not installed by default!
|
||||
DictEng, MorphalouFre, DictSwe, DictTur, DictBul, DictFun, DictLav
|
||||
|
||||
+ 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.
|
||||
|
||||
+ Bug! The startcat menu shows the first category by default, but the startcat
|
||||
flag is actually not set until a selection is made from the menu.
|
||||
|
||||
243
src/www/gfse/about.html
Normal file
243
src/www/gfse/about.html
Normal file
@@ -0,0 +1,243 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>About: GF online editor for simple multilingual grammars</title>
|
||||
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
|
||||
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
|
||||
|
||||
<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>Introduction</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 "Enable 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>Current status</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.
|
||||
|
||||
<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.
|
||||
<li>Functions can be reordered using drag-and-drop.
|
||||
</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, the concrete syntax for a language <var>L</var> is limited to
|
||||
<ul>
|
||||
<li>opening the Resource Grammar Library modules
|
||||
<code>Syntax</code><var>L</var> and <code>Paradigms</code><var>L</var>,
|
||||
<code>Lexicon</code><var>L</var> and <code>Extra</code><var>L</var>,
|
||||
<li><em>linearization types</em> for the categories in the abstract syntax,
|
||||
<li><em>linearizations</em> for the functions in the abstract syntax,
|
||||
<li><em>parameter type definitions</em>,
|
||||
<var>P</var> = <var>C<sub>1</sub></var> | ... |<var>C<sub>n</sub></var>,
|
||||
<li>and <em>operation definitions</em>, <var>op</var> = <var>expr</var>,
|
||||
<var>op</var> : <var>type</var> = <var>expr</var>,
|
||||
</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.
|
||||
<li>Definitions can be reordered (using drag-and-drop)
|
||||
</ul>
|
||||
Also,
|
||||
|
||||
<ul>
|
||||
<li>When a new concrete syntax is added to the grammar, a copy of the
|
||||
currently open concrete syntax is created, since copying and modifying
|
||||
is usually easier than creating something new from scratch.
|
||||
(If the abstract syntax is currently open, the new conrete syntax will
|
||||
start out empty.)
|
||||
<li>When adding a new concrete syntax, you normally pick one of the supported
|
||||
languages from a list. The language code and the file name is determined
|
||||
automatically. But you can also pick <em>Other</em> from the list and change
|
||||
the language code afterwards to add a concrete syntax for a language
|
||||
that is not in the list.
|
||||
</ul>
|
||||
|
||||
Error checks:
|
||||
<ul>
|
||||
<li>The RHSs in the concrete syntax are checked
|
||||
for syntactic correctness by the editor as they are entered.
|
||||
(TODO: the syntax of parameter types is not check at the moment.)
|
||||
<li>Duplicated definitions are highlighted. Checks for other
|
||||
semantic errors are delayed until the grammar is compiled.
|
||||
</ul>
|
||||
|
||||
<h3>Compiling and testing grammars</h3>
|
||||
|
||||
When pressing the <strong>Compile</strong> button, the grammar will be compiled
|
||||
with GF, and any errors not detected by the editor will be reported.
|
||||
If the grammar is free from errors the user can then
|
||||
test the grammar by clicking on links to the online GF shell, the Minibar or
|
||||
the Translation Quiz.
|
||||
|
||||
<h3><img class=right src="P/1307545089_weather_04.png" alt="">
|
||||
<img class=right src="P/1306856253_weather_06.png" alt="">Grammars in the
|
||||
cloud</h3>
|
||||
|
||||
While the editor normally stores grammars locally in the browser, it is also
|
||||
possible to store grammars in the cloud. Grammars can be stored in the cloud
|
||||
just for backup, or to make them accessible from multiple devices.
|
||||
|
||||
<p>
|
||||
There is no automatic synchronization between local grammars and the cloud.
|
||||
Instead, the user should press
|
||||
<img src="P/1306856253_weather_06.png" alt="[Cloud Upload]">
|
||||
to upload the grammars to the cloud, and press
|
||||
<img src="P/1307545089_weather_04.png" alt="[Cloud download]">
|
||||
to download grammars from the cloud. In both cases, complete grammars
|
||||
are copied and older versions at the destination will be overwritten.
|
||||
When a grammar is deleted, both the local copy and the copy in the cloud
|
||||
is deleted.
|
||||
|
||||
<p>
|
||||
Each device is initially assigned to its own unique cloud. Each device can thus
|
||||
have its own set of grammars that are not available on other devices. It is
|
||||
also possible to merge clouds and share a common set of grammars between
|
||||
multiple devices: when uploading grammars to the cloud, a link to this grammar
|
||||
cloud appears. Accessing this link from another device will cause the clouds of
|
||||
the two devices to be merged. After this, grammars uploaded from one of the
|
||||
devices can be downloaded on the other devices. Any number devices can join the
|
||||
same grammar cloud in this way.
|
||||
|
||||
<p>
|
||||
<strong>Note</strong> that while it is possible to copy grammars between
|
||||
multiple devices, there is no way to merge concurrent edits from multiple
|
||||
devices. If the same grammar is uploaded to the
|
||||
cloud from multiple devices, the last upload wins. Thus the current
|
||||
implementation is suitable for a single user switching between different
|
||||
devices, but not recommended for sharing grammars between multiple users.
|
||||
|
||||
<p>
|
||||
Also <strong>note</strong> that each grammar is assigned a unique identity
|
||||
when it is first created. Renaming a grammar does not change its identity.
|
||||
This means that name changes are propagated between devices like other changes.
|
||||
|
||||
<h3>Example-based grammar writing</h3>
|
||||
|
||||
This is work in progress...
|
||||
|
||||
<h3>Future work</h3>
|
||||
|
||||
This prototype gives an idea of how a web based GF grammar editor could work.
|
||||
While this editor is implemented in JavaScript and runs in the web browser,
|
||||
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 make more of the existing GF shell functionality
|
||||
accessible directly from the editor.
|
||||
<p>
|
||||
The current grammar cloud service is very primitive. In particular, it is not
|
||||
suitable for multiple users developing a grammar in collaboration.
|
||||
|
||||
<h3>Related documents</h3>
|
||||
<ul>
|
||||
<li><a href="http://www.grammaticalframework.org/~hallgren/Talks/GF/gf-ide.html">GF Grammar Development Tools</a>. Slides from a presentation at the MOLTO meeting in Göteborg, March 2011.
|
||||
<li><a href="http://www.grammaticalframework.org/grammar-ide/">The GF Grammar IDE</a>. MOLTO deliverable.
|
||||
<li><a href="http://www.grammaticalframework.org/compiler-api">The GF Grammar
|
||||
Compiler API</a>. MOLTO deliverable.
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<div class=modtime><small>
|
||||
<!-- hhmts start --> Last modified: Fri Oct 7 14:06:14 CEST 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>
|
||||
148
src/www/gfse/cloud.js
Normal file
148
src/www/gfse/cloud.js
Normal file
@@ -0,0 +1,148 @@
|
||||
|
||||
function with_dir(cont) {
|
||||
var dir=local.get("dir","");
|
||||
if(dir) cont(dir);
|
||||
else ajax_http_get("upload.cgi?dir",
|
||||
function(dir) {
|
||||
local.put("dir",dir);
|
||||
cont(dir);
|
||||
});
|
||||
}
|
||||
|
||||
function remove_cloud_grammar(g) {
|
||||
var dir=local.get("dir")
|
||||
if(dir && g.unique_name) {
|
||||
var path=dir+"/"+g.unique_name+".json"
|
||||
ajax_http_get("upload.cgi?rm="+encodeURIComponent(path),debug);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload the grammar to the server and check it for errors
|
||||
function upload(g) {
|
||||
function upload2(dir) {
|
||||
var form=node("form",{method:"post",action:"upload.cgi"+dir},
|
||||
[hidden(g.basename+".gf",show_abstract(g))])
|
||||
for(var i in g.concretes)
|
||||
form.appendChild(hidden(g.basename+g.concretes[i].langcode+".gf",
|
||||
show_concrete(g.basename)(g.concretes[i])));
|
||||
editor.appendChild(form);
|
||||
form.submit();
|
||||
form.parentNode.removeChild(form);
|
||||
}
|
||||
|
||||
with_dir(upload2);
|
||||
}
|
||||
|
||||
// Upload the grammar to store it in the cloud
|
||||
function upload_json(cont) {
|
||||
function upload3(resptext,status) {
|
||||
local.put("json_uploaded",Date.now());
|
||||
//debug("Upload complete")
|
||||
if(cont) cont();
|
||||
else {
|
||||
var sharing=element("sharing");
|
||||
if(sharing) sharing.innerHTML=resptext;
|
||||
}
|
||||
}
|
||||
function upload2(dir) {
|
||||
var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
|
||||
//debug("New form data");
|
||||
//var form=new FormData(); // !!! Doesn't work on Android 2.2!
|
||||
var form="",sep="";
|
||||
//debug("Preparing form data");
|
||||
for(var i=0;i<local.count;i++) {
|
||||
var g=local.get(i,null);
|
||||
if(g) {
|
||||
if(!g.unique_name) {
|
||||
g.unique_name=prefix+i;
|
||||
save_grammar(g)
|
||||
}
|
||||
//form.append(g.unique_name+".json",JSON.stringify(g));
|
||||
form+=sep+encodeURIComponent(g.unique_name+".json")+"="+
|
||||
encodeURIComponent(JSON.stringify(g))
|
||||
sep="&"
|
||||
}
|
||||
}
|
||||
//debug("Upload to "+prefix);
|
||||
ajax_http_post("upload.cgi"+dir,form,upload3,cont)
|
||||
}
|
||||
|
||||
with_dir(upload2);
|
||||
}
|
||||
|
||||
function download_json() {
|
||||
var dir=local.get("dir");
|
||||
var index=grammar_index();
|
||||
var downloading=0;
|
||||
|
||||
function get_list(ok,err) {
|
||||
ajax_http_get("upload.cgi?ls="+dir,ok,err);
|
||||
}
|
||||
|
||||
function get_file(file,ok,err) {
|
||||
downloading++;
|
||||
ajax_http_get("upload.cgi?download="+encodeURIComponent(dir+"/"+file),ok,err);
|
||||
}
|
||||
|
||||
function file_failed(errormsg,status) {
|
||||
debug(errormsg)
|
||||
downloading--;
|
||||
}
|
||||
function file_downloaded(grammar) {
|
||||
downloading--;
|
||||
var newg=JSON.parse(grammar);
|
||||
debug("Downloaded "+newg.unique_name)
|
||||
var i=index[newg.unique_name];
|
||||
if(i!=undefined) merge_grammar(i,newg)
|
||||
else {
|
||||
debug("New")
|
||||
newg.index=null;
|
||||
save_grammar(newg);
|
||||
}
|
||||
if(downloading==0) done()
|
||||
}
|
||||
|
||||
function done() {
|
||||
setTimeout(function(){location.href="."},2000);
|
||||
}
|
||||
|
||||
function download_files(ls) {
|
||||
local.put("current",0);
|
||||
if(ls) {
|
||||
//debug("Downloading "+ls);
|
||||
var files=ls.split(" ");
|
||||
cleanup_deleted(files);
|
||||
for(var i in files) get_file(files[i],file_downloaded,file_failed);
|
||||
}
|
||||
else {
|
||||
debug("No grammars in the cloud")
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
get_list(download_files);
|
||||
}
|
||||
|
||||
function link_directories(newdir,cont) {
|
||||
with_dir(function(olddir) {
|
||||
ajax_http_get("upload.cgi?rmdir="+olddir+"&newdir="+newdir,cont)
|
||||
})
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
// Send a command to the GF shell
|
||||
function gfshell(cmd,cont) {
|
||||
alert("gfshell(...) not implmemented!!!")
|
||||
}
|
||||
|
||||
// Check the syntax of an expression
|
||||
function check_exp(s,cont) {
|
||||
function check(gf_message) {
|
||||
//debug("cc "+s+" = "+gf_message);
|
||||
cont(/parse error/.test(gf_message) ? "parse error" : null);
|
||||
}
|
||||
if(navigator.onLine)
|
||||
ajax_http_get("upload.cgi?cc="+encodeURIComponent(s),check)
|
||||
else cont(null)
|
||||
}
|
||||
159
src/www/gfse/cloud2.js
Normal file
159
src/www/gfse/cloud2.js
Normal file
@@ -0,0 +1,159 @@
|
||||
|
||||
function with_dir(cont) {
|
||||
var dir=local.get("dir","");
|
||||
if(/^\/tmp\//.test(dir)) cont(dir);
|
||||
else ajax_http_get("/new",
|
||||
function(dir) {
|
||||
local.put("dir",dir);
|
||||
cont(dir);
|
||||
});
|
||||
}
|
||||
|
||||
function remove_cloud_grammar(g) {
|
||||
var dir=local.get("dir")
|
||||
if(dir && g.unique_name) {
|
||||
var path=g.unique_name+".json"
|
||||
gfcloud("rm",{file:path},debug);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload the grammar to the server and check it for errors
|
||||
function upload(g) {
|
||||
function upload2(dir) {
|
||||
var form=node("form",{method:"post",action:"/cloud"},
|
||||
[hidden("dir",dir),hidden("command","make"),
|
||||
hidden(g.basename+".gf",show_abstract(g))])
|
||||
var files = [g.basename+".gf"]
|
||||
for(var i in g.concretes) {
|
||||
var cname=g.basename+g.concretes[i].langcode+".gf";
|
||||
files.push(cname);
|
||||
form.appendChild(hidden(cname,
|
||||
show_concrete(g.basename)(g.concretes[i])));
|
||||
}
|
||||
editor.appendChild(form);
|
||||
form.submit();
|
||||
form.parentNode.removeChild(form);
|
||||
}
|
||||
|
||||
function upload3(message) { if(message) alert(message); }
|
||||
|
||||
with_dir(upload2)
|
||||
}
|
||||
|
||||
// Upload the grammar to store it in the cloud
|
||||
function upload_json(cont) {
|
||||
function upload3(resptext,status) {
|
||||
local.put("json_uploaded",Date.now());
|
||||
//debug("Upload complete")
|
||||
if(cont) cont();
|
||||
else {
|
||||
var sharing=element("sharing");
|
||||
if(sharing) sharing.innerHTML=resptext;
|
||||
}
|
||||
}
|
||||
function upload2(dir) {
|
||||
var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
|
||||
//debug("New form data");
|
||||
//var form=new FormData(); // !!! Doesn't work on Android 2.2!
|
||||
var form={dir:dir};
|
||||
//debug("Preparing form data");
|
||||
for(var i=0;i<local.count;i++) {
|
||||
var g=local.get(i,null);
|
||||
if(g) {
|
||||
if(!g.unique_name) {
|
||||
g.unique_name=prefix+i;
|
||||
save_grammar(g)
|
||||
}
|
||||
//form.append(g.unique_name+".json",JSON.stringify(g));
|
||||
form[encodeURIComponent(g.unique_name+".json")]=JSON.stringify(g)
|
||||
}
|
||||
}
|
||||
//debug("Upload to "+prefix);
|
||||
ajax_http_post("/cloud","command=upload"+encodeArgs(form),upload3,cont)
|
||||
}
|
||||
|
||||
with_dir(upload2);
|
||||
}
|
||||
|
||||
function download_json() {
|
||||
var dir=local.get("dir");
|
||||
var index=grammar_index();
|
||||
var downloading=0;
|
||||
|
||||
function get_list(ok,err) { gfcloud("ls",{},ok,err) }
|
||||
|
||||
function get_file(file,ok,err) {
|
||||
downloading++;
|
||||
gfcloud("download",{file:encodeURIComponent(file)},ok,err);
|
||||
}
|
||||
|
||||
function file_failed(errormsg,status) {
|
||||
debug(errormsg)
|
||||
downloading--;
|
||||
}
|
||||
function file_downloaded(grammar) {
|
||||
downloading--;
|
||||
var newg=JSON.parse(grammar);
|
||||
debug("Downloaded "+newg.unique_name)
|
||||
var i=index[newg.unique_name];
|
||||
if(i!=undefined) merge_grammar(i,newg)
|
||||
else {
|
||||
debug("New")
|
||||
newg.index=null;
|
||||
save_grammar(newg);
|
||||
}
|
||||
if(downloading==0) done()
|
||||
}
|
||||
|
||||
function done() {
|
||||
setTimeout(function(){location.href="."},2000);
|
||||
}
|
||||
|
||||
function download_files(ls) {
|
||||
local.put("current",0);
|
||||
if(ls) {
|
||||
//debug("Downloading "+ls);
|
||||
var files=ls.split(" ");
|
||||
cleanup_deleted(files);
|
||||
for(var i in files) get_file(files[i],file_downloaded,file_failed);
|
||||
}
|
||||
else {
|
||||
debug("No grammars in the cloud")
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
get_list(download_files);
|
||||
}
|
||||
|
||||
function link_directories(newdir,cont) {
|
||||
gfcloud("link_directories",{newdir:newdir},cont)
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
// Request GF cloud service
|
||||
function gfcloud(cmd,args,cont,err) {
|
||||
with_dir(function(dir) {
|
||||
var enc=encodeURIComponent;
|
||||
var url="/cloud?dir="+enc(dir)+"&command="+enc(cmd)+encodeArgs(args)
|
||||
ajax_http_get(url,cont,err)
|
||||
})
|
||||
}
|
||||
|
||||
// Send a command to the GF shell
|
||||
function gfshell(cmd,cont) {
|
||||
with_dir(function(dir) {
|
||||
var enc=encodeURIComponent;
|
||||
ajax_http_get("/gfshell?dir="+enc(dir)+"&command="+enc(cmd),cont)
|
||||
})
|
||||
}
|
||||
|
||||
// Check the syntax of an expression
|
||||
function check_exp(s,cont) {
|
||||
function check(gf_message) {
|
||||
//debug("cc "+s+" = "+gf_message);
|
||||
cont(/parse error/.test(gf_message) ? "parse error" : null);
|
||||
}
|
||||
gfshell("cc "+s,check);
|
||||
}
|
||||
88
src/www/gfse/editor.css
Normal file
88
src/www/gfse/editor.css
Normal file
@@ -0,0 +1,88 @@
|
||||
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.home, div.grammar { border: 1px solid black; background: #9df; }
|
||||
div.home { padding: 5px; }
|
||||
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.cloud, 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, .home 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; }
|
||||
|
||||
div.fun:hover, div.param:hover, div.lincat:hover, div.oper:hover, div.lin:hover,
|
||||
div.template:hover
|
||||
{ background: #def;}
|
||||
|
||||
.lin input[type=button],
|
||||
.template input[type=button] { float: right; clear: right; margin: 0; }
|
||||
.exb_output { background: #dfd; float: right; margin: 0 10px; }
|
||||
|
||||
.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;*/
|
||||
}
|
||||
|
||||
input.string_edit { font-family: inherit; font-size: inherit; }
|
||||
|
||||
ul.languages { -moz-column-width: 20em; }
|
||||
|
||||
li { margin-top: 0.5ex; margin-bottom: 0.5ex; }
|
||||
|
||||
#sharing h1, #sharing .footer { display: none; }
|
||||
1114
src/www/gfse/editor.js
Normal file
1114
src/www/gfse/editor.js
Normal file
File diff suppressed because it is too large
Load Diff
158
src/www/gfse/example_based.js
Normal file
158
src/www/gfse/example_based.js
Normal file
@@ -0,0 +1,158 @@
|
||||
|
||||
var example_based=[];
|
||||
|
||||
/*
|
||||
-- cat lincat fun lin fun cat cat
|
||||
environ :: ([(CId, CId)],[(CId, Expr)],[((CId, CId), [CId])]) -> Environ
|
||||
*/
|
||||
function exb_state(g,ci) {
|
||||
var conc=g.concretes[ci]
|
||||
function show_list(show1,xs) {
|
||||
return "["+map(show1,xs).join(",")+"]";
|
||||
}
|
||||
function show_fun(fun) {
|
||||
var t=fun.type
|
||||
var res=t[t.length-1]
|
||||
var args=t.slice(0,length-1);
|
||||
return "(("+fun.name+","+res+"),["+args.join(",")+"])"
|
||||
}
|
||||
function show_lincat(lincat) {
|
||||
return "("+lincat.cat+","+lincat.type+")"
|
||||
}
|
||||
function show_lin(lin) {
|
||||
return "("+lin.fun+","+(lin.eb_lin||"?")+")"
|
||||
}
|
||||
function show_funs(funs) { return show_list(show_fun,funs) }
|
||||
function show_lincats(lincats) { return show_list(show_lincat,lincats); }
|
||||
function show_lins(lins) { return show_list(show_lin,lins) }
|
||||
return "("+show_lincats(conc.lincats)
|
||||
+","+show_lins(conc.lins)
|
||||
+","+show_funs(g.abstract.funs)+")"
|
||||
}
|
||||
|
||||
function exb_call(g,ci,command,args,cont) {
|
||||
var url=window.exb_url || "exb/exb.fcgi";
|
||||
var q=encodeArgs(args);
|
||||
var cmd="?command="+command+"&state="+encodeURIComponent(exb_state(g,ci))+q;
|
||||
http_get_json(url+cmd,cont)
|
||||
}
|
||||
|
||||
function ask_possibilities(g,ci) {
|
||||
var conc=g.concretes[ci];
|
||||
|
||||
function show_poss(poss) {
|
||||
//debug("possibilities: "+JSON.stringify(poss))
|
||||
var exready={}
|
||||
for(var i in poss[0]) exready[poss[0][i]]=true;
|
||||
var testable={}
|
||||
for(var i in poss[1]) testable[poss[1][i]]=true;
|
||||
example_based[ci]={exready:exready,testable:testable}
|
||||
conc.example_based=true;
|
||||
conc.example_lang=g.concretes[0].langcode;
|
||||
reload_grammar(g);
|
||||
}
|
||||
|
||||
exb_call(g,ci,"possibilities",{},show_poss)
|
||||
}
|
||||
|
||||
function exb_extra(g,ci) {
|
||||
var conc=g.concretes[ci];
|
||||
function stop_exb() {
|
||||
conc.example_based=false;
|
||||
reload_grammar(g);
|
||||
}
|
||||
|
||||
function exblangmenu() {
|
||||
function opt(conc) { return option(conc.langcode,conc.langcode); }
|
||||
// skip target language
|
||||
var m =node("select",{},map(opt,g.concretes));
|
||||
m.onchange=function() { conc.example_lang=m.value }
|
||||
return m
|
||||
}
|
||||
|
||||
function ask_poss() { ask_possibilities(g,ci) }
|
||||
|
||||
if(navigator.onLine && conc.example_based && !example_based[ci]) ask_poss();
|
||||
return conc.langcode=="Eng"
|
||||
? indent([text("Example based editing: "),
|
||||
conc.example_based
|
||||
? node("span",{},[button("Stop",stop_exb),
|
||||
text(" Example language: "),
|
||||
exblangmenu()
|
||||
])
|
||||
: button("Start",ask_poss)])
|
||||
: text("")
|
||||
}
|
||||
|
||||
function exb_linbuttons(g,ci,f) {
|
||||
var conc=g.concretes[ci];
|
||||
var fun=f.fun;
|
||||
var eb=example_based[ci];
|
||||
var exb_output;
|
||||
function fill_example(maybetree) {
|
||||
var tree=maybetree.Just
|
||||
if(tree) {
|
||||
if(f.template)
|
||||
conc.lins.push({fun:f.fun,args:f.args,
|
||||
lin:tree[0],eb_lin:tree[1]});
|
||||
else {
|
||||
f.lin=tree[0];
|
||||
f.eb_lin=tree[1];
|
||||
}
|
||||
ask_possibilities(g,ci)
|
||||
}
|
||||
else exb_output.innerHTML="Bug: no tree found"
|
||||
}
|
||||
function show_example(example){
|
||||
exb_output.innerHTML="";
|
||||
var s=prompt(example[1]);
|
||||
if(s) {
|
||||
var t=function_type(g,fun);
|
||||
var abscat=t[t.length-1]
|
||||
var cat=cat_lincat(conc,abscat)
|
||||
exb_output.innerHTML="...";
|
||||
//server.parse({from:"ParseEng",cat:cat,input:s},fill_example)
|
||||
exb_call(g,ci,"abstract_example",
|
||||
{cat:cat,input:s,
|
||||
params:"["+f.args.join(",")+"]",
|
||||
abstract:example[0]},
|
||||
fill_example)
|
||||
}
|
||||
}
|
||||
function by_example() {
|
||||
var dir=local.get("dir")
|
||||
if(dir) {
|
||||
if(exb_output) {
|
||||
exb_output.innerHTML="...";
|
||||
exb_call(g,ci,"provide_example",
|
||||
{lang:g.basename+conc.example_lang,
|
||||
fun:fun,
|
||||
grammar:dir+"/"+g.basename+".pgf"},
|
||||
show_example)
|
||||
}
|
||||
}
|
||||
else exb_output.innerHTML="Compile the grammar first!"
|
||||
}
|
||||
function show_test(txt) {
|
||||
exb_output.innerHTML="";
|
||||
exb_output.appendChild(text(txt))
|
||||
}
|
||||
function test_it(b) {
|
||||
if(exb_output) {
|
||||
exb_output.innerHTML="...";
|
||||
exb_call(g,ci,"test_function",{fun:fun},show_test)
|
||||
}
|
||||
}
|
||||
var buttons=[];
|
||||
if(conc.example_based && eb) {
|
||||
if(eb.exready[fun])
|
||||
buttons.push(button("By example",by_example))
|
||||
if(eb.testable[fun] && f.eb_lin) {
|
||||
var b=button("Test it",test_it);
|
||||
buttons.push(b)
|
||||
}
|
||||
var exb_output=node("span",{"class":"exb_output"},[]);
|
||||
buttons.push(exb_output)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
226
src/www/gfse/gf_abs.js
Normal file
226
src/www/gfse/gf_abs.js
Normal file
@@ -0,0 +1,226 @@
|
||||
/* Abstract syntax for a small subset of GF grammars in JavaScript */
|
||||
|
||||
function defined_cats(g) {
|
||||
var dc={};
|
||||
with(g.abstract)
|
||||
for(var i in cats) dc[cats[i]]=true;
|
||||
return dc;
|
||||
}
|
||||
|
||||
function defined_funs(g) {
|
||||
var df={};
|
||||
with(g.abstract)
|
||||
for(var i in funs) df[funs[i].name]=true;
|
||||
return df;
|
||||
}
|
||||
|
||||
function function_type(g,fun) {
|
||||
with(g.abstract)
|
||||
for(var i in funs) if(funs[i].name==fun) return funs[i].type
|
||||
return null;
|
||||
}
|
||||
|
||||
function cat_lincat(conc,cat) {
|
||||
with(conc)
|
||||
for(var i in lincats) if(lincats[i].cat==cat) return lincats[i].type
|
||||
return null;
|
||||
}
|
||||
|
||||
function rename_category(g,oldcat,newcat) {
|
||||
function rename_cats(cats) {
|
||||
for(var i in cats) if(cats[i]==oldcat) cats[i]=newcat;
|
||||
}
|
||||
function rename_type(t) {
|
||||
for(var i in t) if(t[i]==oldcat) t[i]=newcat;
|
||||
}
|
||||
function rename_funs(funs) {
|
||||
for(var i in funs) rename_type(funs[i].type)
|
||||
}
|
||||
function rename_abstract(a) {
|
||||
rename_cats(a.cats);
|
||||
rename_funs(a.funs);
|
||||
}
|
||||
function rename_lincat(lc) {
|
||||
if(lc.cat==oldcat) lc.cat=newcat;
|
||||
}
|
||||
function rename_concrete(c) {
|
||||
for(var i in c.lincats) rename_lincat(c.lincats[i]);
|
||||
}
|
||||
function rename_concretes(cs) {
|
||||
for(var i in cs) rename_concrete(cs[i]);
|
||||
}
|
||||
rename_abstract(g.abstract)
|
||||
rename_concretes(g.concretes);
|
||||
return g;
|
||||
}
|
||||
|
||||
function rename_function(g,oldfun,newfun) {
|
||||
function rename_lin(lin) {
|
||||
if(lin.fun==oldfun) lin.fun=newfun;
|
||||
}
|
||||
function rename_concrete(c) {
|
||||
for(var i in c.lins) rename_lin(c.lins[i]);
|
||||
}
|
||||
for(var i in g.concretes) rename_concrete(g.concretes[i]);
|
||||
return g;
|
||||
}
|
||||
|
||||
function change_lin_lhs(g,fun) {
|
||||
function change_lin(lin) {
|
||||
if(lin.fun==fun.name) lin.args=arg_names(fun.type);
|
||||
}
|
||||
function change_concrete(c) {
|
||||
for(var i in c.lins) change_lin(c.lins[i]);
|
||||
}
|
||||
for(var i in g.concretes) change_concrete(g.concretes[i]);
|
||||
return g;
|
||||
}
|
||||
|
||||
/* --- Parsing -------------------------------------------------------------- */
|
||||
|
||||
// GF idenfifier syntax:
|
||||
var lex_id=/^[A-Za-z][A-Za-z0-9_']*$/
|
||||
// See https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions
|
||||
|
||||
function check_id(s) { return lex_id.test(s); }
|
||||
|
||||
function check_name(s,kind) {
|
||||
return check_id(s)
|
||||
? null
|
||||
: s+"? "+kind+" names must start with a letter and can contain letters, digits, _ and '"
|
||||
}
|
||||
|
||||
|
||||
function parse_fun(s) {
|
||||
var ws=s.split(/\s+/);
|
||||
var fun={name:"",type:[]};
|
||||
/* Use a state machine to parse function definitions */
|
||||
/* f : T1 -> ... -> Tn */
|
||||
var state="name";
|
||||
var ok=true;
|
||||
for(var i=0;ok && i<ws.length;i++) {
|
||||
if(ws[i]!="") {
|
||||
switch(state) {
|
||||
case "name": fun.name=ws[i]; state=":"; break;
|
||||
case ":": ok=ws[i]==":"; state="type"; break;
|
||||
case "type": fun.type.push(ws[i]); state="->"; break;
|
||||
case "->": ok=ws[i]=="->"; state="type"; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var err=check_name(fun.name,"Function");
|
||||
if(err) return {error: err};
|
||||
return ok && state=="->"
|
||||
? {ok:fun}
|
||||
: { error : "Fun : Cat<sub>1</sub> -> ... -> Cat<sub>n</sub>" }
|
||||
}
|
||||
|
||||
|
||||
function parse_param(s) {
|
||||
var ws=s.split("=");
|
||||
if(ws.length==2) {
|
||||
var name=ws[0].trim();
|
||||
var err=check_name(name,"Parameter type");
|
||||
return err ? { error:err } : { ok: { name:name,rhs:ws[1].trim() } }
|
||||
}
|
||||
else
|
||||
return { error: "P = C1 | ... | Cn" }
|
||||
}
|
||||
|
||||
function parse_oper(s) {
|
||||
var i=s.indexOf(" ");
|
||||
var operr = { error: "op = expr" }
|
||||
if(i>0 && i<s.length-1) {
|
||||
var name=s.substr(0,i).trim();
|
||||
var rhs=s.substr(i).trim();
|
||||
var err=check_name(name,"Operator");
|
||||
return err
|
||||
? {error:err}
|
||||
: rhs!="" ? {ok: {name:name, rhs:rhs}}
|
||||
: operr
|
||||
}
|
||||
else return operr
|
||||
|
||||
}
|
||||
|
||||
/* --- Print as plain text (normal GF concrete syntax) ---------------------- */
|
||||
|
||||
function show_type(t) {
|
||||
var s="";
|
||||
for(var i in t) {
|
||||
if(i>0) s+=" -> ";
|
||||
s+=t[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function show_fun(fun) {
|
||||
return fun.name+" : "+show_type(fun.type);
|
||||
}
|
||||
|
||||
function show_grammar(g) {
|
||||
return show_abstract(g)+"\n"+show_concretes(g)
|
||||
}
|
||||
|
||||
function show_abstract(g) {
|
||||
// var startcat= g.abstract.cats.length==1 ? g.abstract.cats[0] : g.abstract.startcat;
|
||||
var startcat= g.abstract.startcat || g.abstract.cats[0];
|
||||
return "abstract "+g.basename+" = {\n\n"
|
||||
+"flags coding = utf8 ;\n\n"
|
||||
+show_startcat(startcat)
|
||||
+show_cats(g.abstract.cats)
|
||||
+show_funs(g.abstract.funs)
|
||||
+"}\n";
|
||||
}
|
||||
|
||||
function show_startcat(startcat) {
|
||||
return startcat ? "flags startcat = "+startcat+";\n\n" : "";
|
||||
}
|
||||
|
||||
function show_cats(cats) {
|
||||
return cats.length>0 ? "cat\n "+cats.join("; ")+";\n\n" : "";
|
||||
}
|
||||
|
||||
function show_funs(funs) { return show_list("fun",show_fun,funs); }
|
||||
|
||||
function show_concretes(g) {
|
||||
return map(show_concrete(g.basename),g.concretes).join("\n\n");
|
||||
}
|
||||
|
||||
function show_concrete(basename) {
|
||||
return function(conc) {
|
||||
return "--# -path=.:present\n"
|
||||
+ "concrete "+basename+conc.langcode+" of "+basename+" ="
|
||||
+show_opens(conc.opens)
|
||||
+" {\n\nflags coding = utf8 ;\n\n"
|
||||
+show_params(conc.params)
|
||||
+show_lincats(conc.lincats)
|
||||
+show_opers(conc.opers)
|
||||
+show_lins(conc.lins)
|
||||
+"}\n"
|
||||
}
|
||||
}
|
||||
|
||||
function show_list(kw,show1,list) {
|
||||
return list.length>0
|
||||
? kw+"\n "+map(show1,list).join(";\n ")+";\n\n"
|
||||
: ""
|
||||
}
|
||||
|
||||
function show_opens(opens) {
|
||||
return opens && opens.length>0 ? "\n\nopen "+opens.join(", ")+" in" : ""
|
||||
}
|
||||
|
||||
function show_params(params) { return show_list("param",show_param,params); }
|
||||
function show_lincats(lincats) { return show_list("lincat",show_lincat,lincats); }
|
||||
function show_opers(opers) { return show_list("oper",show_oper,opers); }
|
||||
function show_lins(lins) { return show_list("lin",show_lin,lins); }
|
||||
|
||||
|
||||
function show_param(p) { return p.name + " = " + p.rhs; }
|
||||
function show_oper(p) { return p.name + " " + p.rhs; }
|
||||
function show_lincat(p) { return p.cat + " = " + p.type; }
|
||||
|
||||
function show_lin(lin) {
|
||||
return lin.fun + " " + lin.args.join(" ")+ " = " + lin.lin;
|
||||
}
|
||||
4
src/www/gfse/gfse.manifest
Normal file
4
src/www/gfse/gfse.manifest
Normal file
@@ -0,0 +1,4 @@
|
||||
CACHE MANIFEST
|
||||
# 5
|
||||
NETWORK:
|
||||
*
|
||||
19
src/www/gfse/grammars.cgi
Normal file
19
src/www/gfse/grammars.cgi
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
echo "Content-Type: text/javascript"
|
||||
echo ""
|
||||
|
||||
case "$QUERY_STRING" in
|
||||
jsonp=*) prefix="${QUERY_STRING#jsonp=}("; suffix=")" ;;
|
||||
*) prefix=""; suffix=""
|
||||
esac
|
||||
|
||||
echo -n "$prefix"
|
||||
sep="["
|
||||
for g in *.pgf ; do
|
||||
echo -n "$sep\"$g\""
|
||||
sep=", "
|
||||
done
|
||||
echo "]$suffix"
|
||||
#echo "/*"
|
||||
#set
|
||||
#echo "*/"
|
||||
48
src/www/gfse/index.html
Normal file
48
src/www/gfse/index.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html> <!-- manifest="gfse.manifest" -->
|
||||
<head>
|
||||
<title>GF online editor for simple multilingual grammars</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
|
||||
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
|
||||
|
||||
<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">
|
||||
|
||||
</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: Mon Oct 10 19:24:05 CEST 2011 <!-- hhmts end -->
|
||||
</small></div>
|
||||
<a href="about.html">About</a>
|
||||
<pre id=debug></pre>
|
||||
<script type="text/javascript" src="config.js"></script> <!-- optional -->
|
||||
<script type="text/javascript" src="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="example_based.js"></script>
|
||||
<script type="text/javascript" src="editor.js"></script>
|
||||
<script type="text/javascript" src="cloud2.js"></script>
|
||||
<script type="text/javascript" src="sort.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
21
src/www/gfse/localstorage.js
Normal file
21
src/www/gfse/localstorage.js
Normal 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); }
|
||||
}
|
||||
82
src/www/gfse/molto.css
Normal file
82
src/www/gfse/molto.css
Normal file
@@ -0,0 +1,82 @@
|
||||
body { color: #413b36;
|
||||
background: #fffcfa;
|
||||
}
|
||||
h1 { font-size: 175%; }
|
||||
h1,h2,h3,h4,small { font-family: sans-serif; }
|
||||
h1,h2,h3,h4,a { color: #5c1a1a; }
|
||||
|
||||
h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
|
||||
|
||||
#editor { max-width: 50em; }
|
||||
div.grammar { border: 2px solid #b09779; background: #642121; }
|
||||
div.files { margin: 0 8px 8px 8px; }
|
||||
|
||||
div#file { border: 2px solid #b0977d; border-top-width: 0; }
|
||||
pre.plain { border: 2px solid #b0977d; }
|
||||
div#file, pre.plain { background: #fffcfa; padding: 0.6ex; }
|
||||
|
||||
.slideshow .hidden { display: none; }
|
||||
|
||||
img.right, div.right, div.modtime { float: right; }
|
||||
.modtime { color: #999; white-space: nowrap; }
|
||||
|
||||
/*div.namebar { background: #642121; }*/
|
||||
div.namebar table { width: 100%; }
|
||||
.namebar h3 { margin: 0; color: white; }
|
||||
|
||||
td.right { text-align: right; }
|
||||
|
||||
.kw { font-weight: bold; font-family: sans-serif; color: #642121; }
|
||||
.sep { font-weight: bold; color: #642121; }
|
||||
|
||||
div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
|
||||
|
||||
/*
|
||||
div.fun, div.param, div.lincat, div.oper, div.lin
|
||||
{ padding-left: 2em; text-indent: -2em; }
|
||||
*/
|
||||
.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; }
|
||||
.namebar .editable:hover { background: #04b; }
|
||||
|
||||
.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 #b09779; padding: 2px; }
|
||||
table.tabs td.active { background: white; border-bottom-width: 0; }
|
||||
table.tabs td.inactive {
|
||||
background: #e1e1e1;
|
||||
border-top-color: #b09779; border-left-color: #b09779; border-right-color: #b09779;
|
||||
}
|
||||
|
||||
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: #642121;
|
||||
font-size: inherit;
|
||||
font-weight: bold;
|
||||
/*text-decoration: underline;*/
|
||||
}
|
||||
|
||||
input.string_edit { font-family: inherit; font-size: inherit; }
|
||||
25
src/www/gfse/save.hs
Normal file
25
src/www/gfse/save.hs
Normal file
@@ -0,0 +1,25 @@
|
||||
import System(getArgs)
|
||||
import CGI(getQuery,string)
|
||||
import MUtils(apSnd)
|
||||
|
||||
main = save2 =<< getArgs
|
||||
|
||||
{-
|
||||
save1 [dir] =
|
||||
do fs@[ns,_] <- readIO =<< getContents
|
||||
nes <- save_all fs
|
||||
putStrLn $ unwords nes
|
||||
where
|
||||
save_all [ns,cs] = mapM (write1 dir) (zip ns cs)
|
||||
-}
|
||||
|
||||
write1 dir (n,c) =
|
||||
do writeFile (dir++"/"++ne) c
|
||||
return ne
|
||||
where
|
||||
ne=if '.' `elem` n then n else n++".gf"
|
||||
|
||||
save2 [dir] =
|
||||
do nfs <- getQuery
|
||||
nes <- mapM (write1 dir . apSnd string) nfs
|
||||
putStrLn $ unwords nes
|
||||
27
src/www/gfse/share.html
Normal file
27
src/www/gfse/share.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html> <head>
|
||||
<title>Download from Grammar Cloud</title>
|
||||
<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
|
||||
<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1><img src="P/1307545048_weather_09.png" alt="">Download from Grammar Cloud</h1>
|
||||
|
||||
<pre id=debug></pre>
|
||||
|
||||
<hr>
|
||||
<address></address>
|
||||
<!-- hhmts start --> Last modified: Mon Oct 10 20:29:01 CEST 2011 <!-- hhmts end -->
|
||||
<script type="text/javascript" src="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>
|
||||
<script type="text/javascript" src="cloud2.js"></script>
|
||||
<script type="text/javascript" src="sort.js"></script>
|
||||
<script type="text/javascript">
|
||||
download_from_cloud();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
86
src/www/gfse/slideshow.js
Normal file
86
src/www/gfse/slideshow.js
Normal 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);
|
||||
}
|
||||
}
|
||||
252
src/www/gfse/upload.cgi
Normal file
252
src/www/gfse/upload.cgi
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/bin/bash
|
||||
|
||||
bin=/Users/hallgren/www/bin
|
||||
|
||||
charset="UTF-8"
|
||||
AUTOHEADER=no
|
||||
|
||||
. $bin/cgistart.sh
|
||||
export LC_CTYPE="UTF-8"
|
||||
style_url="editor.css"
|
||||
|
||||
tmp="$documentRoot/tmp"
|
||||
|
||||
make_dir() {
|
||||
dir="$(mktemp -d "$tmp/gfse.XXXXXXXXXX")"
|
||||
# chmod a+rxw "$dir"
|
||||
chmod a+rx "$dir"
|
||||
cp "grammars.cgi" "$dir"
|
||||
}
|
||||
|
||||
|
||||
check_grammar() {
|
||||
pagestart "Uploaded"
|
||||
# echo "$PATH_INFO"
|
||||
chgrp everyone "$dir"
|
||||
chmod g+ws "$dir"
|
||||
umask 002
|
||||
# files=( $(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
|
||||
files=( $(LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
|
||||
gffiles=( )
|
||||
otherfiles=( )
|
||||
for f in ${files[*]} ; do
|
||||
case "$f" in
|
||||
*.gf) gffiles=( ${gffiles[*]} "$f" ) ;;
|
||||
*) otherfiles=( ${otherfiles[*]} "$f" ) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ${#otherfiles} -gt 0 -a -n "$PATH_INFO" ] ; then
|
||||
echo "Use the following link for shared access to your grammars from multiple devices:"
|
||||
begin ul
|
||||
case "$SERVER_PORT" in
|
||||
80) port="" ;;
|
||||
*) port=":$SERVER_PORT"
|
||||
esac
|
||||
parent="http://$SERVER_NAME$port${REQUEST_URI%/upload.cgi/tmp/gfse.*}"
|
||||
cloudurl="$parent/share.html#${dir##*/}"
|
||||
li; link "$cloudurl" "$cloudurl"
|
||||
end
|
||||
#begin dl
|
||||
#dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
|
||||
#end
|
||||
fi
|
||||
|
||||
cd $dir
|
||||
if [ ${#gffiles} -gt 0 ] ; then
|
||||
begin pre
|
||||
echo "gf -s -make ${gffiles[*]}"
|
||||
if gf -s -make ${gffiles[*]} 2>&1 ; then
|
||||
end
|
||||
h3 OK
|
||||
begin dl
|
||||
[ -z "$minibar_url" ] || { dt; echo "▸"; link "$minibar_url?/tmp/${dir##*/}/" "Minibar"; }
|
||||
[ -z "$transquiz_url" ] || { dt; echo "▸"; link "$transquiz_url?/tmp/${dir##*/}/" "Translation Quiz"; }
|
||||
[ -z "$gfshell_url" ] || { dt; echo "▸"; link "$gfshell_url?dir=${dir##*/}" "GF Shell"; }
|
||||
dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
|
||||
|
||||
end
|
||||
sed=();
|
||||
for pgf in *.pgf ; do
|
||||
sed=("${sed[@]}" -e "s%$pgf%<a href=\"${dir##*/}/$pgf\">$pgf</a>%")
|
||||
done
|
||||
begin pre
|
||||
ls -l *.pgf | sed "${sed[@]}"
|
||||
end
|
||||
else
|
||||
end
|
||||
begin h3 class=error_message; echo Error; end
|
||||
for f in ${gffiles[*]} ; do
|
||||
h4 "$f"
|
||||
begin pre class=plain
|
||||
cat -n "$f"
|
||||
end
|
||||
done
|
||||
fi
|
||||
fi
|
||||
begin div class=footer
|
||||
hr
|
||||
date
|
||||
end
|
||||
# begin pre ; env
|
||||
endall
|
||||
}
|
||||
|
||||
error400() {
|
||||
echo "Status: 400"
|
||||
pagestart "Error"
|
||||
echo "What do you want?"
|
||||
endall
|
||||
}
|
||||
|
||||
error404() {
|
||||
echo "Status: 404"
|
||||
pagestart "Not found"
|
||||
echo "Not found"
|
||||
endall
|
||||
}
|
||||
|
||||
if [ -z "$tmp" ] || ! [ -d "$tmp" ] ; then
|
||||
pagestart "Error"
|
||||
begin pre
|
||||
echo "upload.cgi is not properly configured:"
|
||||
if [ -z "$tmp" ] ; then
|
||||
echo "cgiconfig.sh must define tmp"
|
||||
elif [ ! -d "$tmp" ] || [ ! -w "$tmp" ] ; then
|
||||
echo "$tmp must be a writeable directory"
|
||||
fi
|
||||
# cgiconfig.sh should define minibar & gfshell to allow grammars to be tested.
|
||||
endall
|
||||
else
|
||||
case "$REQUEST_METHOD" in
|
||||
POST)
|
||||
case "$PATH_INFO" in
|
||||
/tmp/gfse.*)
|
||||
style_url="../../$style_url"
|
||||
dir="$tmp/${PATH_INFO##*/}"
|
||||
check_grammar
|
||||
;;
|
||||
*)
|
||||
make_dir
|
||||
echo >&2 "Using temporary directory $dir"
|
||||
check_grammar
|
||||
rm -rf "$dir"
|
||||
esac
|
||||
;;
|
||||
GET)
|
||||
case "$QUERY_STRING" in
|
||||
dir) make_dir
|
||||
ContentType="text/plain"
|
||||
cgiheaders
|
||||
echo_n "/tmp/${dir##*/}"
|
||||
;;
|
||||
ls=*)
|
||||
dir=$(qparse "$QUERY_STRING" ls)
|
||||
case "$dir" in
|
||||
/tmp/gfse.*) # shouldn't allow .. in path !!!
|
||||
path="$documentRoot$dir"
|
||||
if [ -d "$path" ] ; then
|
||||
ContentType="text/plain; charset=$charset"
|
||||
cgiheaders
|
||||
cd "$path"
|
||||
shopt -s nullglob
|
||||
echo_n *-*.json
|
||||
else
|
||||
error404
|
||||
fi
|
||||
;;
|
||||
*) error400
|
||||
esac
|
||||
;;
|
||||
rmdir=*)
|
||||
dir=$(qparse "$QUERY_STRING" rmdir)
|
||||
case "$dir" in
|
||||
/tmp/gfse.*) # shouldn't allow .. in path !!!
|
||||
path="$documentRoot$dir"
|
||||
if [ -d "$path" ] ; then
|
||||
ContentType="text/plain; charset=$charset"
|
||||
cgiheaders
|
||||
if [ -h "$path" ] ; then
|
||||
cd "$path"
|
||||
cd ..
|
||||
rm "$path"
|
||||
else
|
||||
cd "$path"
|
||||
shopt -s nullglob
|
||||
rm *.gf *.gfo *-*.json *.pgf grammars.cgi
|
||||
cd ..
|
||||
rmdir "$path"
|
||||
fi
|
||||
newdir=$(qparse "$QUERY_STRING" newdir)
|
||||
case "$newdir" in
|
||||
/tmp/gfse.*) # shouldn't allow .. in path !!!
|
||||
newnode="${newdir##*/}"
|
||||
oldnode="${path##*/}"
|
||||
ln -s "$newnode" "$oldnode"
|
||||
esac
|
||||
else
|
||||
error404
|
||||
fi
|
||||
;;
|
||||
*) error400
|
||||
esac
|
||||
;;
|
||||
download=*)
|
||||
file=$(qparse "$QUERY_STRING" download)
|
||||
case "$file" in
|
||||
/tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
|
||||
path="$documentRoot$file"
|
||||
if [ -r "$path" ] ; then
|
||||
ContentType="text/javascript; charset=$charset"
|
||||
cgiheaders
|
||||
cat "$path"
|
||||
else
|
||||
error404
|
||||
fi
|
||||
;;
|
||||
*) error400
|
||||
esac
|
||||
;;
|
||||
rm=*)
|
||||
file=$(qparse "$QUERY_STRING" rm)
|
||||
case "$file" in
|
||||
/tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
|
||||
path="$documentRoot$file"
|
||||
if [ -r "$path" ] ; then
|
||||
ContentType="text/javascript; charset=$charset"
|
||||
cgiheaders
|
||||
rm "$path"
|
||||
else
|
||||
error404
|
||||
fi
|
||||
;;
|
||||
*) error400
|
||||
esac
|
||||
;;
|
||||
cc=*)
|
||||
# Just to check an expression for syntax errors
|
||||
exp=$(qparse "$QUERY_STRING" cc)
|
||||
ContentType="text/plain; charset=$charset"
|
||||
cgiheaders
|
||||
echo "cc $exp" | GF_RESTRICTED=True gf -run
|
||||
;;
|
||||
"")
|
||||
case "$PATH_INFO" in
|
||||
/tmp/gfse.*/*.pgf)
|
||||
path="$documentRoot$PATH_INFO"
|
||||
if [ -r $path ] ; then
|
||||
ContentType="application/binary"
|
||||
cgiheaders
|
||||
cat "$path"
|
||||
else
|
||||
error404
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
error400
|
||||
esac
|
||||
;;
|
||||
*) error400
|
||||
esac
|
||||
esac
|
||||
fi
|
||||
Reference in New Issue
Block a user