インタネットベースのプログラミング言語HTML5

実行用のソースコードはこんな感じ。

window.log = function(msg) {console.log(msg)};

window.addEventListener('load', function() {
  var stack = [{vars:{}, funcs:{}}];

  function topContext() {
    return stack[stack.length - 1];
  }

  function readParam(node) {
    var ret = '';
    for (var i = 0; i < node.childNodes.length; i++) {
      var child = node.childNodes[i];
      if (child.nodeType == document.TEXT_NODE) {
        ret += child.nodeValue;
      }
      else if (child.nodeName == 'OUTPUT') {
        ret += topContext().vars[child.getAttribute('name')];
      }
    }
    return ret;
  }

  function callFunc(name, args) {
    var vars = {};
    var def = topContext().funcs[name];
    var code;
    for (var i = 0; i < def.childNodes.length; i++) {
      var child = def.childNodes[i];
      if (child.tagName == 'P') {
        vars[child.getAttribute('name')] = args.shift();
      }
      else if (child.tagName == 'CODE') {
        code = child;
      } 
    }   
    stack.push({vars:vars, funcs:{}});
    interprete(code);
    stack.pop();
  }

  function interprete(node) {
    if (!node) node = document.body;
    if (node.nodeName == 'BODY') {
      for (var i = 0; i < node.childNodes.length; i++) {
        interprete(node.childNodes[i]);
      }
    }
    else if (node.nodeName == 'INPUT') {
      topContext().vars[node.name] = node.value;
    }
    else if (node.nodeName == 'FIELDSET') {
      val = readParam(node);
      topContext().vars[node.getAttribute('name')] = val;
    }
    else if (node.nodeName == 'VAR' || node.nodeName == 'OUTPUT') {
    }
    else if (node.nodeName == 'DFN') {
      topContext().funcs[node.getAttribute('title')] = node;
    }
    else if (node.nodeName == 'CODE') {
      for (var i = 0; i < node.childNodes.length; i++) {
        interprete(node.childNodes[i]);
      }
    }
    else if (node.nodeName == 'BLOCKQUOTE') {
      var commandName = node.getAttribute('cite');
      var args = [];
      for (var i = 0; i < node.childNodes.length; i++) {
        var child = node.childNodes[i];
        if (child.nodeName == 'P') {
          args.push(readParam(child));
        }
      }
      if (window[commandName]) {
        // builtin function
        window[commandName].apply(window, args);
      }
      else {
        callFunc(commandName, args);
      }
    }
    else if (node.nodeName == 'DL') {
      var condition = topContext().vars[node.getAttribute('name')];
outer:
      for (var i = 0; i < node.childNodes.length; i++) {
        var child = node.childNodes[i];
        if (child.nodeName == 'DT') {
          if (child.childNodes.length == 0  // default condition
            || child.innerHTML == condition) {
            for (var j = i + 1; j < node.childNodes.length; j++) {
              var grandChild = node.childNodes[j];
              if (grandChild.nodeName == 'DD') {
                for (var k = 0; k < grandChild.childNodes.length; k++) {
                  interprete(grandChild.childNodes[k]);
                  break outer;
                }
              }
            }
          }
        }
      }
    }
    else if (node.nodeName == 'PROGRESS') {
      var counter = node.getAttribute('name')
      var min = node.getAttribute('value') || 0;
      var max = node.getAttribute('max');
      for (var i = min; i <= max; i++) {
        topContext().vars[counter] = i;
        for (var j = 0; j < node.childNodes.length; j++) {
          interprete(node.childNodes[j]);
        }
      }
    }
  }
  if (document.body.getAttribute('autostart')) {
    interprete();
  }
});