diff --git a/src/www/js/support.js b/src/www/js/support.js
index 44c0e3895..9913aa5e3 100644
--- a/src/www/js/support.js
+++ b/src/www/js/support.js
@@ -248,6 +248,13 @@ function insertAfter(el,ref) {
ref.parentNode.insertBefore(el,ref.nextSibling);
}
+function toggleHidden(el) {
+ if (el.classList.contains("hidden"))
+ el.classList.remove("hidden")
+ else
+ el.classList.add("hidden")
+}
+
/* --- Debug ---------------------------------------------------------------- */
function debug(s) {
diff --git a/src/www/syntax-editor/README.md b/src/www/syntax-editor/README.md
index ef9758e71..50f911f13 100644
--- a/src/www/syntax-editor/README.md
+++ b/src/www/syntax-editor/README.md
@@ -1,7 +1,7 @@
# GF web-based syntax editor
John J. Camilleri
-November 2012
+December 2012
An improved version of the [old syntax editor][1].
@@ -14,7 +14,7 @@ An improved version of the [old syntax editor][1].
## Available startup options
|Options|Description|Default|
-|:------|:----------|:------|
+|-------|-----------|-------|
|target | |"editor"|
|initial.grammar|Initial grammar URL, e.g. `"http://localhost:41296/grammars/Foods.pgf"`|-|
|initial.startcat|Initial startcat|-|
@@ -35,7 +35,7 @@ See `editor.html` and `editor_online.js`.
## TODO
-- Wrap a subtree
+- Import AST from text field
- Compatibility with grammars with dependent category types
- Clicking on tokens to select tree node
- Clipboard of trees
@@ -45,3 +45,4 @@ See `editor.html` and `editor_online.js`.
- show all resulting linearizations/variants
- undo/redo (or back/forward) navigation
- structure fridge magnets more (eg newline before the magnet whose first letter is different)
+
diff --git a/src/www/syntax-editor/ast.js b/src/www/syntax-editor/ast.js
index 89bdcf5c4..026452697 100644
--- a/src/www/syntax-editor/ast.js
+++ b/src/www/syntax-editor/ast.js
@@ -183,9 +183,11 @@ function AST(fun, cat) {
var lid = Array.clone(id.get()); // clone NodeID array
var node = this.root;
- if (lid.length==1)
+ if (lid.length==1) {
// Insert at root
this.root = new ASTNode(subtree);
+ this.currentNode = this.root;
+ }
else {
lid.shift(); // throw away root
while (lid.length>1 && node.hasChildren()) {
@@ -297,6 +299,7 @@ function AST(fun, cat) {
// (This probably needs a better home)
AST.parse_type_signature = function(str) {
var obj = {
+ signature: str,
type: undefined,
name: [],
deps: [],
diff --git a/src/www/syntax-editor/editor.css b/src/www/syntax-editor/editor.css
index f36898d5c..6d4511eb0 100644
--- a/src/www/syntax-editor/editor.css
+++ b/src/www/syntax-editor/editor.css
@@ -14,6 +14,27 @@ body.syntax-editor {
min-width: 5em;
}
+#import
+{
+ position: absolute;
+ display: inline-block;
+ padding: 0.3em;
+ background: #EEE;
+ border: 1px solid #999;
+ font-size: 0.95em;
+ box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+ }
+#import.hidden
+{
+ display: none;
+ }
+#import input[type=text]
+{
+ width: 20em;
+ font-family: monospace;
+ padding: 0.2em;
+ }
+
#tree
{
white-space:pre;
@@ -41,6 +62,13 @@ body.syntax-editor {
font-weight: bold;
}
+#refinements
+{
+ /* display: inline-block; */
+ /* margin-left: 0.5em; */
+ margin-top: 0.3em;
+ }
+
#linearisations
{
/* background: rgba(170, 170, 170, 0.5); */
@@ -65,13 +93,14 @@ body.syntax-editor {
.refinement
{
- margin: 0 0.1em;
+ margin: 0.1em;
display: inline-block;
cursor: pointer;
border: 1px solid;
padding: 0.2em;
font: 0.9em sans-serif;
background: white;
+ box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.refinement.disabled
{
diff --git a/src/www/syntax-editor/editor.html b/src/www/syntax-editor/editor.html
index 8105df60d..9464e0fa7 100644
--- a/src/www/syntax-editor/editor.html
+++ b/src/www/syntax-editor/editor.html
@@ -15,7 +15,7 @@
- John J. Camilleri, November 2012
+ John J. Camilleri, December 2012
diff --git a/src/www/syntax-editor/editor.js b/src/www/syntax-editor/editor.js
index 777766f55..65fe6c00f 100644
--- a/src/www/syntax-editor/editor.js
+++ b/src/www/syntax-editor/editor.js
@@ -29,15 +29,35 @@ function Editor(gm,opts) {
this.container.classList.add("editor");
this.ui = {
menubar: div_class("menu"),
+
tree: div_id("tree"),
+
+ actionbar: div_id("actions"),
+ clear_button: button("Clear", function(){
+ t.clear_node();
+ }),
+ wrap_button: button("Wrap…", function(){
+ t.wrap_candidates();
+ }),
+ unwrap_button: button("Unwrap", function(){
+ t.wrap_candidates();
+ }),
+
refinements: div_id("refinements"),
+
lin: div_id("linearisations")
};
appendChildren(this.container, [
- this.ui.menubar,
- this.ui.tree,
- this.ui.refinements,
- this.ui.lin
+ t.ui.menubar,
+ t.ui.tree,
+ t.ui.actionbar,
+ t.ui.lin
+ ]);
+ appendChildren(this.ui.actionbar, [
+ t.ui.clear_button,
+ t.ui.wrap_button,
+ // t.ui.unwrap_button,
+ t.ui.refinements
]);
/* --- Client state initialisation -------------------------------------- */
@@ -142,25 +162,24 @@ Editor.prototype.start_fresh=function () {
/* --- Functions for handling tree manipulation ----------------------------- */
-// Add refinement to UI, with a given callback function
-// The opts param is used both in this function as:
-// opts.disable_destructive
-// as well as being passed to the callback function
-Editor.prototype.add_refinement=function(t,fun,callback,opts) {
- // var t = this;
- if (!opts) opts = {};
+// Add refinement to UI, returning object
+Editor.prototype.add_refinement=function(fun,opts) {
+ var t = this;
+ options = {
+ label: fun,
+ disable_destructive: true
+ };
+ if (opts) for (var o in opts) options[o] = opts[o];
+ var typeobj = t.lookup_fun(fun);
// hide refinement if identical to current fun?
- var opt = span_class("refinement", text(fun));
- opt.onclick = bind(function(){
- callback(opts);
- }, opt);
+ var opt = span_class("refinement", text(options.label));
+ opt.title = typeobj.signature;
// If refinement would be destructive, disable it
- if (opts.disable_destructive) {
+ if (options.disable_destructive) {
var blank = t.ast.is_writable();
- var typeobj = t.lookup_fun(fun);
var inplace = t.ast.fits_in_place(typeobj);
if (!blank && !inplace) {
opt.classList.add("disabled");
@@ -168,12 +187,13 @@ Editor.prototype.add_refinement=function(t,fun,callback,opts) {
}
t.ui.refinements.appendChild(opt);
+ return opt;
}
// Show refinements for given cat (usually that of current node)
Editor.prototype.get_refinements=function(cat) {
var t = this;
- t.ui.refinements.innerHTML = "...";
+ t.ui.refinements.innerHTML = "…";
if (cat == undefined)
cat = t.ast.getCat();
var args = {
@@ -182,12 +202,18 @@ Editor.prototype.get_refinements=function(cat) {
};
var cont = function(data){
clear(t.ui.refinements);
+ // t.ui.refinements.innerHTML = "Refinements: ";
+ function addClickHandler(fun) {
+ return function() {
+ t.select_refinement.apply(t,[fun]);
+ }
+ }
for (var pi in data.producers) {
var fun = data.producers[pi];
- t.add_refinement(t, fun, bind(t.select_refinement,t), {
- fun: fun,
+ var ref = t.add_refinement(fun, {
disable_destructive: true
});
+ ref.onclick = addClickHandler(fun);
}
};
var err = function(data){
@@ -201,9 +227,8 @@ Editor.prototype.get_refinements=function(cat) {
// Case 1: current node is blank/no kids
// Case 2: kids have all same types, perform an in-place replacement
// Case 3: kids have diff types/number, prevent replacement (must clear first)
-Editor.prototype.select_refinement=function(opts) {
+Editor.prototype.select_refinement=function(fun) {
var t = this;
- var fun = opts.fun;
// Check if current node is blank or childless (case 1)
var blank = t.ast.is_writable();
@@ -291,29 +316,30 @@ Editor.prototype.wrap_candidates = function() {
t.ui.refinements.innerHTML = "Wrap with: ";
for (var i in refinements) {
var typeobj = refinements[i];
- // t.add_refinement(t, typeobj.name, bind(t.wrap,t), false);
// Show a refinement for each potential child position
+ function addClickHandler(fun, child_id) {
+ return function() {
+ t.wrap.apply(t,[fun, child_id]);
+ }
+ }
for (var a in typeobj.args) {
var arg = typeobj.args[a];
if (arg == cat) {
var label = typeobj.name + " ?" + (parseInt(a)+1);
- t.add_refinement(t, label, bind(t.wrap,t), {
- fun: typeobj.name,
+ var ref = t.add_refinement(typeobj.name, {
+ label: label,
disable_destructive: false,
- child_id: a
});
+ ref.onclick = addClickHandler(typeobj.name, a);
}
}
}
}
// Wrap the current node inside another function
-Editor.prototype.wrap = function(opts) {
+Editor.prototype.wrap = function(fun, child_id) {
var t = this;
- var fun = opts.fun;
- var child_id = opts.child_id;
-
var typeobj = t.grammar_constructors.funs[fun];
// do actual replacement
@@ -348,6 +374,7 @@ Editor.prototype.generate_random = function() {
server.get_random(args, cont, err);
}
+// Redraw tree
Editor.prototype.redraw_tree=function() {
var t = this;
var elem = node; // function from support.js
@@ -377,9 +404,11 @@ Editor.prototype.redraw_tree=function() {
}
}
+// Get and display linearisations for AST
Editor.prototype.update_linearisation=function(){
var t = this;
- function langpart(conc,abs) { // langpart("FoodsEng","Foods") == "Eng"
+ // langpart("FoodsEng","Foods") == "Eng"
+ function langpart(conc,abs) {
return hasPrefix(conc,abs) ? conc.substr(abs.length) : conc;
}
function row(lang, lin) {
@@ -426,6 +455,9 @@ Editor.prototype.import_ast = function(abstr) {
t.redraw_tree();
t.update_linearisation();
};
- server.pgf_call("abstrjson", args, cont);
+ var err = function(tree){
+ alert("Invalid abstract syntax tree");
+ };
+ server.pgf_call("abstrjson", args, cont, err);
}
diff --git a/src/www/syntax-editor/editor_menu.js b/src/www/syntax-editor/editor_menu.js
index 83375d68d..b5ceedd77 100644
--- a/src/www/syntax-editor/editor_menu.js
+++ b/src/www/syntax-editor/editor_menu.js
@@ -5,7 +5,13 @@ function EditorMenu(editor,opts) {
// default values for options:
this.options={
- target: "editor"
+ target: "editor",
+ show_grammar_menu: true,
+ show_startcat_menu: true,
+ show_to_menu: true,
+ show_random_button: true,
+ show_import: true,
+ show_export: true,
}
// Apply supplied options
@@ -16,50 +22,53 @@ function EditorMenu(editor,opts) {
this.ui = {
grammar_menu: empty_id("select","grammar_menu"),
startcat_menu: empty("select"),
- to_toggle: button("Languages...", function(){
- var sel = t.ui.to_menu;
- if (sel.classList.contains("hidden"))
- sel.classList.remove("hidden")
- else
- sel.classList.add("hidden")
+ to_toggle: button("Languages…", function(){
+ toggleHidden(t.ui.to_menu);
}),
to_menu: node("select",{
id: "to_menu",
multiple: "multiple",
class: "hidden"
}),
- wrap_button: button("Wrap", function(){
- t.editor.wrap_candidates();
- }),
- clear_button: button("Clear", function(){
- t.editor.clear_node();
- }),
random_button: button("Random", function(){
t.editor.generate_random();
}),
- debug_toggle: button("⚙", function(){
- var sel = element("debug");
- if (sel.classList.contains("hidden"))
- sel.classList.remove("hidden")
- else
- sel.classList.add("hidden")
+ import: {
+ toggle: button("Import…", function(){
+ toggleHidden(t.ui.import.panel);
+ }),
+ panel: node("div",{
+ id: "import",
+ class: "hidden"
+ }),
+ input: node("input",{type:"text"},[]),
+ button: button("Import", function(){
+ t.editor.import_ast(t.ui.import.input.value);
+ toggleHidden(t.ui.import.panel);
+ })
+ },
+ export_button: button("Export", function(){
+ alert(t.editor.ast.toString());
}),
+ debug_toggle: button("⚙", function(){
+ toggleHidden(element("debug"));
+ })
};
- if (t.options.show.grammar_menu) {
+ if (t.options.show_grammar_menu) {
appendChildren(t.container, [text(" Grammar: "), t.ui.grammar_menu]);
t.ui.grammar_menu.onchange = function(){
var grammar_url = t.ui.grammar_menu.value;
t.gm.change_grammar(grammar_url);
}
}
- if (t.options.show.startcat_menu) {
+ if (t.options.show_startcat_menu) {
appendChildren(t.container, [text(" Startcat: "), t.ui.startcat_menu]);
t.ui.startcat_menu.onchange = function(){
var startcat = t.ui.startcat_menu.value;
t.gm.change_startcat(startcat);
}
}
- if (t.options.show.to_menu) {
+ if (t.options.show_to_menu) {
appendChildren(t.container, [text(" To: "), t.ui.to_toggle, t.ui.to_menu]);
t.ui.to_menu.onchange = function(){
var languages = new Array();
@@ -71,11 +80,25 @@ function EditorMenu(editor,opts) {
t.gm.change_languages(languages);
}
}
- appendChildren(t.container, [t.ui.clear_button]);
- appendChildren(t.container, [t.ui.wrap_button]);
- if (t.options.show.random_button) {
+ if (t.options.show_random_button) {
appendChildren(t.container, [t.ui.random_button]);
}
+ if (t.options.show_import) {
+ appendChildren(t.container, [
+ t.ui.import.toggle,
+ t.ui.import.panel
+ ]);
+ appendChildren(t.ui.import.panel, [
+ text("Import AST: "),
+ t.ui.import.input,
+ t.ui.import.button
+ ]);
+ }
+ if (t.options.show_export) {
+ appendChildren(t.container, [
+ t.ui.export_button,
+ ]);
+ }
appendChildren(t.container, [t.ui.debug_toggle]);
/* --- Client state initialisation -------------------------------------- */
@@ -86,8 +109,6 @@ function EditorMenu(editor,opts) {
/* --- Register Grammar Manager hooks ----------------------------------- */
this.gm.register_action("onload", bind(this.hook_onload, this));
this.gm.register_action("change_grammar", bind(this.hook_change_grammar, this));
- // this.gm.register_action("change_startcat", bind(this.hook_change_startcat, this));
- // this.gm.register_action("change_languages", bind(this.hook_change_languages, this));
}