SFPW_2014 :: YAW_20140820Yet Another Wiki - epsilonwiki

documents in HTML or PDF (via Latex) form. Scribble helps to ... style="font:bold 2em Georgia; ... Words can be bold, italicized, underlined (and bold, italicized ...
3MB taille 2 téléchargements 275 vues
alpha +

SFPW_2014 :: YAW_20140820Yet

Another Wiki

Alain Marty Engineer Architect 66180, Villeneuve de la Raho, France [email protected]

Abstract alphawiki is a small environment working on top of any modern browser for authoring, styling and scripting dynamic WEB pages using a simple and unique LISP-like syntax, lambdatalk. Initialy built on a single loop working on a unique Regular Expression and a small dictionary of hundred primitive functions, lambdatalk allows one to write and style text using a coherent S-expression based notation. Then, grace to a minimal set of special forms, [lambda, def, if], it offers the capabilities of a programmable programming language with user functions extending the dictionary, recursive functions (and loops), first class functions (and curry), structured datas (ie. arrays, vectors, polynoms) and some other capabilities. The present paper will focus on lambdatalk. In a first section, its functionalities will be progressively introduced, starting as a simple S-expressions evaluator to reach the level of a minimal programmable programming language. Then will be analyzed the Regular Expressions based underlying engine, which doesn't follow the standard approach popularised by LISP and based on a syntaxic tree built and recursively walked through and evaluated.

Keywords Lisp, Scheme, Javascript, Regular Expressions, CMS, wiki.

1.......Introduction 1.1......The context Web browsers parse data such as HTML code, CSS rules and JS scripts stored on the server side and display rich multimedia dynamic pages on the client side. Script languages such as PHP and Javascript allow interactions with these data leading to web applications. Hundreds of engines have been built, managing files on the server side and interfaces on the client side, such as Wordpress, Wikipedia, Joomla,.... The de facto standard Markdown syntax is widely used to simplify and unify the markup and the styling but doesn't give any help on the scripting side. Works have been done to build unified syntaxes, for instance: Skribe [1] by Erick Gallesio and Manuel Serrano: a functional programming language designed for authoring documents, such as web pages or technical reports. It is built on top of the Scheme programming language. Its concrete syntax is simple and it looks familiar to anyone used to markup languages., Scribble [2] by Matthew Flatt and Eli Barzilay: The Racket Documentation Tool, is a collection of tools for creating prose documents in HTML or PDF (via Latex) form. Scribble helps to write programs that are rich in textual content, whether the content is prose to be typeset or any other form of text to be generated programmatically, LAML [3] by Kurt Nørmark: The main idea in this work is to bring XML and HTML into the domain of the Scheme programming language, and as such let the author and programmer use the power of abstraction and programmed solutions when he or she is doing web work. All of these projects, closely related to the Scheme language, are great and powerful. With the plain benefit of existing Scheme implementations they make a strong junction between the markup, styling and programming syntaxes. But:

1) as convenient as the Scheme syntax has proven to be, it is unfitting for "easily" dealing with the textual content of a CMS, 2) these tools are definitively devoted to developers, not precisly to beginners and webdesigners. Another approach might be considered to give all of them a common environment for a collective creation.

1.2......In this paper The 100kb alphawiki's engine can be easily installed on any ISP. It's mainly built on two small "cylinders": 1) PHP.php: on the server side a 460 lines PHP code does everything about pages data, reading and writing, security, administration, ... 2) JS.js: on the client side a 1000 lines JS code manages the user interface and contains the code interpreter. The present contribution will forget alphawiki and will focus on the interpreter, lambdatalk: in "2. Using lambdatalk" will be shown writing, styling and coding in lambdatalk. in "3. Lambdatalk inside" will be quickly analyzed the lambdatalk underlying Javascript engine.

2.......Using lambdatalk 2.1.......Writing some code An alphawiki website is made of several pages sharing the same appearance. Each page can be edited (by authorized users) via a frame editor, the result being evaluated and displayed in real-time in the wiki page. This is the "Hello World" example showing the linked frame editor and wiki page:

Consider a more complex code written in the wiki's editor frame: {ul {li Words can be {b bold}, {i italicized}, {u underlined} (and {b {i {u bold, italicized, underlined}}}). } {li I'm fan of {a {@ href="http://www.pixar.com"}PIXAR}. } {li {span {@}Hello World}. } {li The hypotenuse of a right rectangle of sides 3 and 4 is equal to {sqrt {+ {* 3 3} {* 4 4}}}. } {li {input {@ type = "text" placeholder = "Please, enter your name" onkeyup="getId('yourName').innerHTML =

'Hello '+this.value+' !'" }} } } {h1 {@ id="yourName"}} This is how this code is displayed in the wiki page: Words can be bold, italicized, underlined (and bold, italicized, underlined). I'm fan of PIXAR.

Hello World. The hypotenuse of a right rectangle of sides 3 and 4 is equal to 5. Ward Cunningham

Hello Ward Cunningham ! We can see that a lambdatalk code is made of nested s-expressions sharing the same shape: {first rest}: 1) first must be a word belonging to a dictionary. In the example shown above we can distinguish some of them: ul starts an unordered list, li starts a list item, b, i, u are for "bold", "italic", "underline", @ opens a list of HTML and CSS attributes, a creates a link waiting for attributes, here an URL via the HTML attribute "src" span build an inline container waiting for HTML attributes, here a "style" list of CSS rules, sqrt is a math function computing the square root of a number, + and * are operators adding and multiplying numbers, input creates an input textfield and a behaviour driven by a Javascript code, and h1 starts a title of level one. 2) rest can be any text containing or not s-expressions, leading to a tree structure. 3) first and rest are framed between an opening and a closing curly braces acting as escape characters launching the evaluation process. In a standard text curly braces {} are much less frequent than round braces () and it's the reason they have been chosen for bracketing s-expressions. Note that, except for the value of HTML attributes following the standard HTML conventions, the texts are not surrounded by quotation marks or anything like that. This corrects the problem mentioned in the works recalled in the introduction.

2.2.......The evaluation process We will call leaf a terminal s-expression which doesn't contain any s-expression. The lambdatalk interpreter evaluates the nested s-expressions from the leaves to the root. The evaluation process is the application of a function, belonging to the dictionary and linked to the word first, to the arguments contained in rest. For instance: in {u Hello World}, u applies the HTML tag u to Hello World and returns < u>Hello World< /u> to the browser which will display Hello World, in {* 3 4} * calls the Javascript operator * and returns 3*4 = 12; {* 1 x} will return NaN because x is Not A Number, and when an s-expression is not evaluable, for instance {foo bar}, it will be returned unevaluated, just underlined, as it is: {foo bar}, without any error message polluting the wiki text!

2.3.......The dictionary The lambdatalk's core dictionary contains a list of words linked to some primitive functions. Writing {lib} displays:

lib, serie, map, reduce, >, < , >=, lambda_6195 // OK, it's a function waiting for 2 values {boo 1} -> lambda_3262 // it's a function waiting for 1 value {boo 1 2} -> 3 // OK, it's called with two values

{{boo 1} 2} -> 3 // OK, it's called in two steps {boo 1 2 3} -> 3 // OK, no matter with extra values

{lambda {:n} {div {@ style= "float:right; font-size:12px;"}:n}}} {def radicand {@ style= "text-decoration:overline;"}}

As an application, writing derivees of any order is straightforward: {def D {lambda {:f :x} {/ {- {:f {+ :x 0.01}} {:f {- :x 0.01}} } 0.02} }} {def cubic {lambda {:x} {* :x :x :x}}} {round {round {round {round {round

{cubic 2}} {{D cubic} 2}} {{D {D cubic}} 2}} {{D {D {D cubic}}} 2}} {{D {D {D {D cubic}}}} 2}}

-> -> -> -> ->

8 12 12 6 0

2.5.5.......quadratic equation In lambdatalk words and numbers can easily be mixed, without any string quotation or any special printing format. This is an example giving the roots of a quadratic equation ax2 + bx + c = 0: {def equation {lambda {:a :b :c} {{lambda {:a :b :c :d} discriminant = :d {if {> :d 0} then 2 real roots : x1 = {/ {- {- :b} {sqrt :d}} {* 2 :a}} x2 = {/ {+ {- :b} {sqrt :d}} {* 2 :a}} else {if {= :d 0} then 1 double real root : x = {/ {- :b} {* 2 :a}} else 2 complex roots : x1 = [{/ {- :b} {* 2 :a}} , -{/ {sqrt {- :d}} {* 2 :a}}] x2 = [{/ {- :b} {* 2 :a}} , +{/ {sqrt {- :d}} {* 2 :a}}] }} } :a :b :c {+ {* :b :b} {* 4 :a :c}}} }} 1) {b equation [1 -1 1]} -> {equation 1 -1 1} 2) {b equation [1 -2 1]} -> {equation 1 -2 1} 3) {b equation [1 1 -1]} -> {equation 1 1 -1} displays: 1) equation [1 -1 1] -> discriminant = 5 2 real roots : x1 = -0.6180339887498949 x2 = 1.618033988749895 2) equation [1 -2 1] -> discriminant = 0 1 double real root : x=1 3) equation [1 1 -1] -> discriminant = -3 2 complex roots : x1 = [-0.5 , -0.8660254037844386] x2 = [-0.5 , +0.8660254037844386]

2.5.6......mathematical notations As long as the mathML tags won't be recognized by Chrome, lambdatalk can be used to display formulas.

2.5.6.1 defining some specific functions {def numero

{def quotient {lambda {:h} {@}}} {def quotient_line {lambda {:w} {div {@}}}} displays: numero radicand quotient quotient_line

2.5.6.2 using these functions to display formulas x = {div {quotient 1.0} {div -b ± √{span {radicand} b{sup 2} - 4ac}} {quotient_line 100} {div 2a}} {numero 1.1} Δf(x,y,z) = {div {quotient 1.0} {div ∂{sup 2}f(x,y,z)} {quotient_line 60} {div ∂x{sup 2}}} + {div {quotient 1.0} {div ∂{sup 2}f(x,y,z)} {quotient_line 60} {div ∂y{sup 2}}} + {div {quotient 1.0} {div ∂{sup 2}f(x,y,z)} {quotient_line 60} {div ∂z{sup 2}} } {numero 1.2} displays: -b ± √b2 - 4ac 2a ∂2f(x,y,z) ∂2f(x,y,z) ∂2f(x,y,z) Δf(x,y,z) = + + ∂x2 ∂y2 ∂z2 x=

1.1 1.2

2.5.7.......drawing in a wiki page Its possible to play with bezier curves, lambdatalk and CSS, without any canvas. Writing: {def V.x {lambda {:p} {first {:p}}}} {def V.y {lambda {:p} {rest {:p}}}} {def intp3 {lambda {:a0 :a1 :a2 :a3 :t :u} {round {+ {* :a0 :u :u :u 1} {* :a1 :u :u :t 3} {* :a2 :u :t :t 3} {* :a3 :t :t :t 1}}} }} {def bezier {lambda {:p0 :p1 :p2 :p3 :t} {intp3 {V.x :p0} {V.x :p1} {V.x :p2} {V.x :p3} :t {- 1 :t}} {intp3 {V.y :p0} {V.y :p1} {V.y :p2} {V.y :p3} :t {- 1 :t}} }} {def dot {lambda {:x :y :r :bord :back} {span {@ style=" position:absolute; left:{- :x {/ :r 2}}px; top:{- :y {/ :r 2}}px;

width::rpx; height::rpx; border-radius::rpx; border:1px solid :bord; background::back; "}}}} {def {def {def {def {def {def

P0 200 30} P1 410 80} P2 200 250} P3 410 170} P0123 P0 P1 P2 P3} P0132 P0 P1 P3 P2}

{dot {P0} 20 black yellow} {dot {P1} 20 black cyan} {dot {P2} 20 black cyan} {dot {P3} 20 black yellow} {map {lambda {:t} {dot {bezier {P0123} :t} 5 black red}} {serie -0.2 1.2 0.0125}} {map {lambda {:t} {dot {bezier {P0132} :t} 5 black blue}} {serie -0.2 1.2 0.0125}}

to integrate Tables of Content, a Forum, Lightboxes, a 2D Drawing, a Ray-Tracing, 3D shapes, Fractals, a Lisp, a spreadsheet.

Spreadsheets are known to be a good illustration of the functional approach. With lambdatalk it's possible to insert a spreadsheet in a wiki page and to make some calculus. For instance, the symbolicexpression {+ {lc 2 4} {lc 3 4} {lc 4 4}} written in the cell L6C4 will display the sum of the contents of cells L2C4, L3C4 and L4C4, as it can be seen in the figure below:

will display these two cubic curves, directly in the wiki page:

2.5.8.......the end user is not forgotten! Let's look at the first code shown at the beginning of this page. In order to make things easier for the beginner, lambdatalk comes with some "sugar" for blocks between line returns, ie. titles, paragraphs and lists: h1, p, ul, ol and for external and internal links: a. With some help of a coder defining a couple of useful functions: {def blue span {@} } {def hello-input {lambda {:hi} {input {@ type="text" placeholder="Please, enter your name" onkeyup="getId('yourName').innerHTML = ' :hi ' + this.value + ' !'" }} {h1 {@ id="yourName"}} }}

alphawiki can be considered as a stack of pages. In the same way, a spreadsheet embedded in a page can be viewed as a grid of micro-pages with all the lambdatalk's capabilities. Numerous usages of lambdatalk and several more complex examples of its functionalities can be seen in the alphawiki website. As a first conclusion, with a dictionary of about a hundred primitive functions and a minimal set of three special forms, [lambda, def, if], we have seen that lambdatalk allows writing, styling and scripting in a unique, coherent and readable syntax. The underlying engine of lambdatalk is introduced in the following section.

3.......Lambdatalk inside Lambdatalk is not a Lisp dialect and the evaluation process is quite different. In a Lisp console, everything is evaluated: for instance the word PI would be evaluated to its value, 3.141592653589793. In order to prevent the evaluation of an S-expression quoting must be used, for instance: {quote PI} or 'PI. In the alphawiki context nothing is evaluated: the code is just text and not a sequence of symbols, (ie. sqrt), numbers, (ie. 123.45), or quoted strings, (ie. "Hello World"). Curly braces must be used to trigger the evaluation via s-expr.

this is how this simplified code could be written by the end user:

3.1.......the evaluate() function

_ul Words can be {b bold}, {i italicized}, {u underlined} (and {b {i {u bold, italicized, underlined}}}). _ul I'm fan of [ [PIXAR|http://www.pixar.com]]. _ul {{blue}Hello World}. _ul The hypotenuse of a right rectangle of sides 3 and 4 is equal to {sqrt {+ {* 3 3} {* 4 4}}}. _ul {hello-input Bonjour}

Well formed s-expressions keyed in the frame editor are caught and evaluated by the evaluate() function. Unbalanced s-expressions lock the evaluation. The evaluation process of a leaf, a terminal S-expression, {first rest} is made in two stages: 1) a pre-processing phase and 2) a single loop. For some "special" values of first the s-expr evaluation is made in the pre-processing phase. For the others, leaves are first evaluated (ie. replaced by some text) in the loop until there is no more leaf. The evaluation of a leaf {first rest} is made in a global environment, named dictionary, according to the values of first and rest.

with the Hello name's choice given as an add-on.

2.5.9.......plugins Lambdatalk can call more complex (Java)scripts executed interactively in the wiki page via lambdatalk functions. It's possible

var evaluate = function(str) { str = preprocessing(str); var bal = balance(str);

if (bal.left != bal.right) str = 'none'; else { str = eval_special_forms(str); str = eval_sexprs(str); } return str; };

3.2.......the main Regular Expression lambdatalk uses a single Regular Expression to catch terminal s-expressions: var loop_rex = /\{([^\s{}]*)(?:[\s]*)([^{}]*)\}/g; 1) / start of the regexp 2) \{ begins with a { 3) ([^\s{}]*) everything except "\s{}": first 4) (?:[\s]*) zero or several spaces 5) ([^{}]*) everything except "{}" : rest 6) \} ends with a } 7) / end of the regexp 8) g go next -

3.3.......evaluating S-expressions Following a code snippet shared by Steven Levithan [4], the eval_sexprs() function is a one line single loop using the previous Regular Expression to catch s-expressions and a do_apply() function to replace them by their value: var eval_sexprs = function(str) { while (str != (str = str.replace( loop_rex, do_apply))) ; return str; }; var do_apply = function() { var first = arguments[1] || '', rest = arguments[2] || ''; if (dict.hasOwnProperty(first)) return dict[first].apply(null, [rest]); else return hide_braces( '< u>{' + first + ' ' + rest + '}< /u>'); }; If first belongs to the dictionary it's applyed to to the rest array, if not the s-expression is returned unevaluated.

3.4.......evaluating special forms During the pre-processing phase, the special forms are caught in this order: 1) if, 2) lambda, 3) def and evaluated by specific functions. var eval_special_forms = function(str) { str = eval_ifs(str); str = eval_lambdas(str); str = eval_defs(str, true); return str; };

3.5.......evaluating lambdas The eval_lambdas() function catches every s-expressions {lambda {:args} body} and replaces it by a random name. This name is associated with a function which will replace in the body the occurrences of the arguments by the given values when the function is called. var eval_lambdas = function(str) { while (true) { var s = catch_sexpression('lambda', str);

if (s === 'none') break; str = str.replace( '{lambda '+s+'}',eval_lambda(s.trim())); } return str; }; var eval_lambda = function (s) { s = eval_lambdas(s); var name = 'lambda_' + Math.floor(Math.random()*1000000), args = s.substring(1, s.indexOf('}')) .trim().split(' '), body = s.substring(s.indexOf('}')+1) .trim(), reg_args = []; for (var i=0; i < args.length; i++) reg_args[i] = RegExp( args[i], 'g'); dict[name] = function () { var vals = arguments[0] .replace(/\s{2,}/g, ' ') .trim().split(' '); return function (bod) { if (vals.length < args.length) { for (var i=0; i < vals.length; i++) bod = bod.replace(reg_args[i],vals[i]); var _args = args.slice(vals.length).join(' '); bod = eval_lambdas( '{lambda {'+_args+'}'+bod+'}'); } else if (vals.length==args.length) { for (var i=0; i < vals.length; i++) bod = bod.replace(reg_args[i],vals[i]); } else { // vals.length > args.length for (var i=0; i < args.length; i++) bod = bod.replace(reg_args[i],vals[i]); } return bod; }(body); }; return name; }; Note that lambdas can be nested.

3.6.......evaluating defs The eval_def() function catches every s-expressions {def name body} and replaces it by the given name. var eval_defs = function(str, flag) { while (true) { var s = catch_sexpression('def', str); if (s === 'none') break; str = str.replace('{def '+s+'}', eval_def(s.trim(), flag)); } return str; }; var eval_def = function (s, flag) { s = eval_defs( s, false ); var name = s.substring(0, s.indexOf(' ')) .trim(), body = s.substring(s.indexOf(' ')) .trim(); dict[name] = (dict.hasOwnProperty(body))? dict[body] : function () { return body }; delete dict[body]; return (flag)? name : ''; }; Note that defs can be nested but aren't local to the outside def.

3.7.......evaluating ifs We have seen that the {if bool_term then then_term else else_term}

special form deserves a special attention. The evaluation of the then_term and the else_term must be delayed until the boolean_term gets a value. The process is made in two steps. During the pre-processing phase the eval_ifs() function replaces the special form by the s-expression {_if_ bool_term then then_term else else_term} where _if_ is an associated function belonging to the dictionary and the then_term and the else_term have been "deactivated". During the s-expressions evaluation phase, this function will return the then_term or the else_term according to the value of the bool_term. var eval_ifs = function(str) { while (true) { var s = catch_sexpression('if', str); if (s === 'none') break; str = str.replace('{if '+s+'}', eval_if(s.trim())); } return str; }; var eval_if = function(s) { s = eval_ifs(s); var pif = parse_if( s ); return '{_if_ ' + pif[0] + ' then ' + hide_braces(pif[1]) + ' else ' + hide_braces(pif[2]) + '}'; }; var parse_if = function(s) { var index1 = s.indexOf('then'), index2 = s.indexOf('else'), bool_term = s.substring(0,index1) .trim(), then_term = s.substring(index1+5,index2) .trim(), else_term = s.substring(index2+5) .trim(); return [bool_term, then_term, else_term]; }; Note that ifs can be nested.

3.8.......some primitives in the dictionary The dictionary is an associative array: var dict = {}; Every elements of the dictionary have this structure : dict[ tag ] = function () { .. .. }; // coming from the function catch_sexprs(), // arguments come as a single element array, // so "var args = arguments[0]" // gives arguments as a string // and "var args = arguments[0].split(' ')" // gives arguments as an array The if special form is closely related to the _if_ function belonging to the dictionary: dict['_if_'] = function () { var pif = parse_if( arguments[0] ); return (pif[0] === "true")? show_braces(pif[1]) : show_braces(pif[2]); }; Following, just a few examples of primitive functions: dict['*'] = function() { var args = arguments[0].split(' '); for (var r=1, i=0; i< args.length; i++) if (args[i] !== '')

r *= args[i]; return r; }; dict['first'] = function () { var args = arguments[0].split(' '); return args[0]; } dict['rest'] = function () { var args = arguments[0].split(' '); return args.slice(1).join(' '); } dict['b'] = function () { return '< b>' + arguments[0] + '< /b>' }; More can be seen in the the JS.js file.

3.9.......about evaluation speed alphawiki++lambdatalk allows a rather comfortable realtime edition of a standard page. Tested on a MacBook Air: Pages's content in chars

Speed

1

A page containing about 5,000 chars

1 to 2 ms

3

Pages between 20,000 and 50,000 chars

5 to 15 ms

A very heavy test page "Jules Verne, Ile mystérieuse" built on a plain text of 2 1,228,778 chars with a TOC of 62 chapters (about 15 360 lines = 300 pages of 50 lines)

about 70 ms

It's evident that heavy non tail-recursive functions may require more CPU time. Anyway, the natural modular structure of a wiki, a stack of cards, helps to limit the load to a bearable value.

4. ..... Conclusion This paper was intended to show some usages of lambdatalk and to give a quick view of its underlying engine. More can be seen in the alphawiki++ website : http://epsilonwiki.free.fr/alphawiki_2/. 1) The lambdatalk syntax is small, simple and easy to be used by any beginner and any webdesigner. 2) The underlying JS code is small, simple and easy to be mastered by any JS developer. 3) The underlying JS code appears to be fast enough to be usable in the context of webdesign, and powerful enough to follow some more complex developer's experimentations. With alphawiki++lambdatalk, the beginner, the web-designer and the developer benefit from a simple text editor and a common syntax allowing them, in a gentle learning slope and a collaborative work, to build sets of complex and dynamic pages. This paper has been built in alphawiki++ and exported as a PDF (A4 format) directly from the browser (Firefox). alphawiki++ is free, under the GNU Copyleft Licence.

5...... References [1] : Erick Gallesio and Manuel Serrano, http://www-sop.inria.fr /members/Manuel.Serrano/publi/jfp05/article.html#The-Skribeevaluator [2] : Matthew Flatt and Eli Barzilay, http://docs.racket-lang.org /scribble/ [3] : Kurt Nørmark, http://people.cs.aau.dk/~normark/laml/ [4] : Steven Levithan, http://blog.stevenlevithan.com/archives /reverse-recursive-pattern [5] : Alain Marty, http://epsilonwiki.free.fr/alphawiki_2/