From 155dbddb77b8ba98e2f6e1fb4fc54db8f00ed2ea Mon Sep 17 00:00:00 2001
From: "Tate, Hongliang Tian" <tatetian@gmail.com>
Date: Thu, 19 Feb 2015 01:54:38 +0800
Subject: [PATCH] Add function and procedure construct

---
 PseudoCode.js      | 138 ++++++++++++++++++++++++++++++++++++++-------
 css/PseudoCode.css |   4 ++
 static.html        |   5 +-
 3 files changed, 126 insertions(+), 21 deletions(-)

diff --git a/PseudoCode.js b/PseudoCode.js
index 6622f34..6df9b40 100644
--- a/PseudoCode.js
+++ b/PseudoCode.js
@@ -23,7 +23,8 @@ in a context-free grammar:
     <require>       :== \REQUIRE + <text>
     <ensure>        :== \ENSURE + <text>
 
-    <block>         :== ( <control> | <statement> | <comment> )[0..n]
+    <block>         :== ( <control> | <function>
+                        | <statement> | <comment> | <call> )[0..n]
 
     <control>       :== <if> | <for> | <while>
     <if>            :== \IF{<cond>} + <block>
@@ -33,6 +34,9 @@ in a context-free grammar:
     <for>           :== \FOR{<cond>} + <block> + \ENDFOR
     <while>         :== \WHILE{<cond>} + <block> + \ENDWHILE
 
+    <function>      :== \FUNCTION{<name>}{<params>} <block> \ENDFUNCTION
+                        (same for <procedure>)
+
     <statement>     :== <state> |  <return> | <print>
     <state>         :== \STATE + <text>
     <return>        :== \RETURN + <text>
@@ -40,6 +44,8 @@ in a context-free grammar:
 
     <comment>       :== \COMMENT{<text>}
 
+    <call>          :== \CALL{<text>}
+
     <cond>          :== <text>
     <text>          :== <symbol> + <text> | { <text> } | <empty>
 
@@ -66,8 +72,7 @@ recursive descent parser is **simplity** for the structure of resulting program
 closely mirrors that of the grammar.
 
 TODO:
-    * \function and \procedure{name}{params}
-    * \call
+    * command name case-insensitive
     * noend
     * line number every k lines: \begin{algorithmic}[k]
     * caption without the number: \caption*{}
@@ -298,7 +303,7 @@ Parser.prototype.parse = function() {
         this._closeEnvironment(envName);
         root.addChild(envNode);
     }
-    //TODO: check this is the end of input
+    this._lexer.expect('EOF');
     return root;
 };
 
@@ -377,12 +382,18 @@ Parser.prototype._parseBlock = function() {
         var controlNode = this._parseControl();
         if (controlNode) { blockNode.addChild(controlNode); continue; }
 
+        var functionNode = this._parseFunction();
+        if (functionNode) { blockNode.addChild(functionNode); continue; }
+
         var commandNode = this._parseCommand(['STATE', 'PRINT', 'RETURN']);
         if (commandNode) { blockNode.addChild(commandNode); continue; }
 
         var commentNode = this._parseComment();
         if (commentNode) { blockNode.addChild(commentNode); continue; }
 
+        var callNode = this._parseCall();
+        if (callNode) { blockNode.addChild(callNode); continue; }
+
         break;
     }
 
@@ -395,6 +406,30 @@ Parser.prototype._parseControl = function() {
     if ((controlNode = this._parseLoop())) return controlNode;
 };
 
+Parser.prototype._parseFunction = function() {
+    var lexer = this._lexer;
+    if (!lexer.accept('func', ['FUNCTION', 'PROCEDURE'])) return null;
+
+    // \FUNCTION{funcName}{funcArgs}
+    var funcType = this._lexer.text(); // FUNCTION or PROCEDURE
+    lexer.expect('open');
+    var funcName = lexer.expect('ordinary');
+    lexer.expect('close');
+    lexer.expect('open');
+    var argsNode = this._parseText();
+    lexer.expect('close');
+    // <block>
+    var blockNode = this._parseBlock();
+    // \ENDFUNCTION
+    lexer.expect('func', 'END' + funcType);
+
+    var functionNode = new ParseNode('function',
+                        {type: funcType, name: funcName});
+    functionNode.addChild(argsNode);
+    functionNode.addChild(blockNode);
+    return functionNode;
+}
+
 Parser.prototype._parseIf = function() {
     if (!this._lexer.accept('func', 'IF')) return null;
 
@@ -471,6 +506,24 @@ Parser.prototype._parseComment = function() {
     return commentNode;
 };
 
+Parser.prototype._parseCall = function() {
+    var lexer = this._lexer;
+    if (!lexer.accept('func', 'CALL')) return null;
+
+    // \CALL { <ordinary> } { <text> }
+    lexer.expect('open');
+    var funcName = lexer.expect('ordinary');
+    lexer.expect('close');
+    lexer.expect('open');
+    var argsNode = this._parseText();
+    lexer.expect('close');
+
+    var callNode = new ParseNode('call');
+    callNode.value = funcName;
+    callNode.addChild(argsNode);
+    return callNode;
+};
+
 Parser.prototype._parseCond =
 Parser.prototype._parseText = function() {
     var textNode = new ParseNode('text');
@@ -550,6 +603,7 @@ function Builder(parser, options) {
     this._root = parser.parse();
     this._options = new BuilderOptions(options);
     this._blockLevel = 0;
+    this._openLine = false;
     console.log(this._root.toString());
 }
 
@@ -578,6 +632,22 @@ Builder.prototype._endDiv = function() {
     this._body.push('</div>');
 }
 
+Builder.prototype._newLine = function() {
+    if (this._openLine) this._endLine();
+    this._openLine = true;
+    this._beginLine();
+}
+
+Builder.prototype._beginBlock = function() {
+    if (this._openLine) this._endLine();
+    this._blockLevel++;
+}
+
+Builder.prototype._endBlock = function() {
+    if (this._openLine) this._endLine();
+    this._blockLevel--;
+}
+
 Builder.prototype._beginLine = function() {
     var className = 'ps-line';
     if (this._blockLevel > 0) { // this line is code
@@ -597,12 +667,17 @@ Builder.prototype._beginLine = function() {
 Builder.prototype._endLine = function() {
     this._body.push('</span>')
     this._body.push('</p>');
+    this._openLine = false;
 }
 
 Builder.prototype._typeKeyword = function(keyword) {
     this._body.push('<span class="ps-keyword">' + keyword + '</span>');
 }
 
+Builder.prototype._typeFuncName = function(funcName) {
+    this._body.push('<span class="ps-funcname">' + funcName + '</span>');
+}
+
 Builder.prototype._typeText = function(text) {
     this._body.push('<span>' + text + '</span>');
 }
@@ -664,9 +739,30 @@ Builder.prototype._buildTree = function(node) {
         // node: <block>
         // ==>
         // HTML: <div class="ps-block"> ... </div>
-        this._blockLevel++;
+        this._beginBlock();
         this._buildTreeForAllChildren(node);
-        this._blockLevel--;
+        this._endBlock();
+        break;
+    case 'function':
+        // \FUNCTION{<ordinary>}{<text>} <block> \ENDFUNCTION
+        // ==>
+        // function <ordinary>(<text>)
+        // ...
+        // end function
+        var funcType = node.value.type.toLowerCase();
+        var funcName = node.value.name;
+        var textNode = node.children[0];
+        var blockNode = node.children[1];
+        this._newLine();
+        this._typeKeyword(funcType);
+        this._typeFuncName(funcName);
+        this._typeText('(');
+        this._buildTree(textNode);
+        this._typeText(')');
+
+        this._buildTree(blockNode);
+        this._newLine();
+        this._typeKeyword('end ' + funcType);
         break;
     case 'if':
         // \IF { <cond> }
@@ -676,12 +772,11 @@ Builder.prototype._buildTree = function(node) {
         //      ...
         //      <span class="ps-keyword">then</span>
         // </p>
-        this._beginLine();
+        this._newLine();
         this._typeKeyword('if');
         var cond = node.children[0];
         this._buildTree(cond);
         this._typeKeyword('then');
-        this._endLine();
         // <block>
         var ifBlock = node.children[1];
         this._buildTree(ifBlock);
@@ -696,12 +791,11 @@ Builder.prototype._buildTree = function(node) {
             //      ...
             //      <span class="ps-keyword">then</span>
             // </p>
-            this._beginLine();
+            this._newLine();
             this._typeKeyword('if');
             var elifCond = node.children[2 + 2 * ei];
             this._buildTree(elifCond);
             this._typeKeyword('then');
-            this._endLine();
 
             // <block>
             var elifBlock = node.children[2 + 2 * ei + 1];
@@ -716,9 +810,8 @@ Builder.prototype._buildTree = function(node) {
             // <p class="ps-line">
             //      <span class="ps-keyword">else</span>
             // </p>
-            this._beginLine();
+            this._newLine();
             this._typeKeyword('else');
-            this._endLine();
 
             // <block>
             var elseBlock = node.children[node.children.length - 1];
@@ -726,9 +819,8 @@ Builder.prototype._buildTree = function(node) {
         }
 
         // ENDIF
-        this._beginLine();
+        this._newLine();
         this._typeKeyword('end if');
-        this._endLine();
 
         break;
     case 'loop':
@@ -740,12 +832,11 @@ Builder.prototype._buildTree = function(node) {
         //      <span class="ps-keyword">do</span>
         // </p>
         var loopName = node.value.toLowerCase();
-        this._beginLine();
+        this._newLine();
         this._typeKeyword(loopName);
         var cond = node.children[0];
         this._buildTree(cond);
         this._typeKeyword('do');
-        this._endLine();
 
         // <block>
         var block = node.children[1];
@@ -756,9 +847,8 @@ Builder.prototype._buildTree = function(node) {
         // <p class="ps-line">
         //      <span class="ps-keyword">end for</span>
         // </p>
-        this._beginLine();
+        this._newLine();
         this._typeKeyword('end ' + loopName);
-        this._endLine();
 
         break;
     case 'command':
@@ -776,11 +866,21 @@ Builder.prototype._buildTree = function(node) {
         if (displayName) this._typeKeyword(displayName);
         var text = node.children[0];
         this._buildTree(text);
-        this._endLine();
 
         break;
     // 'comment':
     //     break;
+    case 'call':
+        // \CALL{funcName}{funcArgs}
+        // ==>
+        // funcName(funcArgs)
+        var funcName = node.value;
+        var argsNode = node.children[0];
+        this._typeFuncName(funcName);
+        this._typeText('(');
+        this._buildTree(argsNode);
+        this._typeText(')');
+        break;
     case 'cond':
     case 'text':
         this._buildTreeForAllChildren(node);
diff --git a/css/PseudoCode.css b/css/PseudoCode.css
index 9d0e9f1..da19ae7 100644
--- a/css/PseudoCode.css
+++ b/css/PseudoCode.css
@@ -25,6 +25,10 @@
     font-size: 1.21em;
     line-height: 1.2;
 }
+.pseudo .ps-funcname {
+    font-family: serif;
+    font-variant: small-caps;
+}
 /* keyword */
 .pseudo .ps-keyword {
     font-weight: 700;
diff --git a/static.html b/static.html
index 3c1d75e..7d20519 100644
--- a/static.html
+++ b/static.html
@@ -33,8 +33,7 @@
         \end{algorithmic}
         \end{algorithm}
         \begin{algorithmic}
-        \REQUIRE this is shit!
-        \ENSURE i think so
+        \PROCEDURE{Simple}{}
         \STATE bigger
         \WHILE{1+1}
             \IF{n < 10}
@@ -44,8 +43,10 @@
                 \STATE $ f(x) = \int_{-\infty}^\infty
     \hat f(\xi)\,e^{2 \pi i \xi x}
     \,d\xi $
+                \STATE $m \gets$ \CALL{Simple}{}
             \ENDIF
         \ENDWHILE
+        \ENDPROCEDURE
         \end{algorithmic}
     </pre>
     <script type="text/javascript">
-- 
GitLab