From 3c9d4264ff9a23f634aaac683564fd5d143395eb Mon Sep 17 00:00:00 2001 From: lupyuen Date: Fri, 7 May 2021 16:26:39 +0800 Subject: [PATCH 01/48] Copy Dart code generator to Lisp --- generators/lisp/colour.js | 114 +++++++ generators/lisp/lists.js | 447 ++++++++++++++++++++++++ generators/lisp/logic.js | 127 +++++++ generators/lisp/loops.js | 169 ++++++++++ generators/lisp/math.js | 485 +++++++++++++++++++++++++++ generators/lisp/procedures.js | 111 ++++++ generators/lisp/text.js | 343 +++++++++++++++++++ generators/lisp/variables.js | 32 ++ generators/lisp/variables_dynamic.js | 21 ++ 9 files changed, 1849 insertions(+) create mode 100644 generators/lisp/colour.js create mode 100644 generators/lisp/lists.js create mode 100644 generators/lisp/logic.js create mode 100644 generators/lisp/loops.js create mode 100644 generators/lisp/math.js create mode 100644 generators/lisp/procedures.js create mode 100644 generators/lisp/text.js create mode 100644 generators/lisp/variables.js create mode 100644 generators/lisp/variables_dynamic.js diff --git a/generators/lisp/colour.js b/generators/lisp/colour.js new file mode 100644 index 00000000000..0ef54f24379 --- /dev/null +++ b/generators/lisp/colour.js @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for colour blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.colour'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp.addReservedWords('Math'); + +Blockly.Lisp['colour_picker'] = function(block) { + // Colour picker. + var code = Blockly.Lisp.quote_(block.getFieldValue('COLOUR')); + return [code, Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['colour_random'] = function(block) { + // Generate a random colour. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'colour_random', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + '() {', + ' String hex = \'0123456789abcdef\';', + ' var rnd = new Math.Random();', + ' return \'#${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\'', + ' \'${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\'', + ' \'${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\';', + '}']); + var code = functionName + '()'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['colour_rgb'] = function(block) { + // Compose a colour from RGB components expressed as percentages. + var red = Blockly.Lisp.valueToCode(block, 'RED', + Blockly.Lisp.ORDER_NONE) || 0; + var green = Blockly.Lisp.valueToCode(block, 'GREEN', + Blockly.Lisp.ORDER_NONE) || 0; + var blue = Blockly.Lisp.valueToCode(block, 'BLUE', + Blockly.Lisp.ORDER_NONE) || 0; + + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'colour_rgb', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(num r, num g, num b) {', + ' num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round();', + ' String rs = rn.toInt().toRadixString(16);', + ' rs = \'0$rs\';', + ' rs = rs.substring(rs.length - 2);', + ' num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round();', + ' String gs = gn.toInt().toRadixString(16);', + ' gs = \'0$gs\';', + ' gs = gs.substring(gs.length - 2);', + ' num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round();', + ' String bs = bn.toInt().toRadixString(16);', + ' bs = \'0$bs\';', + ' bs = bs.substring(bs.length - 2);', + ' return \'#$rs$gs$bs\';', + '}']); + var code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['colour_blend'] = function(block) { + // Blend two colours together. + var c1 = Blockly.Lisp.valueToCode(block, 'COLOUR1', + Blockly.Lisp.ORDER_NONE) || '\'#000000\''; + var c2 = Blockly.Lisp.valueToCode(block, 'COLOUR2', + Blockly.Lisp.ORDER_NONE) || '\'#000000\''; + var ratio = Blockly.Lisp.valueToCode(block, 'RATIO', + Blockly.Lisp.ORDER_NONE) || 0.5; + + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'colour_blend', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String c1, String c2, num ratio) {', + ' ratio = Math.max(Math.min(ratio, 1), 0);', + ' int r1 = int.parse(\'0x${c1.substring(1, 3)}\');', + ' int g1 = int.parse(\'0x${c1.substring(3, 5)}\');', + ' int b1 = int.parse(\'0x${c1.substring(5, 7)}\');', + ' int r2 = int.parse(\'0x${c2.substring(1, 3)}\');', + ' int g2 = int.parse(\'0x${c2.substring(3, 5)}\');', + ' int b2 = int.parse(\'0x${c2.substring(5, 7)}\');', + ' num rn = (r1 * (1 - ratio) + r2 * ratio).round();', + ' String rs = rn.toInt().toRadixString(16);', + ' num gn = (g1 * (1 - ratio) + g2 * ratio).round();', + ' String gs = gn.toInt().toRadixString(16);', + ' num bn = (b1 * (1 - ratio) + b2 * ratio).round();', + ' String bs = bn.toInt().toRadixString(16);', + ' rs = \'0$rs\';', + ' rs = rs.substring(rs.length - 2);', + ' gs = \'0$gs\';', + ' gs = gs.substring(gs.length - 2);', + ' bs = \'0$bs\';', + ' bs = bs.substring(bs.length - 2);', + ' return \'#$rs$gs$bs\';', + '}']); + var code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; diff --git a/generators/lisp/lists.js b/generators/lisp/lists.js new file mode 100644 index 00000000000..5d64b8aaf0f --- /dev/null +++ b/generators/lisp/lists.js @@ -0,0 +1,447 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for list blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.lists'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp.addReservedWords('Math'); + +Blockly.Lisp['lists_create_empty'] = function(block) { + // Create an empty list. + return ['[]', Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['lists_create_with'] = function(block) { + // Create a list with any number of elements of any type. + var elements = new Array(block.itemCount_); + for (var i = 0; i < block.itemCount_; i++) { + elements[i] = Blockly.Lisp.valueToCode(block, 'ADD' + i, + Blockly.Lisp.ORDER_NONE) || 'null'; + } + var code = '[' + elements.join(', ') + ']'; + return [code, Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['lists_repeat'] = function(block) { + // Create a list with one element repeated. + var element = Blockly.Lisp.valueToCode(block, 'ITEM', + Blockly.Lisp.ORDER_NONE) || 'null'; + var repeatCount = Blockly.Lisp.valueToCode(block, 'NUM', + Blockly.Lisp.ORDER_NONE) || '0'; + var code = 'new List.filled(' + repeatCount + ', ' + element + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_length'] = function(block) { + // String or array length. + var list = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '[]'; + return [list + '.length', Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_isEmpty'] = function(block) { + // Is the string null or array empty? + var list = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '[]'; + return [list + '.isEmpty', Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_indexOf'] = function(block) { + // Find an item in the list. + var operator = block.getFieldValue('END') == 'FIRST' ? + 'indexOf' : 'lastIndexOf'; + var item = Blockly.Lisp.valueToCode(block, 'FIND', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var list = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '[]'; + var code = list + '.' + operator + '(' + item + ')'; + if (block.workspace.options.oneBasedIndex) { + return [code + ' + 1', Blockly.Lisp.ORDER_ADDITIVE]; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_getIndex'] = function(block) { + // Get element at index. + // Note: Until January 2013 this block did not have MODE or WHERE inputs. + var mode = block.getFieldValue('MODE') || 'GET'; + var where = block.getFieldValue('WHERE') || 'FROM_START'; + var listOrder = (where == 'RANDOM' || where == 'FROM_END') ? + Blockly.Lisp.ORDER_NONE : Blockly.Lisp.ORDER_UNARY_POSTFIX; + var list = Blockly.Lisp.valueToCode(block, 'VALUE', listOrder) || '[]'; + // Cache non-trivial values to variables to prevent repeated look-ups. + // Closure, which accesses and modifies 'list'. + function cacheList() { + var listVar = Blockly.Lisp.variableDB_.getDistinctName( + 'tmp_list', Blockly.VARIABLE_CATEGORY_NAME); + var code = 'List ' + listVar + ' = ' + list + ';\n'; + list = listVar; + return code; + } + // If `list` would be evaluated more than once (which is the case for + // RANDOM REMOVE and FROM_END) and is non-trivial, make sure to access it + // only once. + if (((where == 'RANDOM' && mode == 'REMOVE') || where == 'FROM_END') && + !list.match(/^\w+$/)) { + // `list` is an expression, so we may not evaluate it more than once. + if (where == 'RANDOM') { + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + // We can use multiple statements. + var code = cacheList(); + var xVar = Blockly.Lisp.variableDB_.getDistinctName( + 'tmp_x', Blockly.VARIABLE_CATEGORY_NAME); + code += 'int ' + xVar + ' = new Math.Random().nextInt(' + list + + '.length);\n'; + code += list + '.removeAt(' + xVar + ');\n'; + return code; + } else { // where == 'FROM_END' + if (mode == 'REMOVE') { + // We can use multiple statements. + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1, false, + Blockly.Lisp.ORDER_ADDITIVE); + var code = cacheList(); + code += list + '.removeAt(' + list + '.length' + ' - ' + at + ');\n'; + return code; + + } else if (mode == 'GET') { + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1); + // We need to create a procedure to avoid reevaluating values. + var functionName = Blockly.Lisp.provideFunction_( + 'lists_get_from_end', + ['dynamic ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List my_list, num x) {', + ' x = my_list.length - x;', + ' return my_list[x];', + '}']); + var code = functionName + '(' + list + ', ' + at + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE') { + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1); + // We need to create a procedure to avoid reevaluating values. + var functionName = Blockly.Lisp.provideFunction_( + 'lists_remove_from_end', + ['dynamic ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List my_list, num x) {', + ' x = my_list.length - x;', + ' return my_list.removeAt(x);', + '}']); + var code = functionName + '(' + list + ', ' + at + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + } + } else { + // Either `list` is a simple variable, or we only need to refer to `list` + // once. + switch (where) { + case 'FIRST': + if (mode == 'GET') { + var code = list + '.first'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE') { + var code = list + '.removeAt(0)'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'REMOVE') { + return list + '.removeAt(0);\n'; + } + break; + case 'LAST': + if (mode == 'GET') { + var code = list + '.last'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE') { + var code = list + '.removeLast()'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'REMOVE') { + return list + '.removeLast();\n'; + } + break; + case 'FROM_START': + var at = Blockly.Lisp.getAdjusted(block, 'AT'); + if (mode == 'GET') { + var code = list + '[' + at + ']'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE') { + var code = list + '.removeAt(' + at + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'REMOVE') { + return list + '.removeAt(' + at + ');\n'; + } + break; + case 'FROM_END': + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1, false, + Blockly.Lisp.ORDER_ADDITIVE); + if (mode == 'GET') { + var code = list + '[' + list + '.length - ' + at + ']'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE' || mode == 'REMOVE') { + var code = list + '.removeAt(' + list + '.length - ' + at + ')'; + if (mode == 'GET_REMOVE') { + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'REMOVE') { + return code + ';\n'; + } + } + break; + case 'RANDOM': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + if (mode == 'REMOVE') { + // We can use multiple statements. + var xVar = Blockly.Lisp.variableDB_.getDistinctName( + 'tmp_x', Blockly.VARIABLE_CATEGORY_NAME); + var code = 'int ' + xVar + ' = new Math.Random().nextInt(' + list + + '.length);\n'; + code += list + '.removeAt(' + xVar + ');\n'; + return code; + } else if (mode == 'GET') { + var functionName = Blockly.Lisp.provideFunction_( + 'lists_get_random_item', + ['dynamic ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List my_list) {', + ' int x = new Math.Random().nextInt(my_list.length);', + ' return my_list[x];', + '}']); + var code = functionName + '(' + list + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } else if (mode == 'GET_REMOVE') { + var functionName = Blockly.Lisp.provideFunction_( + 'lists_remove_random_item', + ['dynamic ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List my_list) {', + ' int x = new Math.Random().nextInt(my_list.length);', + ' return my_list.removeAt(x);', + '}']); + var code = functionName + '(' + list + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + break; + } + } + throw Error('Unhandled combination (lists_getIndex).'); +}; + +Blockly.Lisp['lists_setIndex'] = function(block) { + // Set element at index. + // Note: Until February 2013 this block did not have MODE or WHERE inputs. + var mode = block.getFieldValue('MODE') || 'GET'; + var where = block.getFieldValue('WHERE') || 'FROM_START'; + var list = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '[]'; + var value = Blockly.Lisp.valueToCode(block, 'TO', + Blockly.Lisp.ORDER_ASSIGNMENT) || 'null'; + // Cache non-trivial values to variables to prevent repeated look-ups. + // Closure, which accesses and modifies 'list'. + function cacheList() { + if (list.match(/^\w+$/)) { + return ''; + } + var listVar = Blockly.Lisp.variableDB_.getDistinctName( + 'tmp_list', Blockly.VARIABLE_CATEGORY_NAME); + var code = 'List ' + listVar + ' = ' + list + ';\n'; + list = listVar; + return code; + } + switch (where) { + case 'FIRST': + if (mode == 'SET') { + return list + '[0] = ' + value + ';\n'; + } else if (mode == 'INSERT') { + return list + '.insert(0, ' + value + ');\n'; + } + break; + case 'LAST': + if (mode == 'SET') { + var code = cacheList(); + code += list + '[' + list + '.length - 1] = ' + value + ';\n'; + return code; + } else if (mode == 'INSERT') { + return list + '.add(' + value + ');\n'; + } + break; + case 'FROM_START': + var at = Blockly.Lisp.getAdjusted(block, 'AT'); + if (mode == 'SET') { + return list + '[' + at + '] = ' + value + ';\n'; + } else if (mode == 'INSERT') { + return list + '.insert(' + at + ', ' + value + ');\n'; + } + break; + case 'FROM_END': + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1, false, + Blockly.Lisp.ORDER_ADDITIVE); + var code = cacheList(); + if (mode == 'SET') { + code += list + '[' + list + '.length - ' + at + '] = ' + value + + ';\n'; + return code; + } else if (mode == 'INSERT') { + code += list + '.insert(' + list + '.length - ' + at + ', ' + + value + ');\n'; + return code; + } + break; + case 'RANDOM': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var code = cacheList(); + var xVar = Blockly.Lisp.variableDB_.getDistinctName( + 'tmp_x', Blockly.VARIABLE_CATEGORY_NAME); + code += 'int ' + xVar + + ' = new Math.Random().nextInt(' + list + '.length);\n'; + if (mode == 'SET') { + code += list + '[' + xVar + '] = ' + value + ';\n'; + return code; + } else if (mode == 'INSERT') { + code += list + '.insert(' + xVar + ', ' + value + ');\n'; + return code; + } + break; + } + throw Error('Unhandled combination (lists_setIndex).'); +}; + +Blockly.Lisp['lists_getSublist'] = function(block) { + // Get sublist. + var list = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '[]'; + var where1 = block.getFieldValue('WHERE1'); + var where2 = block.getFieldValue('WHERE2'); + if (list.match(/^\w+$/) || (where1 != 'FROM_END' && where2 == 'FROM_START')) { + // If the list is a is a variable or doesn't require a call for length, + // don't generate a helper function. + switch (where1) { + case 'FROM_START': + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1'); + break; + case 'FROM_END': + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1', 1, false, + Blockly.Lisp.ORDER_ADDITIVE); + at1 = list + '.length - ' + at1; + break; + case 'FIRST': + var at1 = '0'; + break; + default: + throw Error('Unhandled option (lists_getSublist).'); + } + switch (where2) { + case 'FROM_START': + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2', 1); + break; + case 'FROM_END': + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2', 0, false, + Blockly.Lisp.ORDER_ADDITIVE); + at2 = list + '.length - ' + at2; + break; + case 'LAST': + // There is no second index if LAST option is chosen. + break; + default: + throw Error('Unhandled option (lists_getSublist).'); + } + if (where2 == 'LAST') { + var code = list + '.sublist(' + at1 + ')'; + } else { + var code = list + '.sublist(' + at1 + ', ' + at2 + ')'; + } + } else { + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1'); + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2'); + var functionName = Blockly.Lisp.provideFunction_( + 'lists_get_sublist', + ['List ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List list, String where1, num at1, String where2, num at2) {', + ' int getAt(String where, num at) {', + ' if (where == \'FROM_END\') {', + ' at = list.length - 1 - at;', + ' } else if (where == \'FIRST\') {', + ' at = 0;', + ' } else if (where == \'LAST\') {', + ' at = list.length - 1;', + ' } else if (where != \'FROM_START\') {', + ' throw \'Unhandled option (lists_getSublist).\';', + ' }', + ' return at;', + ' }', + ' at1 = getAt(where1, at1);', + ' at2 = getAt(where2, at2) + 1;', + ' return list.sublist(at1, at2);', + '}']); + var code = functionName + '(' + list + ', \'' + + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_sort'] = function(block) { + // Block for sorting a list. + var list = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_NONE) || '[]'; + var direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; + var type = block.getFieldValue('TYPE'); + var sortFunctionName = Blockly.Lisp.provideFunction_( + 'lists_sort', + ['List ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List list, String type, int direction) {', + ' var compareFuncs = {', + ' "NUMERIC": (a, b) => (direction * a.compareTo(b)).toInt(),', + ' "TEXT": (a, b) => direction * ' + + 'a.toString().compareTo(b.toString()),', + ' "IGNORE_CASE": ', + ' (a, b) => direction * ', + ' a.toString().toLowerCase().compareTo(b.toString().toLowerCase())', + ' };', + ' list = new List.from(list);', // Clone the list. + ' var compare = compareFuncs[type];', + ' list.sort(compare);', + ' return list;', + '}']); + return [sortFunctionName + '(' + list + ', ' + + '"' + type + '", ' + direction + ')', + Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_split'] = function(block) { + // Block for splitting text into a list, or joining a list into text. + var input = Blockly.Lisp.valueToCode(block, 'INPUT', + Blockly.Lisp.ORDER_UNARY_POSTFIX); + var delimiter = Blockly.Lisp.valueToCode(block, 'DELIM', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var mode = block.getFieldValue('MODE'); + if (mode == 'SPLIT') { + if (!input) { + input = '\'\''; + } + var functionName = 'split'; + } else if (mode == 'JOIN') { + if (!input) { + input = '[]'; + } + var functionName = 'join'; + } else { + throw Error('Unknown mode: ' + mode); + } + var code = input + '.' + functionName + '(' + delimiter + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['lists_reverse'] = function(block) { + // Block for reversing a list. + var list = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_NONE) || '[]'; + // XXX What should the operator precedence be for a `new`? + var code = 'new List.from(' + list + '.reversed)'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; diff --git a/generators/lisp/logic.js b/generators/lisp/logic.js new file mode 100644 index 00000000000..45d281374a4 --- /dev/null +++ b/generators/lisp/logic.js @@ -0,0 +1,127 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for logic blocks. + * @author q.neutron@gmail.com (Quynh Neutron) + */ +'use strict'; + +goog.provide('Blockly.Lisp.logic'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp['controls_if'] = function(block) { + // If/elseif/else condition. + var n = 0; + var code = '', branchCode, conditionCode; + if (Blockly.Lisp.STATEMENT_PREFIX) { + // Automatic prefix insertion is switched off for this block. Add manually. + code += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_PREFIX, block); + } + do { + conditionCode = Blockly.Lisp.valueToCode(block, 'IF' + n, + Blockly.Lisp.ORDER_NONE) || 'false'; + branchCode = Blockly.Lisp.statementToCode(block, 'DO' + n); + if (Blockly.Lisp.STATEMENT_SUFFIX) { + branchCode = Blockly.Lisp.prefixLines( + Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_SUFFIX, block), + Blockly.Lisp.INDENT) + branchCode; + } + code += (n > 0 ? 'else ' : '') + + 'if (' + conditionCode + ') {\n' + branchCode + '}'; + ++n; + } while (block.getInput('IF' + n)); + + if (block.getInput('ELSE') || Blockly.Lisp.STATEMENT_SUFFIX) { + branchCode = Blockly.Lisp.statementToCode(block, 'ELSE'); + if (Blockly.Lisp.STATEMENT_SUFFIX) { + branchCode = Blockly.Lisp.prefixLines( + Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_SUFFIX, block), + Blockly.Lisp.INDENT) + branchCode; + } + code += ' else {\n' + branchCode + '}'; + } + return code + '\n'; +}; + +Blockly.Lisp['controls_ifelse'] = Blockly.Lisp['controls_if']; + +Blockly.Lisp['logic_compare'] = function(block) { + // Comparison operator. + var OPERATORS = { + 'EQ': '==', + 'NEQ': '!=', + 'LT': '<', + 'LTE': '<=', + 'GT': '>', + 'GTE': '>=' + }; + var operator = OPERATORS[block.getFieldValue('OP')]; + var order = (operator == '==' || operator == '!=') ? + Blockly.Lisp.ORDER_EQUALITY : Blockly.Lisp.ORDER_RELATIONAL; + var argument0 = Blockly.Lisp.valueToCode(block, 'A', order) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'B', order) || '0'; + var code = argument0 + ' ' + operator + ' ' + argument1; + return [code, order]; +}; + +Blockly.Lisp['logic_operation'] = function(block) { + // Operations 'and', 'or'. + var operator = (block.getFieldValue('OP') == 'AND') ? '&&' : '||'; + var order = (operator == '&&') ? Blockly.Lisp.ORDER_LOGICAL_AND : + Blockly.Lisp.ORDER_LOGICAL_OR; + var argument0 = Blockly.Lisp.valueToCode(block, 'A', order); + var argument1 = Blockly.Lisp.valueToCode(block, 'B', order); + if (!argument0 && !argument1) { + // If there are no arguments, then the return value is false. + argument0 = 'false'; + argument1 = 'false'; + } else { + // Single missing arguments have no effect on the return value. + var defaultArgument = (operator == '&&') ? 'true' : 'false'; + if (!argument0) { + argument0 = defaultArgument; + } + if (!argument1) { + argument1 = defaultArgument; + } + } + var code = argument0 + ' ' + operator + ' ' + argument1; + return [code, order]; +}; + +Blockly.Lisp['logic_negate'] = function(block) { + // Negation. + var order = Blockly.Lisp.ORDER_UNARY_PREFIX; + var argument0 = Blockly.Lisp.valueToCode(block, 'BOOL', order) || 'true'; + var code = '!' + argument0; + return [code, order]; +}; + +Blockly.Lisp['logic_boolean'] = function(block) { + // Boolean values true and false. + var code = (block.getFieldValue('BOOL') == 'TRUE') ? 'true' : 'false'; + return [code, Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['logic_null'] = function(block) { + // Null data type. + return ['null', Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['logic_ternary'] = function(block) { + // Ternary operator. + var value_if = Blockly.Lisp.valueToCode(block, 'IF', + Blockly.Lisp.ORDER_CONDITIONAL) || 'false'; + var value_then = Blockly.Lisp.valueToCode(block, 'THEN', + Blockly.Lisp.ORDER_CONDITIONAL) || 'null'; + var value_else = Blockly.Lisp.valueToCode(block, 'ELSE', + Blockly.Lisp.ORDER_CONDITIONAL) || 'null'; + var code = value_if + ' ? ' + value_then + ' : ' + value_else; + return [code, Blockly.Lisp.ORDER_CONDITIONAL]; +}; diff --git a/generators/lisp/loops.js b/generators/lisp/loops.js new file mode 100644 index 00000000000..4f2ff107746 --- /dev/null +++ b/generators/lisp/loops.js @@ -0,0 +1,169 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for loop blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.loops'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp['controls_repeat_ext'] = function(block) { + // Repeat n times. + if (block.getField('TIMES')) { + // Internal number. + var repeats = String(Number(block.getFieldValue('TIMES'))); + } else { + // External number. + var repeats = Blockly.Lisp.valueToCode(block, 'TIMES', + Blockly.Lisp.ORDER_ASSIGNMENT) || '0'; + } + var branch = Blockly.Lisp.statementToCode(block, 'DO'); + branch = Blockly.Lisp.addLoopTrap(branch, block); + var code = ''; + var loopVar = Blockly.Lisp.variableDB_.getDistinctName( + 'count', Blockly.VARIABLE_CATEGORY_NAME); + var endVar = repeats; + if (!repeats.match(/^\w+$/) && !Blockly.isNumber(repeats)) { + endVar = Blockly.Lisp.variableDB_.getDistinctName( + 'repeat_end', Blockly.VARIABLE_CATEGORY_NAME); + code += 'var ' + endVar + ' = ' + repeats + ';\n'; + } + code += 'for (int ' + loopVar + ' = 0; ' + + loopVar + ' < ' + endVar + '; ' + + loopVar + '++) {\n' + + branch + '}\n'; + return code; +}; + +Blockly.Lisp['controls_repeat'] = Blockly.Lisp['controls_repeat_ext']; + +Blockly.Lisp['controls_whileUntil'] = function(block) { + // Do while/until loop. + var until = block.getFieldValue('MODE') == 'UNTIL'; + var argument0 = Blockly.Lisp.valueToCode(block, 'BOOL', + until ? Blockly.Lisp.ORDER_UNARY_PREFIX : + Blockly.Lisp.ORDER_NONE) || 'false'; + var branch = Blockly.Lisp.statementToCode(block, 'DO'); + branch = Blockly.Lisp.addLoopTrap(branch, block); + if (until) { + argument0 = '!' + argument0; + } + return 'while (' + argument0 + ') {\n' + branch + '}\n'; +}; + +Blockly.Lisp['controls_for'] = function(block) { + // For loop. + var variable0 = Blockly.Lisp.variableDB_.getName( + block.getFieldValue('VAR'), Blockly.VARIABLE_CATEGORY_NAME); + var argument0 = Blockly.Lisp.valueToCode(block, 'FROM', + Blockly.Lisp.ORDER_ASSIGNMENT) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'TO', + Blockly.Lisp.ORDER_ASSIGNMENT) || '0'; + var increment = Blockly.Lisp.valueToCode(block, 'BY', + Blockly.Lisp.ORDER_ASSIGNMENT) || '1'; + var branch = Blockly.Lisp.statementToCode(block, 'DO'); + branch = Blockly.Lisp.addLoopTrap(branch, block); + var code; + if (Blockly.isNumber(argument0) && Blockly.isNumber(argument1) && + Blockly.isNumber(increment)) { + // All arguments are simple numbers. + var up = Number(argument0) <= Number(argument1); + code = 'for (' + variable0 + ' = ' + argument0 + '; ' + + variable0 + (up ? ' <= ' : ' >= ') + argument1 + '; ' + + variable0; + var step = Math.abs(Number(increment)); + if (step == 1) { + code += up ? '++' : '--'; + } else { + code += (up ? ' += ' : ' -= ') + step; + } + code += ') {\n' + branch + '}\n'; + } else { + code = ''; + // Cache non-trivial values to variables to prevent repeated look-ups. + var startVar = argument0; + if (!argument0.match(/^\w+$/) && !Blockly.isNumber(argument0)) { + startVar = Blockly.Lisp.variableDB_.getDistinctName( + variable0 + '_start', Blockly.VARIABLE_CATEGORY_NAME); + code += 'var ' + startVar + ' = ' + argument0 + ';\n'; + } + var endVar = argument1; + if (!argument1.match(/^\w+$/) && !Blockly.isNumber(argument1)) { + endVar = Blockly.Lisp.variableDB_.getDistinctName( + variable0 + '_end', Blockly.VARIABLE_CATEGORY_NAME); + code += 'var ' + endVar + ' = ' + argument1 + ';\n'; + } + // Determine loop direction at start, in case one of the bounds + // changes during loop execution. + var incVar = Blockly.Lisp.variableDB_.getDistinctName( + variable0 + '_inc', Blockly.VARIABLE_CATEGORY_NAME); + code += 'num ' + incVar + ' = '; + if (Blockly.isNumber(increment)) { + code += Math.abs(increment) + ';\n'; + } else { + code += '(' + increment + ').abs();\n'; + } + code += 'if (' + startVar + ' > ' + endVar + ') {\n'; + code += Blockly.Lisp.INDENT + incVar + ' = -' + incVar + ';\n'; + code += '}\n'; + code += 'for (' + variable0 + ' = ' + startVar + '; ' + + incVar + ' >= 0 ? ' + + variable0 + ' <= ' + endVar + ' : ' + + variable0 + ' >= ' + endVar + '; ' + + variable0 + ' += ' + incVar + ') {\n' + + branch + '}\n'; + } + return code; +}; + +Blockly.Lisp['controls_forEach'] = function(block) { + // For each loop. + var variable0 = Blockly.Lisp.variableDB_.getName( + block.getFieldValue('VAR'), Blockly.VARIABLE_CATEGORY_NAME); + var argument0 = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_ASSIGNMENT) || '[]'; + var branch = Blockly.Lisp.statementToCode(block, 'DO'); + branch = Blockly.Lisp.addLoopTrap(branch, block); + var code = 'for (var ' + variable0 + ' in ' + argument0 + ') {\n' + + branch + '}\n'; + return code; +}; + +Blockly.Lisp['controls_flow_statements'] = function(block) { + // Flow statements: continue, break. + var xfix = ''; + if (Blockly.Lisp.STATEMENT_PREFIX) { + // Automatic prefix insertion is switched off for this block. Add manually. + xfix += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_PREFIX, block); + } + if (Blockly.Lisp.STATEMENT_SUFFIX) { + // Inject any statement suffix here since the regular one at the end + // will not get executed if the break/continue is triggered. + xfix += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_SUFFIX, block); + } + if (Blockly.Lisp.STATEMENT_PREFIX) { + var loop = Blockly.Constants.Loops + .CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block); + if (loop && !loop.suppressPrefixSuffix) { + // Inject loop's statement prefix here since the regular one at the end + // of the loop will not get executed if 'continue' is triggered. + // In the case of 'break', a prefix is needed due to the loop's suffix. + xfix += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_PREFIX, loop); + } + } + switch (block.getFieldValue('FLOW')) { + case 'BREAK': + return xfix + 'break;\n'; + case 'CONTINUE': + return xfix + 'continue;\n'; + } + throw Error('Unknown flow statement.'); +}; diff --git a/generators/lisp/math.js b/generators/lisp/math.js new file mode 100644 index 00000000000..cf295d07bb7 --- /dev/null +++ b/generators/lisp/math.js @@ -0,0 +1,485 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for math blocks. + * @author q.neutron@gmail.com (Quynh Neutron) + */ +'use strict'; + +goog.provide('Blockly.Lisp.math'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp.addReservedWords('Math'); + +Blockly.Lisp['math_number'] = function(block) { + // Numeric value. + var code = Number(block.getFieldValue('NUM')); + var order; + if (code == Infinity) { + code = 'double.infinity'; + order = Blockly.Lisp.ORDER_UNARY_POSTFIX; + } else if (code == -Infinity) { + code = '-double.infinity'; + order = Blockly.Lisp.ORDER_UNARY_PREFIX; + } else { + // -4.abs() returns -4 in Lisp due to strange order of operation choices. + // -4 is actually an operator and a number. Reflect this in the order. + order = code < 0 ? + Blockly.Lisp.ORDER_UNARY_PREFIX : Blockly.Lisp.ORDER_ATOMIC; + } + return [code, order]; +}; + +Blockly.Lisp['math_arithmetic'] = function(block) { + // Basic arithmetic operators, and power. + var OPERATORS = { + 'ADD': [' + ', Blockly.Lisp.ORDER_ADDITIVE], + 'MINUS': [' - ', Blockly.Lisp.ORDER_ADDITIVE], + 'MULTIPLY': [' * ', Blockly.Lisp.ORDER_MULTIPLICATIVE], + 'DIVIDE': [' / ', Blockly.Lisp.ORDER_MULTIPLICATIVE], + 'POWER': [null, Blockly.Lisp.ORDER_NONE] // Handle power separately. + }; + var tuple = OPERATORS[block.getFieldValue('OP')]; + var operator = tuple[0]; + var order = tuple[1]; + var argument0 = Blockly.Lisp.valueToCode(block, 'A', order) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'B', order) || '0'; + var code; + // Power in Lisp requires a special case since it has no operator. + if (!operator) { + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + code = 'Math.pow(' + argument0 + ', ' + argument1 + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + code = argument0 + operator + argument1; + return [code, order]; +}; + +Blockly.Lisp['math_single'] = function(block) { + // Math operators with single operand. + var operator = block.getFieldValue('OP'); + var code; + var arg; + if (operator == 'NEG') { + // Negation is a special case given its different operator precedence. + arg = Blockly.Lisp.valueToCode(block, 'NUM', + Blockly.Lisp.ORDER_UNARY_PREFIX) || '0'; + if (arg[0] == '-') { + // --3 is not legal in Lisp. + arg = ' ' + arg; + } + code = '-' + arg; + return [code, Blockly.Lisp.ORDER_UNARY_PREFIX]; + } + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + if (operator == 'ABS' || operator.substring(0, 5) == 'ROUND') { + arg = Blockly.Lisp.valueToCode(block, 'NUM', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '0'; + } else if (operator == 'SIN' || operator == 'COS' || operator == 'TAN') { + arg = Blockly.Lisp.valueToCode(block, 'NUM', + Blockly.Lisp.ORDER_MULTIPLICATIVE) || '0'; + } else { + arg = Blockly.Lisp.valueToCode(block, 'NUM', + Blockly.Lisp.ORDER_NONE) || '0'; + } + // First, handle cases which generate values that don't need parentheses + // wrapping the code. + switch (operator) { + case 'ABS': + code = arg + '.abs()'; + break; + case 'ROOT': + code = 'Math.sqrt(' + arg + ')'; + break; + case 'LN': + code = 'Math.log(' + arg + ')'; + break; + case 'EXP': + code = 'Math.exp(' + arg + ')'; + break; + case 'POW10': + code = 'Math.pow(10,' + arg + ')'; + break; + case 'ROUND': + code = arg + '.round()'; + break; + case 'ROUNDUP': + code = arg + '.ceil()'; + break; + case 'ROUNDDOWN': + code = arg + '.floor()'; + break; + case 'SIN': + code = 'Math.sin(' + arg + ' / 180 * Math.pi)'; + break; + case 'COS': + code = 'Math.cos(' + arg + ' / 180 * Math.pi)'; + break; + case 'TAN': + code = 'Math.tan(' + arg + ' / 180 * Math.pi)'; + break; + } + if (code) { + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + // Second, handle cases which generate values that may need parentheses + // wrapping the code. + switch (operator) { + case 'LOG10': + code = 'Math.log(' + arg + ') / Math.log(10)'; + break; + case 'ASIN': + code = 'Math.asin(' + arg + ') / Math.pi * 180'; + break; + case 'ACOS': + code = 'Math.acos(' + arg + ') / Math.pi * 180'; + break; + case 'ATAN': + code = 'Math.atan(' + arg + ') / Math.pi * 180'; + break; + default: + throw Error('Unknown math operator: ' + operator); + } + return [code, Blockly.Lisp.ORDER_MULTIPLICATIVE]; +}; + +Blockly.Lisp['math_constant'] = function(block) { + // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. + var CONSTANTS = { + 'PI': ['Math.pi', Blockly.Lisp.ORDER_UNARY_POSTFIX], + 'E': ['Math.e', Blockly.Lisp.ORDER_UNARY_POSTFIX], + 'GOLDEN_RATIO': + ['(1 + Math.sqrt(5)) / 2', Blockly.Lisp.ORDER_MULTIPLICATIVE], + 'SQRT2': ['Math.sqrt2', Blockly.Lisp.ORDER_UNARY_POSTFIX], + 'SQRT1_2': ['Math.sqrt1_2', Blockly.Lisp.ORDER_UNARY_POSTFIX], + 'INFINITY': ['double.infinity', Blockly.Lisp.ORDER_ATOMIC] + }; + var constant = block.getFieldValue('CONSTANT'); + if (constant != 'INFINITY') { + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + } + return CONSTANTS[constant]; +}; + +Blockly.Lisp['math_number_property'] = function(block) { + // Check if a number is even, odd, prime, whole, positive, or negative + // or if it is divisible by certain number. Returns true or false. + var number_to_check = Blockly.Lisp.valueToCode(block, 'NUMBER_TO_CHECK', + Blockly.Lisp.ORDER_MULTIPLICATIVE); + if (!number_to_check) { + return ['false', Blockly.Lisp.ORDER_ATOMIC]; + } + var dropdown_property = block.getFieldValue('PROPERTY'); + var code; + if (dropdown_property == 'PRIME') { + // Prime is a special case as it is not a one-liner test. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_isPrime', + ['bool ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + '(n) {', + ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods', + ' if (n == 2 || n == 3) {', + ' return true;', + ' }', + ' // False if n is null, negative, is 1, or not whole.', + ' // And false if n is divisible by 2 or 3.', + ' if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 ||' + + ' n % 3 == 0) {', + ' return false;', + ' }', + ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).', + ' for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {', + ' if (n % (x - 1) == 0 || n % (x + 1) == 0) {', + ' return false;', + ' }', + ' }', + ' return true;', + '}']); + code = functionName + '(' + number_to_check + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + switch (dropdown_property) { + case 'EVEN': + code = number_to_check + ' % 2 == 0'; + break; + case 'ODD': + code = number_to_check + ' % 2 == 1'; + break; + case 'WHOLE': + code = number_to_check + ' % 1 == 0'; + break; + case 'POSITIVE': + code = number_to_check + ' > 0'; + break; + case 'NEGATIVE': + code = number_to_check + ' < 0'; + break; + case 'DIVISIBLE_BY': + var divisor = Blockly.Lisp.valueToCode(block, 'DIVISOR', + Blockly.Lisp.ORDER_MULTIPLICATIVE); + if (!divisor) { + return ['false', Blockly.Lisp.ORDER_ATOMIC]; + } + code = number_to_check + ' % ' + divisor + ' == 0'; + break; + } + return [code, Blockly.Lisp.ORDER_EQUALITY]; +}; + +Blockly.Lisp['math_change'] = function(block) { + // Add to a variable in place. + var argument0 = Blockly.Lisp.valueToCode(block, 'DELTA', + Blockly.Lisp.ORDER_ADDITIVE) || '0'; + var varName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('VAR'), + Blockly.VARIABLE_CATEGORY_NAME); + return varName + ' = (' + varName + ' is num ? ' + varName + ' : 0) + ' + + argument0 + ';\n'; +}; + +// Rounding functions have a single operand. +Blockly.Lisp['math_round'] = Blockly.Lisp['math_single']; +// Trigonometry functions have a single operand. +Blockly.Lisp['math_trig'] = Blockly.Lisp['math_single']; + +Blockly.Lisp['math_on_list'] = function(block) { + // Math functions for lists. + var func = block.getFieldValue('OP'); + var list = Blockly.Lisp.valueToCode(block, 'LIST', + Blockly.Lisp.ORDER_NONE) || '[]'; + var code; + switch (func) { + case 'SUM': + var functionName = Blockly.Lisp.provideFunction_( + 'math_sum', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' num sumVal = 0;', + ' myList.forEach((num entry) {sumVal += entry;});', + ' return sumVal;', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'MIN': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_min', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' if (myList.isEmpty) return null;', + ' num minVal = myList[0];', + ' myList.forEach((num entry) ' + + '{minVal = Math.min(minVal, entry);});', + ' return minVal;', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'MAX': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_max', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' if (myList.isEmpty) return null;', + ' num maxVal = myList[0];', + ' myList.forEach((num entry) ' + + '{maxVal = Math.max(maxVal, entry);});', + ' return maxVal;', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'AVERAGE': + // This operation exclude null and values that are not int or float: + // math_mean([null,null,"aString",1,9]) == 5.0. + var functionName = Blockly.Lisp.provideFunction_( + 'math_mean', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' // First filter list for numbers only.', + ' List localList = new List.from(myList);', + ' localList.removeWhere((a) => a is! num);', + ' if (localList.isEmpty) return null;', + ' num sumVal = 0;', + ' localList.forEach((var entry) {sumVal += entry;});', + ' return sumVal / localList.length;', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'MEDIAN': + var functionName = Blockly.Lisp.provideFunction_( + 'math_median', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' // First filter list for numbers only, then sort, ' + + 'then return middle value', + ' // or the average of two middle values if list has an ' + + 'even number of elements.', + ' List localList = new List.from(myList);', + ' localList.removeWhere((a) => a is! num);', + ' if (localList.isEmpty) return null;', + ' localList.sort((a, b) => (a - b));', + ' int index = localList.length ~/ 2;', + ' if (localList.length % 2 == 1) {', + ' return localList[index];', + ' } else {', + ' return (localList[index - 1] + localList[index]) / 2;', + ' }', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'MODE': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + // As a list of numbers can contain more than one mode, + // the returned result is provided as an array. + // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. + var functionName = Blockly.Lisp.provideFunction_( + 'math_modes', + ['List ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List values) {', + ' List modes = [];', + ' List counts = [];', + ' int maxCount = 0;', + ' for (int i = 0; i < values.length; i++) {', + ' var value = values[i];', + ' bool found = false;', + ' int thisCount;', + ' for (int j = 0; j < counts.length; j++) {', + ' if (counts[j][0] == value) {', + ' thisCount = ++counts[j][1];', + ' found = true;', + ' break;', + ' }', + ' }', + ' if (!found) {', + ' counts.add([value, 1]);', + ' thisCount = 1;', + ' }', + ' maxCount = Math.max(thisCount, maxCount);', + ' }', + ' for (int j = 0; j < counts.length; j++) {', + ' if (counts[j][1] == maxCount) {', + ' modes.add(counts[j][0]);', + ' }', + ' }', + ' return modes;', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'STD_DEV': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_standard_deviation', + ['num ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' // First filter list for numbers only.', + ' List numbers = new List.from(myList);', + ' numbers.removeWhere((a) => a is! num);', + ' if (numbers.isEmpty) return null;', + ' num n = numbers.length;', + ' num sum = 0;', + ' numbers.forEach((x) => sum += x);', + ' num mean = sum / n;', + ' num sumSquare = 0;', + ' numbers.forEach((x) => sumSquare += ' + + 'Math.pow(x - mean, 2));', + ' return Math.sqrt(sumSquare / n);', + '}']); + code = functionName + '(' + list + ')'; + break; + case 'RANDOM': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_random_item', + ['dynamic ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(List myList) {', + ' int x = new Math.Random().nextInt(myList.length);', + ' return myList[x];', + '}']); + code = functionName + '(' + list + ')'; + break; + default: + throw Error('Unknown operator: ' + func); + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['math_modulo'] = function(block) { + // Remainder computation. + var argument0 = Blockly.Lisp.valueToCode(block, 'DIVIDEND', + Blockly.Lisp.ORDER_MULTIPLICATIVE) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'DIVISOR', + Blockly.Lisp.ORDER_MULTIPLICATIVE) || '0'; + var code = argument0 + ' % ' + argument1; + return [code, Blockly.Lisp.ORDER_MULTIPLICATIVE]; +}; + +Blockly.Lisp['math_constrain'] = function(block) { + // Constrain a number between two limits. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var argument0 = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_NONE) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'LOW', + Blockly.Lisp.ORDER_NONE) || '0'; + var argument2 = Blockly.Lisp.valueToCode(block, 'HIGH', + Blockly.Lisp.ORDER_NONE) || 'double.infinity'; + var code = 'Math.min(Math.max(' + argument0 + ', ' + argument1 + '), ' + + argument2 + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['math_random_int'] = function(block) { + // Random integer between [X] and [Y]. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var argument0 = Blockly.Lisp.valueToCode(block, 'FROM', + Blockly.Lisp.ORDER_NONE) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'TO', + Blockly.Lisp.ORDER_NONE) || '0'; + var functionName = Blockly.Lisp.provideFunction_( + 'math_random_int', + ['int ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + '(num a, num b) {', + ' if (a > b) {', + ' // Swap a and b to ensure a is smaller.', + ' num c = a;', + ' a = b;', + ' b = c;', + ' }', + ' return new Math.Random().nextInt(b - a + 1) + a;', + '}']); + var code = functionName + '(' + argument0 + ', ' + argument1 + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['math_random_float'] = function(block) { + // Random fraction between 0 and 1. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + return ['new Math.Random().nextDouble()', Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['math_atan2'] = function(block) { + // Arctangent of point (X, Y) in degrees from -180 to 180. + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var argument0 = Blockly.Lisp.valueToCode(block, 'X', + Blockly.Lisp.ORDER_NONE) || '0'; + var argument1 = Blockly.Lisp.valueToCode(block, 'Y', + Blockly.Lisp.ORDER_NONE) || '0'; + return ['Math.atan2(' + argument1 + ', ' + argument0 + ') / Math.pi * 180', + Blockly.Lisp.ORDER_MULTIPLICATIVE]; +}; diff --git a/generators/lisp/procedures.js b/generators/lisp/procedures.js new file mode 100644 index 00000000000..2e97a45d1d1 --- /dev/null +++ b/generators/lisp/procedures.js @@ -0,0 +1,111 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for procedure blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.procedures'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp['procedures_defreturn'] = function(block) { + // Define a procedure with a return value. + var funcName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('NAME'), + Blockly.PROCEDURE_CATEGORY_NAME); + var xfix1 = ''; + if (Blockly.Lisp.STATEMENT_PREFIX) { + xfix1 += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_PREFIX, block); + } + if (Blockly.Lisp.STATEMENT_SUFFIX) { + xfix1 += Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_SUFFIX, block); + } + if (xfix1) { + xfix1 = Blockly.Lisp.prefixLines(xfix1, Blockly.Lisp.INDENT); + } + var loopTrap = ''; + if (Blockly.Lisp.INFINITE_LOOP_TRAP) { + loopTrap = Blockly.Lisp.prefixLines( + Blockly.Lisp.injectId(Blockly.Lisp.INFINITE_LOOP_TRAP, block), + Blockly.Lisp.INDENT); + } + var branch = Blockly.Lisp.statementToCode(block, 'STACK'); + var returnValue = Blockly.Lisp.valueToCode(block, 'RETURN', + Blockly.Lisp.ORDER_NONE) || ''; + var xfix2 = ''; + if (branch && returnValue) { + // After executing the function body, revisit this block for the return. + xfix2 = xfix1; + } + if (returnValue) { + returnValue = Blockly.Lisp.INDENT + 'return ' + returnValue + ';\n'; + } + var returnType = returnValue ? 'dynamic' : 'void'; + var args = []; + var variables = block.getVars(); + for (var i = 0; i < variables.length; i++) { + args[i] = Blockly.Lisp.variableDB_.getName(variables[i], + Blockly.VARIABLE_CATEGORY_NAME); + } + var code = returnType + ' ' + funcName + '(' + args.join(', ') + ') {\n' + + xfix1 + loopTrap + branch + xfix2 + returnValue + '}'; + code = Blockly.Lisp.scrub_(block, code); + // Add % so as not to collide with helper functions in definitions list. + Blockly.Lisp.definitions_['%' + funcName] = code; + return null; +}; + +// Defining a procedure without a return value uses the same generator as +// a procedure with a return value. +Blockly.Lisp['procedures_defnoreturn'] = Blockly.Lisp['procedures_defreturn']; + +Blockly.Lisp['procedures_callreturn'] = function(block) { + // Call a procedure with a return value. + var funcName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('NAME'), + Blockly.PROCEDURE_CATEGORY_NAME); + var args = []; + var variables = block.getVars(); + for (var i = 0; i < variables.length; i++) { + args[i] = Blockly.Lisp.valueToCode(block, 'ARG' + i, + Blockly.Lisp.ORDER_NONE) || 'null'; + } + var code = funcName + '(' + args.join(', ') + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['procedures_callnoreturn'] = function(block) { + // Call a procedure with no return value. + // Generated code is for a function call as a statement is the same as a + // function call as a value, with the addition of line ending. + var tuple = Blockly.Lisp['procedures_callreturn'](block); + return tuple[0] + ';\n'; +}; + +Blockly.Lisp['procedures_ifreturn'] = function(block) { + // Conditionally return value from a procedure. + var condition = Blockly.Lisp.valueToCode(block, 'CONDITION', + Blockly.Lisp.ORDER_NONE) || 'false'; + var code = 'if (' + condition + ') {\n'; + if (Blockly.Lisp.STATEMENT_SUFFIX) { + // Inject any statement suffix here since the regular one at the end + // will not get executed if the return is triggered. + code += Blockly.Lisp.prefixLines( + Blockly.Lisp.injectId(Blockly.Lisp.STATEMENT_SUFFIX, block), + Blockly.Lisp.INDENT); + } + if (block.hasReturnValue_) { + var value = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_NONE) || 'null'; + code += Blockly.Lisp.INDENT + 'return ' + value + ';\n'; + } else { + code += Blockly.Lisp.INDENT + 'return;\n'; + } + code += '}\n'; + return code; +}; diff --git a/generators/lisp/text.js b/generators/lisp/text.js new file mode 100644 index 00000000000..52a81ad9690 --- /dev/null +++ b/generators/lisp/text.js @@ -0,0 +1,343 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for text blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.texts'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp.addReservedWords('Html,Math'); + +Blockly.Lisp['text'] = function(block) { + // Text value. + var code = Blockly.Lisp.quote_(block.getFieldValue('TEXT')); + return [code, Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['text_multiline'] = function(block) { + // Text value. + var code = Blockly.Lisp.multiline_quote_(block.getFieldValue('TEXT')); + var order = code.indexOf('+') != -1 ? Blockly.Lisp.ORDER_ADDITIVE : + Blockly.Lisp.ORDER_ATOMIC; + return [code, order]; +}; + +Blockly.Lisp['text_join'] = function(block) { + // Create a string made up of any number of elements of any type. + switch (block.itemCount_) { + case 0: + return ['\'\'', Blockly.Lisp.ORDER_ATOMIC]; + case 1: + var element = Blockly.Lisp.valueToCode(block, 'ADD0', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + var code = element + '.toString()'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + default: + var elements = new Array(block.itemCount_); + for (var i = 0; i < block.itemCount_; i++) { + elements[i] = Blockly.Lisp.valueToCode(block, 'ADD' + i, + Blockly.Lisp.ORDER_NONE) || '\'\''; + } + var code = '[' + elements.join(',') + '].join()'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } +}; + +Blockly.Lisp['text_append'] = function(block) { + // Append to a variable in place. + var varName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('VAR'), + Blockly.VARIABLE_CATEGORY_NAME); + var value = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_NONE) || '\'\''; + return varName + ' = [' + varName + ', ' + value + '].join();\n'; +}; + +Blockly.Lisp['text_length'] = function(block) { + // String or array length. + var text = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + return [text + '.length', Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_isEmpty'] = function(block) { + // Is the string null or array empty? + var text = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + return [text + '.isEmpty', Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_indexOf'] = function(block) { + // Search the text for a substring. + var operator = block.getFieldValue('END') == 'FIRST' ? + 'indexOf' : 'lastIndexOf'; + var substring = Blockly.Lisp.valueToCode(block, 'FIND', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var text = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + var code = text + '.' + operator + '(' + substring + ')'; + if (block.workspace.options.oneBasedIndex) { + return [code + ' + 1', Blockly.Lisp.ORDER_ADDITIVE]; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_charAt'] = function(block) { + // Get letter at index. + // Note: Until January 2013 this block did not have the WHERE input. + var where = block.getFieldValue('WHERE') || 'FROM_START'; + var textOrder = (where == 'FIRST' || where == 'FROM_START') ? + Blockly.Lisp.ORDER_UNARY_POSTFIX : Blockly.Lisp.ORDER_NONE; + var text = Blockly.Lisp.valueToCode(block, 'VALUE', textOrder) || '\'\''; + switch (where) { + case 'FIRST': + var code = text + '[0]'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + case 'FROM_START': + var at = Blockly.Lisp.getAdjusted(block, 'AT'); + var code = text + '[' + at + ']'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + case 'LAST': + at = 1; + // Fall through. + case 'FROM_END': + var at = Blockly.Lisp.getAdjusted(block, 'AT', 1); + var functionName = Blockly.Lisp.provideFunction_( + 'text_get_from_end', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String text, num x) {', + ' return text[text.length - x];', + '}']); + code = functionName + '(' + text + ', ' + at + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + case 'RANDOM': + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + var functionName = Blockly.Lisp.provideFunction_( + 'text_random_letter', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String text) {', + ' int x = new Math.Random().nextInt(text.length);', + ' return text[x];', + '}']); + code = functionName + '(' + text + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; + } + throw Error('Unhandled option (text_charAt).'); +}; + +Blockly.Lisp['text_getSubstring'] = function(block) { + // Get substring. + var where1 = block.getFieldValue('WHERE1'); + var where2 = block.getFieldValue('WHERE2'); + var requiresLengthCall = (where1 != 'FROM_END' && where2 == 'FROM_START'); + var textOrder = requiresLengthCall ? Blockly.Lisp.ORDER_UNARY_POSTFIX : + Blockly.Lisp.ORDER_NONE; + var text = Blockly.Lisp.valueToCode(block, 'STRING', textOrder) || '\'\''; + if (where1 == 'FIRST' && where2 == 'LAST') { + var code = text; + return [code, Blockly.Lisp.ORDER_NONE]; + } else if (text.match(/^'?\w+'?$/) || requiresLengthCall) { + // If the text is a variable or literal or doesn't require a call for + // length, don't generate a helper function. + switch (where1) { + case 'FROM_START': + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1'); + break; + case 'FROM_END': + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1', 1, false, + Blockly.Lisp.ORDER_ADDITIVE); + at1 = text + '.length - ' + at1; + break; + case 'FIRST': + var at1 = '0'; + break; + default: + throw Error('Unhandled option (text_getSubstring).'); + } + switch (where2) { + case 'FROM_START': + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2', 1); + break; + case 'FROM_END': + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2', 0, false, + Blockly.Lisp.ORDER_ADDITIVE); + at2 = text + '.length - ' + at2; + break; + case 'LAST': + break; + default: + throw Error('Unhandled option (text_getSubstring).'); + } + if (where2 == 'LAST') { + var code = text + '.substring(' + at1 + ')'; + } else { + var code = text + '.substring(' + at1 + ', ' + at2 + ')'; + } + } else { + var at1 = Blockly.Lisp.getAdjusted(block, 'AT1'); + var at2 = Blockly.Lisp.getAdjusted(block, 'AT2'); + var functionName = Blockly.Lisp.provideFunction_( + 'text_get_substring', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String text, String where1, num at1, String where2, num at2) {', + ' int getAt(String where, num at) {', + ' if (where == \'FROM_END\') {', + ' at = text.length - 1 - at;', + ' } else if (where == \'FIRST\') {', + ' at = 0;', + ' } else if (where == \'LAST\') {', + ' at = text.length - 1;', + ' } else if (where != \'FROM_START\') {', + ' throw \'Unhandled option (text_getSubstring).\';', + ' }', + ' return at;', + ' }', + ' at1 = getAt(where1, at1);', + ' at2 = getAt(where2, at2) + 1;', + ' return text.substring(at1, at2);', + '}']); + var code = functionName + '(' + text + ', \'' + + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_changeCase'] = function(block) { + // Change capitalization. + var OPERATORS = { + 'UPPERCASE': '.toUpperCase()', + 'LOWERCASE': '.toLowerCase()', + 'TITLECASE': null + }; + var operator = OPERATORS[block.getFieldValue('CASE')]; + var textOrder = operator ? Blockly.Lisp.ORDER_UNARY_POSTFIX : + Blockly.Lisp.ORDER_NONE; + var text = Blockly.Lisp.valueToCode(block, 'TEXT', textOrder) || '\'\''; + if (operator) { + // Upper and lower case are functions built into Lisp. + var code = text + operator; + } else { + // Title case is not a native Lisp function. Define one. + var functionName = Blockly.Lisp.provideFunction_( + 'text_toTitleCase', + ['String ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String str) {', + ' RegExp exp = new RegExp(r\'\\b\');', + ' List list = str.split(exp);', + ' final title = new StringBuffer();', + ' for (String part in list) {', + ' if (part.length > 0) {', + ' title.write(part[0].toUpperCase());', + ' if (part.length > 0) {', + ' title.write(part.substring(1).toLowerCase());', + ' }', + ' }', + ' }', + ' return title.toString();', + '}']); + var code = functionName + '(' + text + ')'; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_trim'] = function(block) { + // Trim spaces. + var OPERATORS = { + 'LEFT': '.replaceFirst(new RegExp(r\'^\\s+\'), \'\')', + 'RIGHT': '.replaceFirst(new RegExp(r\'\\s+$\'), \'\')', + 'BOTH': '.trim()' + }; + var operator = OPERATORS[block.getFieldValue('MODE')]; + var text = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + return [text + operator, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_print'] = function(block) { + // Print statement. + var msg = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_NONE) || '\'\''; + return 'print(' + msg + ');\n'; +}; + +Blockly.Lisp['text_prompt_ext'] = function(block) { + // Prompt function. + Blockly.Lisp.definitions_['import_lisp_html'] = + 'import \'lisp:html\' as Html;'; + if (block.getField('TEXT')) { + // Internal message. + var msg = Blockly.Lisp.quote_(block.getFieldValue('TEXT')); + } else { + // External message. + var msg = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_NONE) || '\'\''; + } + var code = 'Html.window.prompt(' + msg + ', \'\')'; + var toNumber = block.getFieldValue('TYPE') == 'NUMBER'; + if (toNumber) { + Blockly.Lisp.definitions_['import_lisp_math'] = + 'import \'lisp:math\' as Math;'; + code = 'Math.parseDouble(' + code + ')'; + } + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_prompt'] = Blockly.Lisp['text_prompt_ext']; + +Blockly.Lisp['text_count'] = function(block) { + var text = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var sub = Blockly.Lisp.valueToCode(block, 'SUB', + Blockly.Lisp.ORDER_NONE) || '\'\''; + // Substring count is not a native Lisp function. Define one. + var functionName = Blockly.Lisp.provideFunction_( + 'text_count', + ['int ' + Blockly.Lisp.FUNCTION_NAME_PLACEHOLDER_ + + '(String haystack, String needle) {', + ' if (needle.length == 0) {', + ' return haystack.length + 1;', + ' }', + ' int index = 0;', + ' int count = 0;', + ' while (index != -1) {', + ' index = haystack.indexOf(needle, index);', + ' if (index != -1) {', + ' count++;', + ' index += needle.length;', + ' }', + ' }', + ' return count;', + '}']); + var code = functionName + '(' + text + ', ' + sub + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_replace'] = function(block) { + var text = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + var from = Blockly.Lisp.valueToCode(block, 'FROM', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var to = Blockly.Lisp.valueToCode(block, 'TO', + Blockly.Lisp.ORDER_NONE) || '\'\''; + var code = text + '.replaceAll(' + from + ', ' + to + ')'; + return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX]; +}; + +Blockly.Lisp['text_reverse'] = function(block) { + // There isn't a sensible way to do this in Lisp. See: + // http://stackoverflow.com/a/21613700/3529104 + // Implementing something is possibly better than not implementing anything? + var text = Blockly.Lisp.valueToCode(block, 'TEXT', + Blockly.Lisp.ORDER_UNARY_POSTFIX) || '\'\''; + var code = 'new String.fromCharCodes(' + text + '.runes.toList().reversed)'; + return [code, Blockly.Lisp.ORDER_UNARY_PREFIX]; +}; diff --git a/generators/lisp/variables.js b/generators/lisp/variables.js new file mode 100644 index 00000000000..a129d78e884 --- /dev/null +++ b/generators/lisp/variables.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for variable blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Lisp.variables'); + +goog.require('Blockly.Lisp'); + + +Blockly.Lisp['variables_get'] = function(block) { + // Variable getter. + var code = Blockly.Lisp.variableDB_.getName(block.getFieldValue('VAR'), + Blockly.VARIABLE_CATEGORY_NAME); + return [code, Blockly.Lisp.ORDER_ATOMIC]; +}; + +Blockly.Lisp['variables_set'] = function(block) { + // Variable setter. + var argument0 = Blockly.Lisp.valueToCode(block, 'VALUE', + Blockly.Lisp.ORDER_ASSIGNMENT) || '0'; + var varName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('VAR'), + Blockly.VARIABLE_CATEGORY_NAME); + return varName + ' = ' + argument0 + ';\n'; +}; diff --git a/generators/lisp/variables_dynamic.js b/generators/lisp/variables_dynamic.js new file mode 100644 index 00000000000..ace2f25d6c6 --- /dev/null +++ b/generators/lisp/variables_dynamic.js @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generating Lisp for dynamic variable blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Lisp.variablesDynamic'); + +goog.require('Blockly.Lisp'); +goog.require('Blockly.Lisp.variables'); + + +// Lisp is dynamically typed. +Blockly.Lisp['variables_get_dynamic'] = Blockly.Lisp['variables_get']; +Blockly.Lisp['variables_set_dynamic'] = Blockly.Lisp['variables_set']; From d36c50645f20d6f1ac2451dddd4d59b23c687aab Mon Sep 17 00:00:00 2001 From: lupyuen Date: Fri, 7 May 2021 18:33:07 +0800 Subject: [PATCH 02/48] Add Lisp to code demo --- demos/code/index.html | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/demos/code/index.html b/demos/code/index.html index 9fd858aeffa..b2ffa8e59c8 100644 --- a/demos/code/index.html +++ b/demos/code/index.html @@ -4,16 +4,42 @@ - Blockly Demo: + uLisp Blockly + + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36,6 +62,8 @@

Blockly‏ > ...   + Lisp +   JavaScript   Python @@ -72,6 +100,7 @@

Blockly‏ >
+

   

   

   


From 5751455b6e69246ef4edfedfcc04301aff1d7145 Mon Sep 17 00:00:00 2001
From: lupyuen 
Date: Fri, 7 May 2021 18:56:53 +0800
Subject: [PATCH 03/48] Fixed code generator

---
 demos/code/code.js |   6 +-
 generators/lisp.js | 297 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 301 insertions(+), 2 deletions(-)
 create mode 100644 generators/lisp.js

diff --git a/demos/code/code.js b/demos/code/code.js
index 0c27653252f..2a36d385360 100644
--- a/demos/code/code.js
+++ b/demos/code/code.js
@@ -234,14 +234,14 @@ Code.LANG = Code.getLang();
  * List of tab names.
  * @private
  */
-Code.TABS_ = ['blocks', 'javascript', 'php', 'python', 'dart', 'lua', 'xml'];
+Code.TABS_ = ['blocks', 'lisp', 'javascript', 'php', 'python', 'dart', 'lua', 'xml'];
 
 /**
  * List of tab names with casing, for display in the UI.
  * @private
  */
 Code.TABS_DISPLAY_ = [
-  'Blocks', 'JavaScript', 'PHP', 'Python', 'Dart', 'Lua', 'XML',
+  'Blocks', 'Lisp', 'JavaScript', 'PHP', 'Python', 'Dart', 'Lua', 'XML',
 ];
 
 Code.selected = 'blocks';
@@ -324,6 +324,8 @@ Code.renderContent = function() {
     var xmlText = Blockly.Xml.domToPrettyText(xmlDom);
     xmlTextarea.value = xmlText;
     xmlTextarea.focus();
+  } else if (content.id == 'content_lisp') {
+    Code.attemptCodeGeneration(Blockly.Lisp);
   } else if (content.id == 'content_javascript') {
     Code.attemptCodeGeneration(Blockly.JavaScript);
   } else if (content.id == 'content_python') {
diff --git a/generators/lisp.js b/generators/lisp.js
new file mode 100644
index 00000000000..7d59388bebc
--- /dev/null
+++ b/generators/lisp.js
@@ -0,0 +1,297 @@
+/**
+ * @license
+ * Copyright 2014 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Helper functions for generating Lisp for blocks.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Lisp');
+
+goog.require('Blockly.Generator');
+goog.require('Blockly.inputTypes');
+goog.require('Blockly.utils.string');
+
+
+/**
+ * Lisp code generator.
+ * @type {!Blockly.Generator}
+ */
+Blockly.Lisp = new Blockly.Generator('Lisp');
+
+/**
+ * List of illegal variable names.
+ * This is not intended to be a security feature.  Blockly is 100% client-side,
+ * so bypassing this list is trivial.  This is intended to prevent users from
+ * accidentally clobbering a built-in object or function.
+ * @private
+ */
+Blockly.Lisp.addReservedWords(
+    // https://www.lisplang.org/docs/spec/latest/lisp-language-specification.pdf
+    // Section 16.1.1
+    'assert,break,case,catch,class,const,continue,default,do,else,enum,' +
+    'extends,false,final,finally,for,if,in,is,new,null,rethrow,return,super,' +
+    'switch,this,throw,true,try,var,void,while,with,' +
+    // https://api.lisplang.org/lisp_core.html
+    'print,identityHashCode,identical,BidirectionalIterator,Comparable,' +
+    'double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,' +
+    'Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,' +
+    'Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,' +
+    'StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,' +
+    'ArgumentError,AssertionError,CastError,ConcurrentModificationError,' +
+    'CyclicInitializationError,Error,Exception,FallThroughError,' +
+    'FormatException,IntegerDivisionByZeroException,NoSuchMethodError,' +
+    'NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,' +
+    'StateError,TypeError,UnimplementedError,UnsupportedError'
+);
+
+/**
+ * Order of operation ENUMs.
+ * https://lisp.dev/guides/language/language-tour#operators
+ */
+Blockly.Lisp.ORDER_ATOMIC = 0;         // 0 "" ...
+Blockly.Lisp.ORDER_UNARY_POSTFIX = 1;  // expr++ expr-- () [] . ?.
+Blockly.Lisp.ORDER_UNARY_PREFIX = 2;   // -expr !expr ~expr ++expr --expr
+Blockly.Lisp.ORDER_MULTIPLICATIVE = 3; // * / % ~/
+Blockly.Lisp.ORDER_ADDITIVE = 4;       // + -
+Blockly.Lisp.ORDER_SHIFT = 5;          // << >>
+Blockly.Lisp.ORDER_BITWISE_AND = 6;    // &
+Blockly.Lisp.ORDER_BITWISE_XOR = 7;    // ^
+Blockly.Lisp.ORDER_BITWISE_OR = 8;     // |
+Blockly.Lisp.ORDER_RELATIONAL = 9;     // >= > <= < as is is!
+Blockly.Lisp.ORDER_EQUALITY = 10;      // == !=
+Blockly.Lisp.ORDER_LOGICAL_AND = 11;   // &&
+Blockly.Lisp.ORDER_LOGICAL_OR = 12;    // ||
+Blockly.Lisp.ORDER_IF_NULL = 13;       // ??
+Blockly.Lisp.ORDER_CONDITIONAL = 14;   // expr ? expr : expr
+Blockly.Lisp.ORDER_CASCADE = 15;       // ..
+Blockly.Lisp.ORDER_ASSIGNMENT = 16;    // = *= /= ~/= %= += -= <<= >>= &= ^= |=
+Blockly.Lisp.ORDER_NONE = 99;          // (...)
+
+/**
+ * Whether the init method has been called.
+ * @type {?boolean}
+ */
+Blockly.Lisp.isInitialized = false;
+
+/**
+ * Initialise the database of variable names.
+ * @param {!Blockly.Workspace} workspace Workspace to generate code from.
+ */
+Blockly.Lisp.init = function(workspace) {
+  // Create a dictionary of definitions to be printed before the code.
+  Blockly.Lisp.definitions_ = Object.create(null);
+  // Create a dictionary mapping desired function names in definitions_
+  // to actual function names (to avoid collisions with user functions).
+  Blockly.Lisp.functionNames_ = Object.create(null);
+
+  if (!Blockly.Lisp.variableDB_) {
+    Blockly.Lisp.variableDB_ =
+        new Blockly.Names(Blockly.Lisp.RESERVED_WORDS_);
+  } else {
+    Blockly.Lisp.variableDB_.reset();
+  }
+
+  Blockly.Lisp.variableDB_.setVariableMap(workspace.getVariableMap());
+
+  var defvars = [];
+  // Add developer variables (not created or named by the user).
+  var devVarList = Blockly.Variables.allDeveloperVariables(workspace);
+  for (var i = 0; i < devVarList.length; i++) {
+    defvars.push(Blockly.Lisp.variableDB_.getName(devVarList[i],
+        Blockly.Names.DEVELOPER_VARIABLE_TYPE));
+  }
+
+  // Add user variables, but only ones that are being used.
+  var variables = Blockly.Variables.allUsedVarModels(workspace);
+  for (var i = 0; i < variables.length; i++) {
+    defvars.push(Blockly.Lisp.variableDB_.getName(variables[i].getId(),
+        Blockly.VARIABLE_CATEGORY_NAME));
+  }
+
+  // Declare all of the variables.
+  if (defvars.length) {
+    Blockly.Lisp.definitions_['variables'] =
+        'var ' + defvars.join(', ') + ';';
+  }
+  this.isInitialized = true;
+};
+
+/**
+ * Prepend the generated code with the variable definitions.
+ * @param {string} code Generated code.
+ * @return {string} Completed code.
+ */
+Blockly.Lisp.finish = function(code) {
+  // Indent every line.
+  if (code) {
+    code = Blockly.Lisp.prefixLines(code, Blockly.Lisp.INDENT);
+  }
+  code = 'main() {\n' + code + '}';
+
+  // Convert the definitions dictionary into a list.
+  var imports = [];
+  var definitions = [];
+  for (var name in Blockly.Lisp.definitions_) {
+    var def = Blockly.Lisp.definitions_[name];
+    if (def.match(/^import\s/)) {
+      imports.push(def);
+    } else {
+      definitions.push(def);
+    }
+  }
+  // Clean up temporary data.
+  delete Blockly.Lisp.definitions_;
+  delete Blockly.Lisp.functionNames_;
+  Blockly.Lisp.variableDB_.reset();
+  var allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n');
+  return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code;
+};
+
+/**
+ * Naked values are top-level blocks with outputs that aren't plugged into
+ * anything.  A trailing semicolon is needed to make this legal.
+ * @param {string} line Line of generated code.
+ * @return {string} Legal line of code.
+ */
+Blockly.Lisp.scrubNakedValue = function(line) {
+  return line + ';\n';
+};
+
+/**
+ * Encode a string as a properly escaped Lisp string, complete with quotes.
+ * @param {string} string Text to encode.
+ * @return {string} Lisp string.
+ * @protected
+ */
+Blockly.Lisp.quote_ = function(string) {
+  // Can't use goog.string.quote since $ must also be escaped.
+  string = string.replace(/\\/g, '\\\\')
+                 .replace(/\n/g, '\\\n')
+                 .replace(/\$/g, '\\$')
+                 .replace(/'/g, '\\\'');
+  return '\'' + string + '\'';
+};
+
+/**
+ * Encode a string as a properly escaped multiline Lisp string, complete with
+ * quotes.
+ * @param {string} string Text to encode.
+ * @return {string} Lisp string.
+ * @protected
+ */
+Blockly.Lisp.multiline_quote_ = function (string) {
+  var lines = string.split(/\n/g).map(Blockly.Lisp.quote_);
+  // Join with the following, plus a newline:
+  // + '\n' +
+  return lines.join(' + \'\\n\' + \n');
+};
+
+/**
+ * Common tasks for generating Lisp from blocks.
+ * Handles comments for the specified block and any connected value blocks.
+ * Calls any statements following this block.
+ * @param {!Blockly.Block} block The current block.
+ * @param {string} code The Lisp code created for this block.
+ * @param {boolean=} opt_thisOnly True to generate code for only this statement.
+ * @return {string} Lisp code with comments and subsequent blocks added.
+ * @protected
+ */
+Blockly.Lisp.scrub_ = function(block, code, opt_thisOnly) {
+  var commentCode = '';
+  // Only collect comments for blocks that aren't inline.
+  if (!block.outputConnection || !block.outputConnection.targetConnection) {
+    // Collect comment for this block.
+    var comment = block.getCommentText();
+    if (comment) {
+      comment = Blockly.utils.string.wrap(comment,
+          Blockly.Lisp.COMMENT_WRAP - 3);
+      if (block.getProcedureDef) {
+        // Use documentation comment for function comments.
+        commentCode += Blockly.Lisp.prefixLines(comment + '\n', '/// ');
+      } else {
+        commentCode += Blockly.Lisp.prefixLines(comment + '\n', '// ');
+      }
+    }
+    // Collect comments for all value arguments.
+    // Don't collect comments for nested statements.
+    for (var i = 0; i < block.inputList.length; i++) {
+      if (block.inputList[i].type == Blockly.inputTypes.VALUE) {
+        var childBlock = block.inputList[i].connection.targetBlock();
+        if (childBlock) {
+          comment = Blockly.Lisp.allNestedComments(childBlock);
+          if (comment) {
+            commentCode += Blockly.Lisp.prefixLines(comment, '// ');
+          }
+        }
+      }
+    }
+  }
+  var nextBlock = block.nextConnection && block.nextConnection.targetBlock();
+  var nextCode = opt_thisOnly ? '' : Blockly.Lisp.blockToCode(nextBlock);
+  return commentCode + code + nextCode;
+};
+
+/**
+ * Gets a property and adjusts the value while taking into account indexing.
+ * @param {!Blockly.Block} block The block.
+ * @param {string} atId The property ID of the element to get.
+ * @param {number=} opt_delta Value to add.
+ * @param {boolean=} opt_negate Whether to negate the value.
+ * @param {number=} opt_order The highest order acting on this value.
+ * @return {string|number}
+ */
+Blockly.Lisp.getAdjusted = function(block, atId, opt_delta, opt_negate,
+    opt_order) {
+  var delta = opt_delta || 0;
+  var order = opt_order || Blockly.Lisp.ORDER_NONE;
+  if (block.workspace.options.oneBasedIndex) {
+    delta--;
+  }
+  var defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0';
+  if (delta) {
+    var at = Blockly.Lisp.valueToCode(block, atId,
+        Blockly.Lisp.ORDER_ADDITIVE) || defaultAtIndex;
+  } else if (opt_negate) {
+    var at = Blockly.Lisp.valueToCode(block, atId,
+        Blockly.Lisp.ORDER_UNARY_PREFIX) || defaultAtIndex;
+  } else {
+    var at = Blockly.Lisp.valueToCode(block, atId, order) ||
+        defaultAtIndex;
+  }
+
+  if (Blockly.isNumber(at)) {
+    // If the index is a naked number, adjust it right now.
+    at = parseInt(at, 10) + delta;
+    if (opt_negate) {
+      at = -at;
+    }
+  } else {
+    // If the index is dynamic, adjust it in code.
+    if (delta > 0) {
+      at = at + ' + ' + delta;
+      var innerOrder = Blockly.Lisp.ORDER_ADDITIVE;
+    } else if (delta < 0) {
+      at = at + ' - ' + -delta;
+      var innerOrder = Blockly.Lisp.ORDER_ADDITIVE;
+    }
+    if (opt_negate) {
+      if (delta) {
+        at = '-(' + at + ')';
+      } else {
+        at = '-' + at;
+      }
+      var innerOrder = Blockly.Lisp.ORDER_UNARY_PREFIX;
+    }
+    innerOrder = Math.floor(innerOrder);
+    order = Math.floor(order);
+    if (innerOrder && order >= innerOrder) {
+      at = '(' + at + ')';
+    }
+  }
+  return at;
+};

From cfa20d19ce043efd5af0ce62556ad0a34a7ae0c9 Mon Sep 17 00:00:00 2001
From: lupyuen 
Date: Fri, 7 May 2021 19:05:58 +0800
Subject: [PATCH 04/48] Add mynewt blocks

---
 demos/code/index.html               |   10 +-
 generators/lisp/app_blocks.js       |  208 +++++
 generators/lisp/app_code.js         |  126 +++
 generators/lisp/mynewt_blocks.js    |  288 +++++++
 generators/lisp/mynewt_coap.js      |  208 +++++
 generators/lisp/mynewt_functions.js |  120 +++
 generators/lisp/mynewt_library.xml  |  605 ++++++++++++++
 generators/lisp/widgets_blocks.js   | 1135 +++++++++++++++++++++++++++
 generators/lisp/widgets_category.js |  323 ++++++++
 generators/lisp/widgets_code.js     |  169 ++++
 10 files changed, 3191 insertions(+), 1 deletion(-)
 create mode 100644 generators/lisp/app_blocks.js
 create mode 100644 generators/lisp/app_code.js
 create mode 100644 generators/lisp/mynewt_blocks.js
 create mode 100644 generators/lisp/mynewt_coap.js
 create mode 100644 generators/lisp/mynewt_functions.js
 create mode 100644 generators/lisp/mynewt_library.xml
 create mode 100644 generators/lisp/widgets_blocks.js
 create mode 100644 generators/lisp/widgets_category.js
 create mode 100644 generators/lisp/widgets_code.js

diff --git a/demos/code/index.html b/demos/code/index.html
index b2ffa8e59c8..c5400c1d405 100644
--- a/demos/code/index.html
+++ b/demos/code/index.html
@@ -27,7 +27,15 @@
   
 
   
-  
+  
+  
+  
+  
+  
+  
+  
+  
+
   
   
   
diff --git a/generators/lisp/app_blocks.js b/generators/lisp/app_blocks.js
new file mode 100644
index 00000000000..a845e6de96f
--- /dev/null
+++ b/generators/lisp/app_blocks.js
@@ -0,0 +1,208 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview App blocks for Blockly.
+ * @author luppy@appkaki.com (Lee Lup Yuen)
+ */
+'use strict';
+
+goog.provide('Blockly.Constants.App');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly');
+
+
+/**
+ * Unused constant for the common HSV hue for all blocks in this category.
+ * @deprecated Use Blockly.Msg['TEXTS_HUE']. (2018 April 5)
+ */
+Blockly.Constants.App.HUE = 120;
+
+Blockly.defineBlocksWithJsonArray([  // BEGIN JSON EXTRACT
+  {
+    "type": "app",
+    "message0": "",
+    "output": "String",
+    "style": "text_blocks",  //  TODO
+    "helpUrl": "%{BKY_TEXT_JOIN_HELPURL}",
+    "tooltip": "Define the Widgets for an App",
+    "mutator": "app_mutator"
+  },
+  {
+    "type": "app_container",
+    "message0": "create app with %1 %2",
+    "args0": [{
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "STACK"
+    }],
+    "style": "text_blocks",  //  TODO
+    "tooltip": "App Widgets",
+    "enableContextMenu": false
+  },
+  {
+    "type": "app_item",
+    "message0": "item",
+    "previousStatement": null,
+    "nextStatement": null,
+    "style": "text_blocks",  //  TODO
+    "tooltip": "App Widget",
+    "enableContextMenu": false
+  }
+]);  // END JSON EXTRACT (Do not delete this comment.)
+
+/**
+ * Mixin for mutator functions in the 'app_mutator' extension.
+ * @mixin
+ * @augments Blockly.Block
+ * @package
+ */
+Blockly.Constants.App.APP_MUTATOR_MIXIN = {
+  /**
+   * Create XML to represent number of text inputs.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('items', this.itemCount_);
+    return container;
+  },
+  /**
+   * Parse XML to restore the text inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
+    this.updateShape_();
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = workspace.newBlock('app_container');
+    containerBlock.initSvg();
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.itemCount_; i++) {
+      var itemBlock = workspace.newBlock('app_item');
+      itemBlock.initSvg();
+      connection.connect(itemBlock.previousConnection);
+      connection = itemBlock.nextConnection;
+    }
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    // Count number of inputs.
+    var connections = [];
+    while (itemBlock) {
+      connections.push(itemBlock.valueConnection_);
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+    // Disconnect any children that don't belong.
+    for (var i = 0; i < this.itemCount_; i++) {
+      var connection = this.getInput('ADD' + i).connection.targetConnection;
+      if (connection && connections.indexOf(connection) == -1) {
+        connection.disconnect();
+      }
+    }
+    this.itemCount_ = connections.length;
+    this.updateShape_();
+    // Reconnect any child blocks.
+    for (var i = 0; i < this.itemCount_; i++) {
+      Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
+    }
+  },
+  /**
+   * Store pointers to any connected child blocks.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  saveConnections: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    var i = 0;
+    while (itemBlock) {
+      var input = this.getInput('ADD' + i);
+      itemBlock.valueConnection_ = input && input.connection.targetConnection;
+      i++;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of inputs.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    if (this.itemCount_ && this.getInput('EMPTY')) {
+      this.removeInput('EMPTY');
+    } else if (!this.itemCount_ && !this.getInput('EMPTY')) {
+      this.appendDummyInput('EMPTY')
+          .appendField(this.newQuote_(true))
+          .appendField(this.newQuote_(false));
+    }
+    // Add new inputs.
+    for (var i = 0; i < this.itemCount_; i++) {
+      if (!this.getInput('ADD' + i)) {
+        var input = this.appendValueInput('ADD' + i);
+        if (i == 0) {
+          input.appendField('create app with');
+        }
+      }
+    }
+    // Remove deleted inputs.
+    while (this.getInput('ADD' + i)) {
+      this.removeInput('ADD' + i);
+      i++;
+    }
+  }
+};
+
+/**
+ * Performs final setup of a text_join block.
+ * @this Blockly.Block
+ */
+Blockly.Constants.App.APP_EXTENSION = function() {
+  // Add the quote mixin for the itemCount_ = 0 case.
+  this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
+  // Initialize the mutator values.
+  this.itemCount_ = 2;
+  this.updateShape_();
+  // Configure the mutator UI.
+  this.setMutator(new Blockly.Mutator(['app_item']));
+};
+
+Blockly.Extensions.registerMutator('app_mutator',
+    Blockly.Constants.App.APP_MUTATOR_MIXIN,
+    Blockly.Constants.App.APP_EXTENSION);
diff --git a/generators/lisp/app_code.js b/generators/lisp/app_code.js
new file mode 100644
index 00000000000..9da2783aaff
--- /dev/null
+++ b/generators/lisp/app_code.js
@@ -0,0 +1,126 @@
+/// Code Generator Functions for App Blocks
+
+Blockly.Lisp['on_start'] = function(block) {
+  var statements_stmts = Blockly.Lisp.statementToCode(block, 'STMTS');
+  var code = statements_stmts;
+  if (code) {
+    //  code = Blockly.Lisp.prefixLines(code, Blockly.Lisp.INDENT);
+  }
+  //  TODO: Allow multiple `on_start` blocks.
+  code = [
+    '/// Will be run upon startup to launch the app',
+    '#[infer_type]  //  Infer the missing types',
+    'pub fn on_start() -> MynewtResult<()> {',
+    Blockly.Lisp.prefixLines([
+      'console::print("on_start\\n");',
+      '//  Build a new window',
+      'let main_window = WindowDesc::new(ui_builder);',
+      '//  Create application state',
+      'let state = State::default();',
+    ].join('\n'), 
+    Blockly.Lisp.INDENT),
+    code,
+    Blockly.Lisp.prefixLines([
+      '//  Launch the window with the application state',
+      'AppLauncher::with_window(main_window)',
+      Blockly.Lisp.INDENT + '.use_simple_logger()',
+      Blockly.Lisp.INDENT + '.launch(state)',
+      Blockly.Lisp.INDENT + '.expect("launch failed");',
+      '//  Return success to `main()` function',
+      'Ok(())',
+    ].join('\n'), 
+    Blockly.Lisp.INDENT),
+    '}',
+    ''
+  ].join('\n');
+  return code;
+};
+
+Blockly.Lisp['app'] = function(block) {
+  //  Generate App Widget with ui_builder() function
+  Blockly.Lisp.widgets_ = {};
+  var elements = new Array(block.itemCount_);
+  for (var i = 0; i < block.itemCount_; i++) {
+    elements[i] = Blockly.Lisp.valueToCode(block, 'ADD' + i,
+            Blockly.Lisp.ORDER_NONE) || '\'\'';
+  }
+
+  //  Create the Widgets e.g. let my_button = Button::new("increment", on_my_button_press); 
+  var widgets = Object.values(Blockly.Lisp.widgets_).join('\n');
+
+  //  Add the Widgets
+  var code = [
+    '/// Build the UI for the window',
+    'fn ui_builder() -> impl Widget {  //  `State` is the Application State',  //  TODO: Fix 
+    Blockly.Lisp.prefixLines([
+        'console::print("Lisp UI builder\\n"); console::flush();',
+        widgets,
+        '',
+        '//  Create a column',
+        'let mut col = Column::new();',
+        //  Insert the elements.
+        elements.join('\n'),
+        '//  Return the column containing the widgets',
+        'col',  
+      ].join('\n'), 
+      Blockly.Lisp.INDENT),
+    '}',  //  TODO: Remove trailing semicolon
+  ].join('\n');
+  return [code, Blockly.Lisp.ORDER_NONE];
+};
+
+Blockly.Lisp['label'] = function(block) {
+  //  Generate a Label Widget
+  var text_name = block.getFieldValue('NAME');  //  e.g. my_label
+  var value_name = Blockly.Lisp.valueToCode(block, 'name', Blockly.JavaScript.ORDER_ATOMIC);
+
+  //  Create the Widget
+  Blockly.Lisp.widgets_[text_name] = [
+    '//  Create a line of text',
+    'let ' + text_name + '_text = LocalizedString::new("hello-counter")',  //  TODO
+    Blockly.Lisp.INDENT + '.with_arg("count", on_' + text_name + '_show);  //  Call `on_' + text_name + '_show` to get label text',
+    '//  Create a label widget ' + text_name,
+    'let ' + text_name + ' = Label::new(' + text_name + '_text);',
+  ].join('\n');
+
+  //  Add the Widget
+  var code = [
+    '//  Add the label widget to the column, centered with padding',
+    'col.add_child(',
+    Blockly.Lisp.INDENT + 'Align::centered(',
+    Blockly.Lisp.INDENT + Blockly.Lisp.INDENT + 'Padding::new(5.0, ',  //  TODO
+    Blockly.Lisp.INDENT + Blockly.Lisp.INDENT + Blockly.Lisp.INDENT + text_name,
+    Blockly.Lisp.INDENT + Blockly.Lisp.INDENT + ')',
+    Blockly.Lisp.INDENT + '),',
+    Blockly.Lisp.INDENT + '1.0',
+    ');',
+  ].join('\n');
+
+  // TODO: Change ORDER_NONE to the correct strength.
+  return [code, Blockly.Lisp.ORDER_NONE];
+};
+
+Blockly.Lisp['button'] = function(block) {
+  //  Generate a Button Widget
+  var text_name = block.getFieldValue('NAME');  //  e.g. my_button
+  var value_name = Blockly.Lisp.valueToCode(block, 'name', Blockly.JavaScript.ORDER_ATOMIC);
+
+  //  Create the Widget
+  Blockly.Lisp.widgets_[text_name] = [
+    '//  Create a button widget ' + text_name,  //  TODO
+    'let ' + text_name + ' = Button::new("increment", on_' + text_name + '_press);  //  Call `on_' + text_name + '_press` when pressed',  //  TODO
+  ].join('\n');
+
+  //  Add the Widget
+  var code = [
+    '//  Add the button widget to the column, with padding',
+    'col.add_child(',
+    Blockly.Lisp.INDENT + 'Padding::new(5.0, ',  //  TODO
+    Blockly.Lisp.INDENT + Blockly.Lisp.INDENT + text_name,
+    Blockly.Lisp.INDENT + '),',
+    Blockly.Lisp.INDENT + '1.0',
+    ');',
+  ].join('\n');
+  // TODO: Change ORDER_NONE to the correct strength.
+  return [code, Blockly.Lisp.ORDER_NONE];
+};
diff --git a/generators/lisp/mynewt_blocks.js b/generators/lisp/mynewt_blocks.js
new file mode 100644
index 00000000000..22e50586273
--- /dev/null
+++ b/generators/lisp/mynewt_blocks.js
@@ -0,0 +1,288 @@
+/// Custom blocks exported from Block Exporter based on mynewt_library.xml.
+/// See mynewt_functions.js for Code Generator Functions.
+var mynewt_blocks =
+// Begin Block Exporter
+[{
+  "type": "digital_toggle_pin",
+  "message0": "digital toggle pin %1",
+  "args0": [
+    {
+      "type": "field_dropdown",
+      "name": "PIN",
+      "options": [
+        [
+          "PA1",
+          "MCU_GPIO_PORTA!(1)"
+        ],
+        [
+          "PB1",
+          "MCU_GPIO_PORTB!(1)"
+        ],
+        [
+          "PC13",
+          "MCU_GPIO_PORTC!(13)"
+        ]
+      ]
+    }
+  ],
+  "inputsInline": true,
+  "previousStatement": "Action",
+  "nextStatement": "Action",
+  "colour": 330,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "forever",
+  "message0": "forever %1 %2",
+  "args0": [
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "STMTS"
+    }
+  ],
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "wait",
+  "message0": "wait %1 seconds",
+  "args0": [
+    {
+      "type": "field_number",
+      "name": "DURATION",
+      "value": 0,
+      "min": 0,
+      "precision": 1
+    }
+  ],
+  "previousStatement": "Action",
+  "nextStatement": "Action",
+  "colour": 160,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "on_start",
+  "message0": "on start %1 %2",
+  "args0": [
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "STMTS"
+    }
+  ],
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "digital_read_pin",
+  "message0": "digital read pin %1",
+  "args0": [
+    {
+      "type": "field_dropdown",
+      "name": "PIN",
+      "options": [
+        [
+          "PA1",
+          "MCU_GPIO_PORTA!(1)"
+        ],
+        [
+          "PB1",
+          "MCU_GPIO_PORTB!(1)"
+        ],
+        [
+          "PC13",
+          "MCU_GPIO_PORTC!(13)"
+        ]
+      ]
+    }
+  ],
+  "output": "Number",
+  "colour": 330,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "digital_write_pin",
+  "message0": "digital write pin %1 to %2",
+  "args0": [
+    {
+      "type": "field_dropdown",
+      "name": "PIN",
+      "options": [
+        [
+          "PA1",
+          "MCU_GPIO_PORTA!(1)"
+        ],
+        [
+          "PB1",
+          "MCU_GPIO_PORTB!(1)"
+        ],
+        [
+          "PC13",
+          "MCU_GPIO_PORTC!(13)"
+        ]
+      ]
+    },
+    {
+      "type": "field_dropdown",
+      "name": "VALUE",
+      "options": [
+        [
+          "LOW",
+          "0"
+        ],
+        [
+          "HIGH",
+          "1"
+        ]
+      ]
+    }
+  ],
+  "previousStatement": "Action",
+  "nextStatement": "Action",
+  "colour": 330,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "field",
+  "message0": "field %1 %2 value %3",
+  "args0": [
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "field_input",
+      "name": "NAME",
+      "text": "name"
+    },
+    {
+      "type": "input_value",
+      "name": "name"
+    }
+  ],
+  "inputsInline": true,
+  "output": null,
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "label",
+  "message0": "label %1 %2 %3 padding %4 %5",
+  "args0": [
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "field_input",
+      "name": "NAME",
+      "text": "name"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "field_number",
+      "name": "PADDING",
+      "value": 0,
+      "min": 0,
+      "max": 240
+    }
+  ],
+  "inputsInline": true,
+  "output": null,
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "button",
+  "message0": "button %1 %2 %3 padding %4 %5",
+  "args0": [
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "field_input",
+      "name": "NAME",
+      "text": "name"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "field_number",
+      "name": "PADDING",
+      "value": 0,
+      "min": 0,
+      "max": 240
+    }
+  ],
+  "inputsInline": true,
+  "output": null,
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "on_label_show",
+  "message0": "on label %1 show %2 %3",
+  "args0": [
+    {
+      "type": "field_input",
+      "name": "NAME",
+      "text": "name"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "NAME"
+    }
+  ],
+  "inputsInline": true,
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+},
+{
+  "type": "on_button_press",
+  "message0": "on button %1 press %2 %3",
+  "args0": [
+    {
+      "type": "field_input",
+      "name": "NAME",
+      "text": "name"
+    },
+    {
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "NAME"
+    }
+  ],
+  "inputsInline": true,
+  "colour": 120,
+  "tooltip": "",
+  "helpUrl": ""
+}]
+// End Block Exporter
+;
\ No newline at end of file
diff --git a/generators/lisp/mynewt_coap.js b/generators/lisp/mynewt_coap.js
new file mode 100644
index 00000000000..1f69b9220a7
--- /dev/null
+++ b/generators/lisp/mynewt_coap.js
@@ -0,0 +1,208 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview CoAP blocks for Blockly.
+ * @author luppy@appkaki.com (Lee Lup Yuen)
+ */
+'use strict';
+
+goog.provide('Blockly.Constants.Coap');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly');
+
+
+/**
+ * Unused constant for the common HSV hue for all blocks in this category.
+ * @deprecated Use Blockly.Msg['TEXTS_HUE']. (2018 April 5)
+ */
+Blockly.Constants.Coap.HUE = 120;
+
+Blockly.defineBlocksWithJsonArray([  // BEGIN JSON EXTRACT
+  {
+    "type": "coap",
+    "message0": "",
+    "output": "String",
+    "style": "text_blocks",  //  TODO
+    "helpUrl": "%{BKY_TEXT_JOIN_HELPURL}",
+    "tooltip": "Create the payload for a CoAP message",
+    "mutator": "coap_mutator"
+  },
+  {
+    "type": "coap_container",
+    "message0": "create coap with %1 %2",
+    "args0": [{
+      "type": "input_dummy"
+    },
+    {
+      "type": "input_statement",
+      "name": "STACK"
+    }],
+    "style": "text_blocks",  //  TODO
+    "tooltip": "CoAP payload",
+    "enableContextMenu": false
+  },
+  {
+    "type": "coap_item",
+    "message0": "item",
+    "previousStatement": null,
+    "nextStatement": null,
+    "style": "text_blocks",  //  TODO
+    "tooltip": "CoAP item",
+    "enableContextMenu": false
+  }
+]);  // END JSON EXTRACT (Do not delete this comment.)
+
+/**
+ * Mixin for mutator functions in the 'coap_mutator' extension.
+ * @mixin
+ * @augments Blockly.Block
+ * @package
+ */
+Blockly.Constants.Coap.COAP_MUTATOR_MIXIN = {
+  /**
+   * Create XML to represent number of text inputs.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('items', this.itemCount_);
+    return container;
+  },
+  /**
+   * Parse XML to restore the text inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
+    this.updateShape_();
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = workspace.newBlock('coap_container');
+    containerBlock.initSvg();
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.itemCount_; i++) {
+      var itemBlock = workspace.newBlock('coap_item');
+      itemBlock.initSvg();
+      connection.connect(itemBlock.previousConnection);
+      connection = itemBlock.nextConnection;
+    }
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    // Count number of inputs.
+    var connections = [];
+    while (itemBlock) {
+      connections.push(itemBlock.valueConnection_);
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+    // Disconnect any children that don't belong.
+    for (var i = 0; i < this.itemCount_; i++) {
+      var connection = this.getInput('ADD' + i).connection.targetConnection;
+      if (connection && connections.indexOf(connection) == -1) {
+        connection.disconnect();
+      }
+    }
+    this.itemCount_ = connections.length;
+    this.updateShape_();
+    // Reconnect any child blocks.
+    for (var i = 0; i < this.itemCount_; i++) {
+      Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
+    }
+  },
+  /**
+   * Store pointers to any connected child blocks.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  saveConnections: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    var i = 0;
+    while (itemBlock) {
+      var input = this.getInput('ADD' + i);
+      itemBlock.valueConnection_ = input && input.connection.targetConnection;
+      i++;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of inputs.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    if (this.itemCount_ && this.getInput('EMPTY')) {
+      this.removeInput('EMPTY');
+    } else if (!this.itemCount_ && !this.getInput('EMPTY')) {
+      this.appendDummyInput('EMPTY')
+          .appendField(this.newQuote_(true))
+          .appendField(this.newQuote_(false));
+    }
+    // Add new inputs.
+    for (var i = 0; i < this.itemCount_; i++) {
+      if (!this.getInput('ADD' + i)) {
+        var input = this.appendValueInput('ADD' + i);
+        if (i == 0) {
+          input.appendField('create coap with');
+        }
+      }
+    }
+    // Remove deleted inputs.
+    while (this.getInput('ADD' + i)) {
+      this.removeInput('ADD' + i);
+      i++;
+    }
+  }
+};
+
+/**
+ * Performs final setup of a text_join block.
+ * @this Blockly.Block
+ */
+Blockly.Constants.Coap.COAP_EXTENSION = function() {
+  // Add the quote mixin for the itemCount_ = 0 case.
+  this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
+  // Initialize the mutator values.
+  this.itemCount_ = 2;
+  this.updateShape_();
+  // Configure the mutator UI.
+  this.setMutator(new Blockly.Mutator(['coap_item']));
+};
+
+Blockly.Extensions.registerMutator('coap_mutator',
+    Blockly.Constants.Coap.COAP_MUTATOR_MIXIN,
+    Blockly.Constants.Coap.COAP_EXTENSION);
diff --git a/generators/lisp/mynewt_functions.js b/generators/lisp/mynewt_functions.js
new file mode 100644
index 00000000000..9983dc0b415
--- /dev/null
+++ b/generators/lisp/mynewt_functions.js
@@ -0,0 +1,120 @@
+/// Code Generator Functions for Custom Blocks in mynewt_blocks.js.
+/// Initially exported by Block Exporter from mynewt_library.xml.
+
+
+Blockly.Lisp['coap'] = function(block) {
+  //  Generate CoAP message payload:
+  //  coap!( @json {        
+  //    "device": &device_id,
+  //    sensor_data,
+  //  })
+  var elements = new Array(block.itemCount_);
+  for (var i = 0; i < block.itemCount_; i++) {
+    elements[i] = Blockly.Lisp.valueToCode(block, 'ADD' + i,
+            Blockly.Lisp.ORDER_NONE) || '\'\'';
+  }
+  var code = [
+    'coap!( @json {',
+    //  Insert the indented elements.
+    Blockly.Lisp.prefixLines(
+      elements.join(',\n'), 
+      Blockly.Lisp.INDENT),
+    '})',
+  ].join('\n');
+  return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX];
+};
+
+Blockly.Lisp['field'] = function(block) {
+  //  Generate a field for CoAP message payload: `name: value`
+  var text_name = block.getFieldValue('NAME');
+  var value_name = Blockly.Lisp.valueToCode(block, 'name', Blockly.JavaScript.ORDER_ATOMIC);
+  var code = [
+    '"', text_name, '"',
+    ': ',
+    value_name,
+  ].join('');
+  // TODO: Change ORDER_NONE to the correct strength.
+  return [code, Blockly.Lisp.ORDER_NONE];
+};
+
+Blockly.Lisp['forever'] = function(block) {
+  var statements_stmts = Blockly.Lisp.statementToCode(block, 'STMTS');
+  // Indent every line twice.
+  var code = statements_stmts;
+  if (code) {
+    code = Blockly.Lisp.prefixLines(code, Blockly.Lisp.INDENT);
+    code = Blockly.Lisp.prefixLines(code, Blockly.Lisp.INDENT);
+  }
+  //  TODO: Allow multiple Background Tasks for multiple `forever` blocks.
+  code = [
+    '/// Will be run as Background Task that never terminates',
+    'fn task_func(arg: Ptr) -> MynewtResult<()> {',
+    '    // Loop forever',
+    '    loop {',
+    code,
+    '    }',
+    '    // Never comes here',
+    '    Ok(())',
+    '}',
+    '',
+    '/// Start the Background Task',
+    'fn start_task() -> MynewtResult<()> {',
+    '    os::task_init(          //  Create a new task and start it...',
+    '        out!( TASK_OBJ ),   //  Task object will be saved here',
+    '        strn!( "forever" ), //  Name of task',
+    '        Some( task_func ),  //  Function to execute when task starts',
+    '        NULL,  //  Argument to be passed to above function',
+    '        10,    //  Task priority: highest is 0, lowest is 255 (main task is 127)',
+    '        os::OS_WAIT_FOREVER as u32, //  Don\'t do sanity / watchdog checking',
+    '        out!( TASK_STACK ),         //  Stack space for the task',
+    '        TASK_STACK_SIZE as u16      //  Size of the stack (in 4-byte units)',
+    '    ) ? ;                           //  `?` means check for error',
+    '    // Return success to `main()` function',
+    '    Ok(())',
+    '}',
+    ''
+  ].join('\n');
+  return code;
+};
+
+Blockly.Lisp['wait'] = function(block) {
+  var number_duration = block.getFieldValue('DURATION');
+  var code = [
+    '// Wait ' + number_duration + ' second(s)',
+    'os::time_delay(' + number_duration + ' * OS_TICKS_PER_SEC) ? ;',
+    ''
+  ].join('\n');
+  return code;
+};
+
+Blockly.Lisp['digital_toggle_pin'] = function(block) {
+  var dropdown_pin = block.getFieldValue('PIN');
+  //  TODO: Call init_out only once,
+  var code = [
+    '//  Toggle the GPIO pin',
+    'gpio::toggle(' + dropdown_pin + ') ? ;',
+    ''
+  ].join('\n');
+  return code;
+};
+
+Blockly.Lisp['digital_read_pin'] = function(block) {
+  var dropdown_pin = block.getFieldValue('PIN');
+  //  TODO: Call init_in only once: gpio::init_in(MCU_GPIO_PORTC!(13), pull_type) ? ;
+  var code = 'gpio::read(' + dropdown_pin + ')';
+  //  TODO: Change ORDER_NONE to the correct strength.
+  return [code, Blockly.Lisp.ORDER_NONE];
+};
+
+Blockly.Lisp['digital_write_pin'] = function(block) {
+  var dropdown_pin = block.getFieldValue('PIN');
+  var dropdown_value = block.getFieldValue('VALUE');
+  //  TODO: Call init_out only once,
+  var code = [
+    '//  Configure the GPIO pin for output and set the value.',
+    'gpio::init_out(' + dropdown_pin + ', ' + dropdown_value + ') ? ;',
+    'gpio::write(' + dropdown_pin + ',' + dropdown_value + ') ? ;',
+    ''
+  ].join('\n');  
+  return code;
+};
\ No newline at end of file
diff --git a/generators/lisp/mynewt_library.xml b/generators/lisp/mynewt_library.xml
new file mode 100644
index 00000000000..6ec9d82086b
--- /dev/null
+++ b/generators/lisp/mynewt_library.xml
@@ -0,0 +1,605 @@
+
+    
+    digital_toggle_pin
+    INT
+    BOTH
+    
+      
+        LEFT
+        
+          
+            digital toggle pin
+            
+              
+                
+                PIN
+                PA1
+                MCU_GPIO_PORTA!(1)
+                PB1
+                MCU_GPIO_PORTB!(1)
+                PC13
+                MCU_GPIO_PORTC!(13)
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+        
+        330
+      
+    
+  
+    
+    forever
+    AUTO
+    NONE
+    
+      
+        LEFT
+        
+          
+            forever
+          
+        
+        
+          
+            STMTS
+            LEFT
+            
+              
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    wait
+    AUTO
+    BOTH
+    
+      
+        LEFT
+        
+          
+            wait
+            
+              
+                0
+                DURATION
+                0
+                Infinity
+                1
+                
+                  
+                    seconds
+                  
+                
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+        
+        160
+      
+    
+  
+    
+    on_start
+    AUTO
+    NONE
+    
+      
+        LEFT
+        
+          
+            on start
+          
+        
+        
+          
+            STMTS
+            LEFT
+            
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    digital_read_pin
+    AUTO
+    LEFT
+    
+      
+        LEFT
+        
+          
+            digital read pin
+            
+              
+                
+                PIN
+                PA1
+                MCU_GPIO_PORTA!(1)
+                PB1
+                MCU_GPIO_PORTB!(1)
+                PC13
+                MCU_GPIO_PORTC!(13)
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+      
+    
+    
+      
+        
+        330
+      
+    
+  
+    
+    digital_write_pin
+    AUTO
+    BOTH
+    
+      
+        LEFT
+        
+          
+            digital write pin
+            
+              
+                
+                PIN
+                PA1
+                MCU_GPIO_PORTA!(1)
+                PB1
+                MCU_GPIO_PORTB!(1)
+                PC13
+                MCU_GPIO_PORTC!(13)
+                
+                  
+                    to
+                    
+                      
+                        
+                        VALUE
+                        LOW
+                        0
+                        HIGH
+                        1
+                      
+                    
+                  
+                
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+      
+        Action
+      
+    
+    
+      
+        
+        330
+      
+    
+  
+    
+    field
+    INT
+    LEFT
+    
+      
+        LEFT
+        
+          
+            field
+          
+        
+        
+          
+            name
+            LEFT
+            
+              
+                name
+                NAME
+                
+                  
+                    value
+                  
+                
+              
+            
+            
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    label
+    INT
+    LEFT
+    
+      
+        LEFT
+        
+          
+            label
+          
+        
+        
+          
+            LEFT
+            
+              
+                name
+                NAME
+              
+            
+            
+              
+                LEFT
+                
+                  
+                    padding
+                  
+                
+                
+                  
+                    LEFT
+                    
+                      
+                        0
+                        PADDING
+                        0
+                        240
+                        0
+                      
+                    
+                  
+                
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    button
+    INT
+    LEFT
+    
+      
+        LEFT
+        
+          
+            button
+          
+        
+        
+          
+            LEFT
+            
+              
+                name
+                NAME
+              
+            
+            
+              
+                LEFT
+                
+                  
+                    padding
+                  
+                
+                
+                  
+                    LEFT
+                    
+                      
+                        0
+                        PADDING
+                        0
+                        240
+                        0
+                      
+                    
+                  
+                
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    on_button_press
+    INT
+    NONE
+    
+      
+        LEFT
+        
+          
+            on button
+            
+              
+                name
+                NAME
+                
+                  
+                    press
+                  
+                
+              
+            
+          
+        
+        
+          
+            NAME
+            LEFT
+            
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+        120
+      
+    
+  
+    
+    on_label_show
+    INT
+    NONE
+    
+      
+        LEFT
+        
+          
+            on label
+            
+              
+                name
+                NAME
+                
+                  
+                    show
+                  
+                
+              
+            
+          
+        
+        
+          
+            NAME
+            LEFT
+            
+              
+            
+          
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+      
+    
+    
+      
+        
+        120
+      
+    
+  
\ No newline at end of file
diff --git a/generators/lisp/widgets_blocks.js b/generators/lisp/widgets_blocks.js
new file mode 100644
index 00000000000..2dc8383e145
--- /dev/null
+++ b/generators/lisp/widgets_blocks.js
@@ -0,0 +1,1135 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Widget blocks for Blockly.
+ * @author luppy@appkaki.com (Lee Lup Yuen)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.widgets');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly');
+
+Blockly.Blocks['widgets_defnoreturn'] = {
+  /**
+   * Block for defining a widget with no return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_DEFNORETURN_TOOLTIP'] = 'WIDGETS_DEFNORETURN_TOOLTIP'; ////
+    Blockly.Msg['WIDGETS_DEFNORETURN_HELPURL'] = 'WIDGETS_DEFNORETURN_HELPURL'; ////
+    var nameField = new Blockly.FieldTextInput('',
+        Blockly.Procedures.rename);
+    nameField.setSpellcheck(false);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg['WIDGETS_DEFNORETURN_TITLE'])
+        .appendField('on button') //// TODO
+        .appendField(nameField, 'NAME')
+        .appendField('press') //// TODO
+        .appendField('', 'PARAMS');
+    this.setMutator(new Blockly.Mutator(['widgets_mutatorarg']));
+    if ((this.workspace.options.comments ||
+         (this.workspace.options.parentWorkspace &&
+          this.workspace.options.parentWorkspace.options.comments)) &&
+        Blockly.Msg['WIDGETS_DEFNORETURN_COMMENT']) {
+      this.setCommentText(Blockly.Msg['WIDGETS_DEFNORETURN_COMMENT']);
+    }
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    this.setTooltip(Blockly.Msg['WIDGETS_DEFNORETURN_TOOLTIP']);
+    this.setHelpUrl(Blockly.Msg['WIDGETS_DEFNORETURN_HELPURL']);
+    this.arguments_ = ['ctx', 'state', 'env']; //// TODO
+    this.argumentVarModels_ = [];
+    this.setStatements_(true);
+    this.statementConnection_ = null;
+  },
+  /**
+   * Add or remove the statement block from this function definition.
+   * @param {boolean} hasStatements True if a statement block is needed.
+   * @this Blockly.Block
+   */
+  setStatements_: function(hasStatements) {
+    Blockly.Msg['WIDGETS_DEFNORETURN_DO'] = ''; ////
+    if (this.hasStatements_ === hasStatements) {
+      return;
+    }
+    if (hasStatements) {
+      this.appendStatementInput('STACK')
+          .appendField(Blockly.Msg['WIDGETS_DEFNORETURN_DO']);
+      if (this.getInput('RETURN')) {
+        this.moveInputBefore('STACK', 'RETURN');
+      }
+    } else {
+      this.removeInput('STACK', true);
+    }
+    this.hasStatements_ = hasStatements;
+  },
+  /**
+   * Update the display of parameters for this widget definition block.
+   * @private
+   * @this Blockly.Block
+   */
+  updateParams_: function() {
+    Blockly.Msg['WIDGETS_BEFORE_PARAMS'] = 'WIDGETS_BEFORE_PARAMS'; ////
+    // Merge the arguments into a human-readable list.
+    var paramString = '';
+    if (this.arguments_.length) {
+      paramString = Blockly.Msg['WIDGETS_BEFORE_PARAMS'] +
+          ' ' + this.arguments_.join(', ');
+    }
+    // The params field is deterministic based on the mutation,
+    // no need to fire a change event.
+    Blockly.Events.disable();
+    try {
+      this.setFieldValue(paramString, 'PARAMS');
+    } finally {
+      Blockly.Events.enable();
+    }
+  },
+  /**
+   * Create XML to represent the argument inputs.
+   * @param {boolean=} opt_paramIds If true include the IDs of the parameter
+   *     quarks.  Used by Blockly.Procedures.mutateCallers for reconnection.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function(opt_paramIds) {
+    var container = document.createElement('mutation');
+    if (opt_paramIds) {
+      container.setAttribute('name', this.getFieldValue('NAME'));
+    }
+    for (var i = 0; i < this.argumentVarModels_.length; i++) {
+      var parameter = document.createElement('arg');
+      var argModel = this.argumentVarModels_[i];
+      parameter.setAttribute('name', argModel.name);
+      parameter.setAttribute('varid', argModel.getId());
+      if (opt_paramIds && this.paramIds_) {
+        parameter.setAttribute('paramId', this.paramIds_[i]);
+      }
+      container.appendChild(parameter);
+    }
+
+    // Save whether the statement input is visible.
+    if (!this.hasStatements_) {
+      container.setAttribute('statements', 'false');
+    }
+    return container;
+  },
+  /**
+   * Parse XML to restore the argument inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.arguments_ = [];
+    this.argumentVarModels_ = [];
+    for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
+      if (childNode.nodeName.toLowerCase() == 'arg') {
+        var varName = childNode.getAttribute('name');
+        var varId = childNode.getAttribute('varid') || childNode.getAttribute('varId');
+        this.arguments_.push(varName);
+        var variable = Blockly.Variables.getOrCreateVariablePackage(
+            this.workspace, varId, varName, '');
+        if (variable != null) {
+          this.argumentVarModels_.push(variable);
+        } else {
+          console.log('Failed to create a variable with name ' + varName + ', ignoring.');
+        }
+      }
+    }
+    this.updateParams_();
+    Blockly.Procedures.mutateCallers(this);
+
+    // Show or hide the statement input.
+    this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = workspace.newBlock('widgets_mutatorcontainer');
+    containerBlock.initSvg();
+
+    // Check/uncheck the allow statement box.
+    if (this.getInput('RETURN')) {
+      containerBlock.setFieldValue(
+          this.hasStatements_ ? 'TRUE' : 'FALSE', 'STATEMENTS');
+    } else {
+      containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
+    }
+
+    // Parameter list.
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var paramBlock = workspace.newBlock('widgets_mutatorarg');
+      paramBlock.initSvg();
+      paramBlock.setFieldValue(this.arguments_[i], 'NAME');
+      // Store the old location.
+      paramBlock.oldLocation = i;
+      connection.connect(paramBlock.previousConnection);
+      connection = paramBlock.nextConnection;
+    }
+    // Initialize widget's callers with blank IDs.
+    Blockly.Procedures.mutateCallers(this);
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    // Parameter list.
+    this.arguments_ = [];
+    this.paramIds_ = [];
+    this.argumentVarModels_ = [];
+    var paramBlock = containerBlock.getInputTargetBlock('STACK');
+    while (paramBlock) {
+      var varName = paramBlock.getFieldValue('NAME');
+      this.arguments_.push(varName);
+      var variable = this.workspace.getVariable(varName, '');
+      if (variable != null) {
+        this.argumentVarModels_.push(variable);
+      } else {
+        console.log('Failed to get variable named ' + varName + ', ignoring.');
+      }
+
+      this.paramIds_.push(paramBlock.id);
+      paramBlock = paramBlock.nextConnection &&
+          paramBlock.nextConnection.targetBlock();
+    }
+    this.updateParams_();
+    Blockly.Procedures.mutateCallers(this);
+
+    // Show/hide the statement input.
+    var hasStatements = containerBlock.getFieldValue('STATEMENTS');
+    if (hasStatements !== null) {
+      hasStatements = hasStatements == 'TRUE';
+      if (this.hasStatements_ != hasStatements) {
+        if (hasStatements) {
+          this.setStatements_(true);
+          // Restore the stack, if one was saved.
+          Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
+          this.statementConnection_ = null;
+        } else {
+          // Save the stack, then disconnect it.
+          var stackConnection = this.getInput('STACK').connection;
+          this.statementConnection_ = stackConnection.targetConnection;
+          if (this.statementConnection_) {
+            var stackBlock = stackConnection.targetBlock();
+            stackBlock.unplug();
+            stackBlock.bumpNeighbours_();
+          }
+          this.setStatements_(false);
+        }
+      }
+    }
+  },
+  /**
+   * Return the signature of this widget definition.
+   * @return {!Array} Tuple containing three elements:
+   *     - the name of the defined widget,
+   *     - a list of all its arguments,
+   *     - that it DOES NOT have a return value.
+   * @this Blockly.Block
+   */
+  getProcedureDef: function() {
+    return [this.getFieldValue('NAME'), this.arguments_, false];
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return this.arguments_;
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.} List of variable models.
+   * @this Blockly.Block
+   */
+  getVarModels: function() {
+    return this.argumentVarModels_;
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the ID matches one of this block's variables, rename it.
+   * @param {string} oldId ID of variable to rename.
+   * @param {string} newId ID of new variable.  May be the same as oldId, but
+   *     with an updated name.  Guaranteed to be the same type as the old
+   *     variable.
+   * @override
+   * @this Blockly.Block
+   */
+  renameVarById: function(oldId, newId) {
+    var oldVariable = this.workspace.getVariableById(oldId);
+    if (oldVariable.type != '') {
+      // Procedure arguments always have the empty type.
+      return;
+    }
+    var oldName = oldVariable.name;
+    var newVar = this.workspace.getVariableById(newId);
+
+    var change = false;
+    for (var i = 0; i < this.argumentVarModels_.length; i++) {
+      if (this.argumentVarModels_[i].getId() == oldId) {
+        this.arguments_[i] = newVar.name;
+        this.argumentVarModels_[i] = newVar;
+        change = true;
+      }
+    }
+    if (change) {
+      this.displayRenamedVar_(oldName, newVar.name);
+      Blockly.Procedures.mutateCallers(this);
+    }
+  },
+  /**
+   * Notification that a variable is renaming but keeping the same ID.  If the
+   * variable is in use on this block, rerender to show the new name.
+   * @param {!Blockly.VariableModel} variable The variable being renamed.
+   * @package
+   * @override
+   * @this Blockly.Block
+   */
+  updateVarName: function(variable) {
+    var newName = variable.name;
+    var change = false;
+    for (var i = 0; i < this.argumentVarModels_.length; i++) {
+      if (this.argumentVarModels_[i].getId() == variable.getId()) {
+        var oldName = this.arguments_[i];
+        this.arguments_[i] = newName;
+        change = true;
+      }
+    }
+    if (change) {
+      this.displayRenamedVar_(oldName, newName);
+      Blockly.Procedures.mutateCallers(this);
+    }
+  },
+  /**
+   * Update the display to reflect a newly renamed argument.
+   * @param {string} oldName The old display name of the argument.
+   * @param {string} newName The new display name of the argument.
+   * @private
+   */
+  displayRenamedVar_: function(oldName, newName) {
+    this.updateParams_();
+    // Update the mutator's variables if the mutator is open.
+    if (this.mutator.isVisible()) {
+      var blocks = this.mutator.workspace_.getAllBlocks(false);
+      for (var i = 0, block; block = blocks[i]; i++) {
+        if (block.type == 'widgets_mutatorarg' &&
+            Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
+          block.setFieldValue(newName, 'NAME');
+        }
+      }
+    }
+  },
+  /**
+   * Add custom menu options to this block's context menu.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    Blockly.Msg['WIDGETS_CREATE_DO'] = 'WIDGETS_CREATE_DO';  ////
+    if (this.isInFlyout){
+      return;
+    }
+    // Add option to create caller.
+    var option = {enabled: true};
+    var name = this.getFieldValue('NAME');
+    option.text = Blockly.Msg['WIDGETS_CREATE_DO'].replace('%1', name);
+    var xmlMutation = document.createElement('mutation');
+    xmlMutation.setAttribute('name', name);
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var xmlArg = document.createElement('arg');
+      xmlArg.setAttribute('name', this.arguments_[i]);
+      xmlMutation.appendChild(xmlArg);
+    }
+    var xmlBlock = document.createElement('block');
+    xmlBlock.setAttribute('type', this.callType_);
+    xmlBlock.appendChild(xmlMutation);
+    option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+    options.push(option);
+
+    // Add options to create getters for each parameter.
+    if (!this.isCollapsed()) {
+      for (var i = 0; i < this.argumentVarModels_.length; i++) {
+        var option = {enabled: true};
+        var argVar = this.argumentVarModels_[i];
+        var name = argVar.name;
+        option.text = Blockly.Msg['VARIABLES_SET_CREATE_GET'].replace('%1', name);
+
+        var xmlField = Blockly.Variables.generateVariableFieldDom(argVar);
+        var xmlBlock = document.createElement('block');
+        xmlBlock.setAttribute('type', 'variables_get');
+        xmlBlock.appendChild(xmlField);
+        option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+        options.push(option);
+      }
+    }
+  },
+  callType_: 'widgets_callnoreturn'
+};
+
+Blockly.Blocks['widgets_defreturn'] = {
+  /**
+   * Block for defining a widget with a return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_DEFRETURN_TITLE'] = ''; ////
+    Blockly.Msg['WIDGETS_DEFRETURN_RETURN'] = 'return'; ////
+    Blockly.Msg['WIDGETS_DEFRETURN_COMMENT'] = ''; ////
+    Blockly.Msg['WIDGETS_DEFRETURN_TOOLTIP'] = 'WIDGETS_DEFRETURN_TOOLTIP'; ////
+    Blockly.Msg['WIDGETS_DEFRETURN_HELPURL'] = 'WIDGETS_DEFRETURN_HELPURL'; ////
+    var nameField = new Blockly.FieldTextInput('',
+        Blockly.Procedures.rename);
+    nameField.setSpellcheck(false);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg['WIDGETS_DEFRETURN_TITLE'])
+        .appendField('on label') //// TODO
+        .appendField(nameField, 'NAME')
+        .appendField('show') //// TODO
+        .appendField('', 'PARAMS');
+    this.appendValueInput('RETURN')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg['WIDGETS_DEFRETURN_RETURN']);
+    this.setMutator(new Blockly.Mutator(['widgets_mutatorarg']));
+    if ((this.workspace.options.comments ||
+         (this.workspace.options.parentWorkspace &&
+          this.workspace.options.parentWorkspace.options.comments)) &&
+        Blockly.Msg['WIDGETS_DEFRETURN_COMMENT']) {
+      this.setCommentText(Blockly.Msg['WIDGETS_DEFRETURN_COMMENT']);
+    }
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    this.setTooltip(Blockly.Msg['WIDGETS_DEFRETURN_TOOLTIP']);
+    this.setHelpUrl(Blockly.Msg['WIDGETS_DEFRETURN_HELPURL']);
+    this.arguments_ = ['state', 'env']; //// TODO
+    this.argumentVarModels_ = [];
+    this.setStatements_(true);
+    this.statementConnection_ = null;
+  },
+  setStatements_: Blockly.Blocks['widgets_defnoreturn'].setStatements_,
+  updateParams_: Blockly.Blocks['widgets_defnoreturn'].updateParams_,
+  mutationToDom: Blockly.Blocks['widgets_defnoreturn'].mutationToDom,
+  domToMutation: Blockly.Blocks['widgets_defnoreturn'].domToMutation,
+  decompose: Blockly.Blocks['widgets_defnoreturn'].decompose,
+  compose: Blockly.Blocks['widgets_defnoreturn'].compose,
+  /**
+   * Return the signature of this widget definition.
+   * @return {!Array} Tuple containing three elements:
+   *     - the name of the defined widget,
+   *     - a list of all its arguments,
+   *     - that it DOES have a return value.
+   * @this Blockly.Block
+   */
+  getProcedureDef: function() {
+    return [this.getFieldValue('NAME'), this.arguments_, true];
+  },
+  getVars: Blockly.Blocks['widgets_defnoreturn'].getVars,
+  getVarModels: Blockly.Blocks['widgets_defnoreturn'].getVarModels,
+  renameVarById: Blockly.Blocks['widgets_defnoreturn'].renameVarById,
+  updateVarName: Blockly.Blocks['widgets_defnoreturn'].updateVarName,
+  displayRenamedVar_: Blockly.Blocks['widgets_defnoreturn'].displayRenamedVar_,
+  customContextMenu: Blockly.Blocks['widgets_defnoreturn'].customContextMenu,
+  callType_: 'widgets_callreturn'
+};
+
+Blockly.Blocks['widgets_mutatorcontainer'] = {
+  /**
+   * Mutator block for widget container.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_MUTATORCONTAINER_TITLE'] = 'WIDGETS_MUTATORCONTAINER_TITLE'; ////
+    Blockly.Msg['WIDGETS_ALLOW_STATEMENTS'] = 'WIDGETS_ALLOW_STATEMENTS'; ////
+    Blockly.Msg['WIDGETS_MUTATORCONTAINER_TOOLTIP'] = 'WIDGETS_MUTATORCONTAINER_TOOLTIP'; ////
+    this.appendDummyInput()
+        .appendField(Blockly.Msg['WIDGETS_MUTATORCONTAINER_TITLE']);
+    this.appendStatementInput('STACK');
+    this.appendDummyInput('STATEMENT_INPUT')
+        .appendField(Blockly.Msg['WIDGETS_ALLOW_STATEMENTS'])
+        .appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    this.setTooltip(Blockly.Msg['WIDGETS_MUTATORCONTAINER_TOOLTIP']);
+    this.contextMenu = false;
+  },
+  /**
+   * This will create & delete variables and in dialogs workspace to ensure
+   * that when a new block is dragged out it will have a unique parameter name.
+   * @param {!Blockly.Events.Abstract} event Change event.
+   * @this Blockly.Block
+   */
+  onchange: function(event) {
+    if (!this.workspace || this.workspace.isFlyout ||
+        (event.type != Blockly.Events.BLOCK_DELETE && event.type != Blockly.Events.BLOCK_CREATE)) {
+      return;
+    }
+    var blocks = this.workspace.getAllBlocks();
+    var allVariables = this.workspace.getAllVariables();
+    if (event.type == Blockly.Events.BLOCK_DELETE) {
+      var variableNamesToKeep = [];
+      for (var i = 0; i < blocks.length; i += 1) {
+        if (blocks[i].getFieldValue('NAME')) {
+          variableNamesToKeep.push(blocks[i].getFieldValue('NAME'));
+        }
+      }
+      for (var k = 0; k < allVariables.length; k += 1) {
+        if (variableNamesToKeep.indexOf(allVariables[k].name) == -1) {
+          this.workspace.deleteVariableById(allVariables[k].getId());
+        }
+      }
+      return;
+    }
+      
+    if (event.type != Blockly.Events.BLOCK_CREATE) {
+      return;
+    }
+
+    var block = this.workspace.getBlockById(event.blockId);
+    // This is to handle the one none variable block
+    // Happens when all the blocks are regenerated
+    if (!block.getField('NAME')) {
+      return;
+    }
+    var varName = block.getFieldValue('NAME');
+    var variable = this.workspace.getVariable(varName);
+
+    if (!variable) {
+      // This means the parameter name is not in use and we can create the variable.
+      variable = this.workspace.createVariable(varName);
+    }
+    // If the blocks are connected we don't have to check duplicate variables
+    // This only happens if the dialog box is open
+    if (block.previousConnection.isConnected() || block.nextConnection.isConnected()) {
+      return;
+    }
+
+    for (var j = 0; j < blocks.length; j += 1) {
+      // filter block that was created
+      if (block.id != blocks[j].id && blocks[j].getFieldValue('NAME') == variable.name) {
+        // generate new name and set name field
+        varName = Blockly.Variables.generateUniqueName(this.workspace);
+        variable = this.workspace.createVariable(varName);
+        block.setFieldValue(variable.name, 'NAME');
+        return;
+      }
+    }
+  }
+};
+
+
+Blockly.Blocks['widgets_mutatorarg'] = {
+  /**
+   * Mutator block for widget argument.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_MUTATORARG_TITLE'] = 'WIDGETS_MUTATORARG_TITLE'; ////
+    Blockly.Msg['WIDGETS_MUTATORARG_TOOLTIP'] = 'WIDGETS_MUTATORARG_TOOLTIP'; ////
+    var field = new Blockly.FieldTextInput('x', this.validator_);
+    // Hack: override showEditor to do just a little bit more work.
+    // We don't have a good place to hook into the start of a text edit.
+    field.oldShowEditorFn_ = field.showEditor_;
+    var newShowEditorFn = function() {
+      this.createdVariables_ = [];
+      this.oldShowEditorFn_();
+    };
+    field.showEditor_ = newShowEditorFn;
+
+    this.appendDummyInput()
+        .appendField(Blockly.Msg['WIDGETS_MUTATORARG_TITLE'])
+        .appendField(field, 'NAME');
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    this.setTooltip(Blockly.Msg['WIDGETS_MUTATORARG_TOOLTIP']);
+    this.contextMenu = false;
+
+    // Create the default variable when we drag the block in from the flyout.
+    // Have to do this after installing the field on the block.
+    field.onFinishEditing_ = this.deleteIntermediateVars_;
+    // Create an empty list so onFinishEditing_ has something to look at, even
+    // though the editor was never opened.
+    field.createdVariables_ = [];
+    field.onFinishEditing_('x');
+  },
+
+  /**
+   * Obtain a valid name for the widget argument. Create a variable if
+   * necessary.
+   * Merge runs of whitespace.  Strip leading and trailing whitespace.
+   * Beyond this, all names are legal.
+   * @param {string} varName User-supplied name.
+   * @return {?string} Valid name, or null if a name was not specified.
+   * @private
+   * @this Blockly.FieldTextInput
+   */
+  validator_: function(varName) {
+    var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace);
+    varName = varName.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
+    if (!varName) {
+      return null;
+    }
+    // Prevents duplicate parameter names in functions
+    var blocks = this.sourceBlock_.workspace.getAllBlocks();
+    for (var i = 0; i < blocks.length; i += 1) {
+      if (blocks[i].id == this.sourceBlock_.id) {
+        continue;
+      }
+      if (blocks[i].getFieldValue('NAME') == varName) {
+        return null;
+      }
+    }
+    var model = outerWs.getVariable(varName, '');
+    if (model && model.name != varName) {
+      // Rename the variable (case change)
+      outerWs.renameVarById(model.getId(), varName);
+    }
+    if (!model) {
+      model = outerWs.createVariable(varName, '');
+      if (model && this.createdVariables_) {
+        this.createdVariables_.push(model);
+      }
+    }
+    return varName;
+  },
+  /**
+   * Called when focusing away from the text field.
+   * Deletes all variables that were created as the user typed their intended
+   * variable name.
+   * @param {string} newText The new variable name.
+   * @private
+   * @this Blockly.FieldTextInput
+   */
+  deleteIntermediateVars_: function(newText) {
+    var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace);
+    if (!outerWs) {
+      return;
+    }
+    for (var i = 0; i < this.createdVariables_.length; i++) {
+      var model = this.createdVariables_[i];
+      if (model.name != newText) {
+        outerWs.deleteVariableById(model.getId());
+      }
+    }
+  }
+};
+
+Blockly.Blocks['widgets_callnoreturn'] = {
+  /**
+   * Block for calling a widget with no return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_CALLNORETURN_HELPURL'] = 'https://WIDGETS_CALLNORETURN_HELPURL'; ////
+    this.appendDummyInput('TOPROW')
+        .appendField(this.id, 'NAME');
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    // Tooltip is set in renameProcedure.
+    this.setHelpUrl(Blockly.Msg['WIDGETS_CALLNORETURN_HELPURL']);
+    this.arguments_ = [];
+    this.argumentVarModels_ = [];
+    this.quarkConnections_ = {};
+    this.quarkIds_ = null;
+    this.previousDisabledState_ = false;
+  },
+
+  /**
+   * Returns the name of the widget this block calls.
+   * @return {string} Procedure name.
+   * @this Blockly.Block
+   */
+  getProcedureCall: function() {
+    // The NAME field is guaranteed to exist, null will never be returned.
+    return /** @type {string} */ (this.getFieldValue('NAME'));
+  },
+  /**
+   * Notification that a widget is renaming.
+   * If the name matches this block's widget, rename it.
+   * @param {string} oldName Previous name of widget.
+   * @param {string} newName Renamed widget.
+   * @this Blockly.Block
+   */
+  renameProcedure: function(oldName, newName) {
+    Blockly.Msg['WIDGETS_CALLRETURN_TOOLTIP'] = 'WIDGETS_CALLRETURN_TOOLTIP'; ////
+    Blockly.Msg['WIDGETS_CALLNORETURN_TOOLTIP'] = 'WIDGETS_CALLNORETURN_TOOLTIP'; ////
+    if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
+      this.setFieldValue(newName, 'NAME');
+      var baseMsg = this.outputConnection ?
+          Blockly.Msg['WIDGETS_CALLRETURN_TOOLTIP'] :
+          Blockly.Msg['WIDGETS_CALLNORETURN_TOOLTIP'];
+      this.setTooltip(baseMsg.replace('%1', newName));
+    }
+  },
+  /**
+   * Notification that the widget's parameters have changed.
+   * @param {!Array.} paramNames New param names, e.g. ['x', 'y', 'z'].
+   * @param {!Array.} paramIds IDs of params (consistent for each
+   *     parameter through the life of a mutator, regardless of param renaming),
+   *     e.g. ['piua', 'f8b_', 'oi.o'].
+   * @private
+   * @this Blockly.Block
+   */
+  setProcedureParameters_: function(paramNames, paramIds) {
+    // Data structures:
+    // this.arguments = ['x', 'y']
+    //     Existing param names.
+    // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
+    //     Look-up of paramIds to connections plugged into the call block.
+    // this.quarkIds_ = ['piua', 'f8b_']
+    //     Existing param IDs.
+    // Note that quarkConnections_ may include IDs that no longer exist, but
+    // which might reappear if a param is reattached in the mutator.
+    var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
+        this.workspace);
+    var mutatorOpen = defBlock && defBlock.mutator &&
+        defBlock.mutator.isVisible();
+    if (!mutatorOpen) {
+      this.quarkConnections_ = {};
+      this.quarkIds_ = null;
+    }
+    if (!paramIds) {
+      // Reset the quarks (a mutator is about to open).
+      return;
+    }
+    // Test arguments (arrays of strings) for changes. '\n' is not a valid
+    // argument name character, so it is a valid delimiter here.
+    if (paramNames.join('\n') == this.arguments_.join('\n')) {
+      // No change.
+      this.quarkIds_ = paramIds;
+      return;
+    }
+    if (paramIds.length != paramNames.length) {
+      throw RangeError('paramNames and paramIds must be the same length.');
+    }
+    this.setCollapsed(false);
+    if (!this.quarkIds_) {
+      // Initialize tracking for this block.
+      this.quarkConnections_ = {};
+      this.quarkIds_ = [];
+    }
+    // Switch off rendering while the block is rebuilt.
+    var savedRendered = this.rendered;
+    this.rendered = false;
+    // Update the quarkConnections_ with existing connections.
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var input = this.getInput('ARG' + i);
+      if (input) {
+        var connection = input.connection.targetConnection;
+        this.quarkConnections_[this.quarkIds_[i]] = connection;
+        if (mutatorOpen && connection &&
+            paramIds.indexOf(this.quarkIds_[i]) == -1) {
+          // This connection should no longer be attached to this block.
+          connection.disconnect();
+          connection.getSourceBlock().bumpNeighbours_();
+        }
+      }
+    }
+    // Rebuild the block's arguments.
+    this.arguments_ = [].concat(paramNames);
+    // And rebuild the argument model list.
+    this.argumentVarModels_ = [];
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var variable = Blockly.Variables.getOrCreateVariablePackage(
+          this.workspace, null, this.arguments_[i], '');
+      this.argumentVarModels_.push(variable);
+    }
+
+    this.updateShape_();
+    this.quarkIds_ = paramIds;
+    // Reconnect any child blocks.
+    if (this.quarkIds_) {
+      for (var i = 0; i < this.arguments_.length; i++) {
+        var quarkId = this.quarkIds_[i];
+        if (quarkId in this.quarkConnections_) {
+          var connection = this.quarkConnections_[quarkId];
+          if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
+            // Block no longer exists or has been attached elsewhere.
+            delete this.quarkConnections_[quarkId];
+          }
+        }
+      }
+    }
+    // Restore rendering and show the changes.
+    this.rendered = savedRendered;
+    if (this.rendered) {
+      this.render();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of arguments.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    Blockly.Msg['WIDGETS_CALL_BEFORE_PARAMS'] = 'WIDGETS_CALL_BEFORE_PARAMS';
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var field = this.getField('ARGNAME' + i);
+      if (field) {
+        // Ensure argument name is up to date.
+        // The argument name field is deterministic based on the mutation,
+        // no need to fire a change event.
+        Blockly.Events.disable();
+        try {
+          field.setValue(this.arguments_[i]);
+        } finally {
+          Blockly.Events.enable();
+        }
+      } else {
+        // Add new input.
+        field = new Blockly.FieldLabel(this.arguments_[i]);
+        var input = this.appendValueInput('ARG' + i)
+            .setAlign(Blockly.ALIGN_RIGHT)
+            .appendField(field, 'ARGNAME' + i);
+        input.init();
+      }
+    }
+    // Remove deleted inputs.
+    while (this.getInput('ARG' + i)) {
+      this.removeInput('ARG' + i);
+      i++;
+    }
+    // Add 'with:' if there are parameters, remove otherwise.
+    var topRow = this.getInput('TOPROW');
+    if (topRow) {
+      if (this.arguments_.length) {
+        if (!this.getField('WITH')) {
+          topRow.appendField(Blockly.Msg['WIDGETS_CALL_BEFORE_PARAMS'], 'WITH');
+          topRow.init();
+        }
+      } else {
+        if (this.getField('WITH')) {
+          topRow.removeField('WITH');
+        }
+      }
+    }
+  },
+  /**
+   * Create XML to represent the (non-editable) name and arguments.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('name', this.getProcedureCall());
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var parameter = document.createElement('arg');
+      parameter.setAttribute('name', this.arguments_[i]);
+      container.appendChild(parameter);
+    }
+    return container;
+  },
+  /**
+   * Parse XML to restore the (non-editable) name and parameters.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    var name = xmlElement.getAttribute('name');
+    this.renameProcedure(this.getProcedureCall(), name);
+    var args = [];
+    var paramIds = [];
+    for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
+      if (childNode.nodeName.toLowerCase() == 'arg') {
+        args.push(childNode.getAttribute('name'));
+        paramIds.push(childNode.getAttribute('paramId'));
+      }
+    }
+    this.setProcedureParameters_(args, paramIds);
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.} List of variable models.
+   * @this Blockly.Block
+   */
+  getVarModels: function() {
+    return this.argumentVarModels_;
+  },
+  /**
+   * Procedure calls cannot exist without the corresponding widget
+   * definition.  Enforce this link whenever an event is fired.
+   * @param {!Blockly.Events.Abstract} event Change event.
+   * @this Blockly.Block
+   */
+  onchange: function(event) {
+    if (!this.workspace || this.workspace.isFlyout) {
+      // Block is deleted or is in a flyout.
+      return;
+    }
+    if (!event.recordUndo) {
+      // Events not generated by user. Skip handling.
+      return;
+    }
+    if (event.type == Blockly.Events.BLOCK_CREATE &&
+      event.ids.indexOf(this.id) != -1) {
+      // Look for the case where a widget call was created (usually through
+      // paste) and there is no matching definition.  In this case, create
+      // an empty definition block with the correct signature.
+      var name = this.getProcedureCall();
+      var def = Blockly.Procedures.getDefinition(name, this.workspace);
+      if (def && (def.type != this.defType_ ||
+          JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
+        // The signatures don't match.
+        def = null;
+      }
+      if (!def) {
+        Blockly.Events.setGroup(event.group);
+        /**
+         * Create matching definition block.
+         * 
+         *   
+         *     
+         *       
+         *     
+         *     test
+         *   
+         * 
+         */
+        var xml = document.createElement('xml');
+        var block = document.createElement('block');
+        block.setAttribute('type', this.defType_);
+        var xy = this.getRelativeToSurfaceXY();
+        var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
+        var y = xy.y + Blockly.SNAP_RADIUS * 2;
+        block.setAttribute('x', x);
+        block.setAttribute('y', y);
+        var mutation = this.mutationToDom();
+        block.appendChild(mutation);
+        var field = document.createElement('field');
+        field.setAttribute('name', 'NAME');
+        field.appendChild(document.createTextNode(this.getProcedureCall()));
+        block.appendChild(field);
+        xml.appendChild(block);
+        Blockly.Xml.domToWorkspace(xml, this.workspace);
+        Blockly.Events.setGroup(false);
+      }
+    } else if (event.type == Blockly.Events.BLOCK_DELETE) {
+      // Look for the case where a widget definition has been deleted,
+      // leaving this block (a widget call) orphaned.  In this case, delete
+      // the orphan.
+      var name = this.getProcedureCall();
+      var def = Blockly.Procedures.getDefinition(name, this.workspace);
+      if (!def) {
+        Blockly.Events.setGroup(event.group);
+        this.dispose(true, false);
+        Blockly.Events.setGroup(false);
+      }
+    } else if (event.type == Blockly.Events.CHANGE && event.element == 'disabled') {
+      var name = this.getProcedureCall();
+      var def = Blockly.Procedures.getDefinition(name, this.workspace);
+      if (def && def.id == event.blockId) {
+        // in most cases the old group should be ''
+        var oldGroup = Blockly.Events.getGroup();
+        if (oldGroup) {
+          // This should only be possible programatically and may indicate a problem
+          // with event grouping. If you see this message please investigate. If the
+          // use ends up being valid we may need to reorder events in the undo stack.
+          console.log('Saw an existing group while responding to a definition change');
+        }
+        Blockly.Events.setGroup(event.group);
+        if (event.newValue) {
+          this.previousDisabledState_ = this.disabled;
+          this.setDisabled(true);
+        } else {
+          this.setDisabled(this.previousDisabledState_);
+        }
+        Blockly.Events.setGroup(oldGroup);
+      }
+    }
+  },
+  /**
+   * Add menu option to find the definition block for this call.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    Blockly.Msg['WIDGETS_HIGHLIGHT_DEF'] = 'WIDGETS_HIGHLIGHT_DEF'; ////
+    if (!this.workspace.isMovable()) {
+      // If we center on the block and the workspace isn't movable we could
+      // loose blocks at the edges of the workspace.
+      return;
+    }
+
+    var option = {enabled: true};
+    option.text = Blockly.Msg['WIDGETS_HIGHLIGHT_DEF'];
+    var name = this.getProcedureCall();
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      if (def) {
+        workspace.centerOnBlock(def.id);
+        def.select();
+      }
+    };
+    options.push(option);
+  },
+  defType_: 'widgets_defnoreturn'
+};
+
+Blockly.Blocks['widgets_callreturn'] = {
+  /**
+   * Block for calling a widget with a return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_CALLRETURN_HELPURL'] = 'https://WIDGETS_CALLRETURN_HELPURL'; ////
+    this.appendDummyInput('TOPROW')
+        .appendField('', 'NAME');
+    this.setOutput(true);
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    // Tooltip is set in domToMutation.
+    this.setHelpUrl(Blockly.Msg['WIDGETS_CALLRETURN_HELPURL']);
+    this.arguments_ = [];
+    this.quarkConnections_ = {};
+    this.quarkIds_ = null;
+    this.previousDisabledState_ = false;
+  },
+
+  getProcedureCall: Blockly.Blocks['widgets_callnoreturn'].getProcedureCall,
+  renameProcedure: Blockly.Blocks['widgets_callnoreturn'].renameProcedure,
+  setProcedureParameters_:
+      Blockly.Blocks['widgets_callnoreturn'].setProcedureParameters_,
+  updateShape_: Blockly.Blocks['widgets_callnoreturn'].updateShape_,
+  mutationToDom: Blockly.Blocks['widgets_callnoreturn'].mutationToDom,
+  domToMutation: Blockly.Blocks['widgets_callnoreturn'].domToMutation,
+  getVarModels: Blockly.Blocks['widgets_callnoreturn'].getVarModels,
+  onchange: Blockly.Blocks['widgets_callnoreturn'].onchange,
+  customContextMenu:
+      Blockly.Blocks['widgets_callnoreturn'].customContextMenu,
+  defType_: 'widgets_defreturn'
+};
+
+Blockly.Blocks['widgets_ifreturn'] = {
+  /**
+   * Block for conditionally returning a value from a widget.
+   * @this Blockly.Block
+   */
+  init: function() {
+    Blockly.Msg['WIDGETS_DEFRETURN_RETURN'] = 'return'; ////
+    Blockly.Msg['WIDGETS_IFRETURN_TOOLTIP'] = 'WIDGETS_IFRETURN_TOOLTIP'; ////
+    Blockly.Msg['WIDGETS_IFRETURN_HELPURL'] = 'WIDGETS_IFRETURN_HELPURL'; ////
+    this.appendValueInput('CONDITION')
+        .setCheck('Boolean')
+        .appendField(Blockly.Msg['CONTROLS_IF_MSG_IF']);
+    this.appendValueInput('VALUE')
+        .appendField(Blockly.Msg['WIDGETS_DEFRETURN_RETURN']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    //// TODO: this.setStyle('widget_blocks');
+    this.setStyle('procedure_blocks'); //// TODO
+    this.setTooltip(Blockly.Msg['WIDGETS_IFRETURN_TOOLTIP']);
+    this.setHelpUrl(Blockly.Msg['WIDGETS_IFRETURN_HELPURL']);
+    this.hasReturnValue_ = true;
+  },
+  /**
+   * Create XML to represent whether this block has a return value.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('value', Number(this.hasReturnValue_));
+    return container;
+  },
+  /**
+   * Parse XML to restore whether this block has a return value.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    Blockly.Msg['WIDGETS_DEFRETURN_RETURN'] = 'return'; ////
+    var value = xmlElement.getAttribute('value');
+    this.hasReturnValue_ = (value == 1);
+    if (!this.hasReturnValue_) {
+      this.removeInput('VALUE');
+      this.appendDummyInput('VALUE')
+          .appendField(Blockly.Msg['WIDGETS_DEFRETURN_RETURN']);
+    }
+  },
+  /**
+   * Called whenever anything on the workspace changes.
+   * Add warning if this flow block is not nested inside a loop.
+   * @param {!Blockly.Events.Abstract} e Change event.
+   * @this Blockly.Block
+   */
+  onchange: function(/* e */) {
+    Blockly.Msg['WIDGETS_DEFRETURN_RETURN'] = 'return'; ////
+    Blockly.Msg['WIDGETS_IFRETURN_WARNING'] = 'WIDGETS_IFRETURN_WARNING'; ////
+    if (!this.workspace.isDragging || this.workspace.isDragging()) {
+      return;  // Don't change state at the start of a drag.
+    }
+    var legal = false;
+    // Is the block nested in a widget?
+    var block = this;
+    do {
+      if (this.FUNCTION_TYPES.indexOf(block.type) != -1) {
+        legal = true;
+        break;
+      }
+      block = block.getSurroundParent();
+    } while (block);
+    if (legal) {
+      // If needed, toggle whether this block has a return value.
+      if (block.type == 'widgets_defnoreturn' && this.hasReturnValue_) {
+        this.removeInput('VALUE');
+        this.appendDummyInput('VALUE')
+            .appendField(Blockly.Msg['WIDGETS_DEFRETURN_RETURN']);
+        this.hasReturnValue_ = false;
+      } else if (block.type == 'widgets_defreturn' &&
+                 !this.hasReturnValue_) {
+        this.removeInput('VALUE');
+        this.appendValueInput('VALUE')
+            .appendField(Blockly.Msg['WIDGETS_DEFRETURN_RETURN']);
+        this.hasReturnValue_ = true;
+      }
+      this.setWarningText(null);
+      if (!this.isInFlyout) {
+        this.setDisabled(false);
+      }
+    } else {
+      this.setWarningText(Blockly.Msg['WIDGETS_IFRETURN_WARNING']);
+      if (!this.isInFlyout && !this.getInheritedDisabled()) {
+        this.setDisabled(true);
+      }
+    }
+  },
+  /**
+   * List of block types that are functions and thus do not need warnings.
+   * To add a new function type add this to your code:
+   * Blockly.Blocks['widgets_ifreturn'].FUNCTION_TYPES.push('custom_func');
+   */
+  FUNCTION_TYPES: ['widgets_defnoreturn', 'widgets_defreturn']
+};
diff --git a/generators/lisp/widgets_category.js b/generators/lisp/widgets_category.js
new file mode 100644
index 00000000000..9aca0bf3a9c
--- /dev/null
+++ b/generators/lisp/widgets_category.js
@@ -0,0 +1,323 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Utility functions for handling widgets.
+ * @author luppy@appkaki.com (Lee Lup Yuen)
+ */
+'use strict';
+
+/**
+ * @name Blockly.Widgets
+ * @namespace
+ */
+goog.provide('Blockly.Widgets');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly.constants');
+goog.require('Blockly.Events.BlockChange');
+goog.require('Blockly.Field');
+goog.require('Blockly.Names');
+goog.require('Blockly.Workspace');
+goog.require('Blockly.Xml');
+////goog.require('Blockly.Xml.utils');
+
+
+/**
+ * Constant to separate widget names from variables and generated functions
+ * when running generators.
+ * @deprecated Use Blockly.WIDGET_CATEGORY_NAME
+ */
+Blockly.Widgets.NAME_TYPE = Blockly.WIDGET_CATEGORY_NAME;
+
+/**
+ * Find all user-created widget definitions in a workspace.
+ * @param {!Blockly.Workspace} root Root workspace.
+ * @return {!Array.>} Pair of arrays, the
+ *     first contains widgets without return variables, the second with.
+ *     Each widget is defined by a three-element list of name, parameter
+ *     list, and return value boolean.
+ */
+Blockly.Widgets.allWidgets = function(root) {
+  var blocks = root.getAllBlocks(false);
+  var widgetsReturn = [];
+  var widgetsNoReturn = [];
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getWidgetDef) {
+      var tuple = blocks[i].getWidgetDef();
+      if (tuple) {
+        if (tuple[2]) {
+          widgetsReturn.push(tuple);
+        } else {
+          widgetsNoReturn.push(tuple);
+        }
+      }
+    }
+  }
+  widgetsNoReturn.sort(Blockly.Widgets.procTupleComparator_);
+  widgetsReturn.sort(Blockly.Widgets.procTupleComparator_);
+  return [widgetsNoReturn, widgetsReturn];
+};
+
+/**
+ * Comparison function for case-insensitive sorting of the first element of
+ * a tuple.
+ * @param {!Array} ta First tuple.
+ * @param {!Array} tb Second tuple.
+ * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
+ * @private
+ */
+Blockly.Widgets.procTupleComparator_ = function(ta, tb) {
+  return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
+};
+
+/**
+ * Ensure two identically-named widgets don't exist.
+ * @param {string} name Proposed widget name.
+ * @param {!Blockly.Block} block Block to disambiguate.
+ * @return {string} Non-colliding name.
+ */
+Blockly.Widgets.findLegalName = function(name, block) {
+  if (block.isInFlyout) {
+    // Flyouts can have multiple widgets called 'do something'.
+    return name;
+  }
+  while (!Blockly.Widgets.isLegalName_(name, block.workspace, block)) {
+    // Collision with another widget.
+    var r = name.match(/^(.*?)(\d+)$/);
+    if (!r) {
+      name += '2';
+    } else {
+      name = r[1] + (parseInt(r[2], 10) + 1);
+    }
+  }
+  return name;
+};
+
+/**
+ * Does this widget have a legal name?  Illegal names include names of
+ * widgets already defined.
+ * @param {string} name The questionable name.
+ * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
+ * @param {Blockly.Block=} opt_exclude Optional block to exclude from
+ *     comparisons (one doesn't want to collide with oneself).
+ * @return {boolean} True if the name is legal.
+ * @private
+ */
+Blockly.Widgets.isLegalName_ = function(name, workspace, opt_exclude) {
+  return !Blockly.Widgets.isNameUsed(name, workspace, opt_exclude);
+};
+
+/**
+ * Return if the given name is already a widget name.
+ * @param {string} name The questionable name.
+ * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
+ * @param {Blockly.Block=} opt_exclude Optional block to exclude from
+ *     comparisons (one doesn't want to collide with oneself).
+ * @return {boolean} True if the name is used, otherwise return false.
+ */
+Blockly.Widgets.isNameUsed = function(name, workspace, opt_exclude) {
+  var blocks = workspace.getAllBlocks(false);
+  // Iterate through every block and check the name.
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i] == opt_exclude) {
+      continue;
+    }
+    if (blocks[i].getWidgetDef) {
+      var procName = blocks[i].getWidgetDef();
+      if (Blockly.Names.equals(procName[0], name)) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
+
+/**
+ * Rename a widget.  Called by the editable field.
+ * @param {string} name The proposed new name.
+ * @return {string} The accepted name.
+ * @this {Blockly.Field}
+ */
+Blockly.Widgets.rename = function(name) {
+  // Strip leading and trailing whitespace.  Beyond this, all names are legal.
+  name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+
+  // Ensure two identically-named widgets don't exist.
+  var legalName = Blockly.Widgets.findLegalName(name, this.sourceBlock_);
+  var oldName = this.text_;
+  if (oldName != name && oldName != legalName) {
+    // Rename any callers.
+    var blocks = this.sourceBlock_.workspace.getAllBlocks(false);
+    for (var i = 0; i < blocks.length; i++) {
+      if (blocks[i].renameWidget) {
+        blocks[i].renameWidget(oldName, legalName);
+      }
+    }
+  }
+  return legalName;
+};
+
+/**
+ * Construct the blocks required by the flyout for the widget category.
+ * @param {!Blockly.Workspace} workspace The workspace containing widgets.
+ * @return {!Array.} Array of XML block elements.
+ */
+Blockly.Widgets.flyoutCategory = function(workspace) {
+  var xmlList = [];
+  if (Blockly.Blocks['widgets_defnoreturn']) {
+    // 
+    //     do something
+    // 
+    var block = Blockly.Xml.utils.createElement('block');
+    block.setAttribute('type', 'widgets_defnoreturn');
+    block.setAttribute('gap', 16);
+    var nameField = Blockly.Xml.utils.createElement('field');
+    nameField.setAttribute('name', 'NAME');
+    nameField.appendChild(Blockly.Xml.utils.createTextNode(
+        Blockly.Msg['WIDGETS_DEFNORETURN_WIDGET']));
+    block.appendChild(nameField);
+    xmlList.push(block);
+  }
+  if (Blockly.Blocks['widgets_defreturn']) {
+    // 
+    //     do something
+    // 
+    var block = Blockly.Xml.utils.createElement('block');
+    block.setAttribute('type', 'widgets_defreturn');
+    block.setAttribute('gap', 16);
+    var nameField = Blockly.Xml.utils.createElement('field');
+    nameField.setAttribute('name', 'NAME');
+    nameField.appendChild(Blockly.Xml.utils.createTextNode(
+        Blockly.Msg['WIDGETS_DEFRETURN_WIDGET']));
+    block.appendChild(nameField);
+    xmlList.push(block);
+  }
+  if (Blockly.Blocks['widgets_ifreturn']) {
+    // 
+    var block = Blockly.Xml.utils.createElement('block');
+    block.setAttribute('type', 'widgets_ifreturn');
+    block.setAttribute('gap', 16);
+    xmlList.push(block);
+  }
+  if (xmlList.length) {
+    // Add slightly larger gap between system blocks and user calls.
+    xmlList[xmlList.length - 1].setAttribute('gap', 24);
+  }
+
+  function populateWidgets(widgetList, templateName) {
+    for (var i = 0; i < widgetList.length; i++) {
+      var name = widgetList[i][0];
+      var args = widgetList[i][1];
+      // 
+      //   
+      //     
+      //   
+      // 
+      var block = Blockly.Xml.utils.createElement('block');
+      block.setAttribute('type', templateName);
+      block.setAttribute('gap', 16);
+      var mutation = Blockly.Xml.utils.createElement('mutation');
+      mutation.setAttribute('name', name);
+      block.appendChild(mutation);
+      for (var j = 0; j < args.length; j++) {
+        var arg = Blockly.Xml.utils.createElement('arg');
+        arg.setAttribute('name', args[j]);
+        mutation.appendChild(arg);
+      }
+      xmlList.push(block);
+    }
+  }
+
+  var tuple = Blockly.Widgets.allWidgets(workspace);
+  populateWidgets(tuple[0], 'widgets_callnoreturn');
+  populateWidgets(tuple[1], 'widgets_callreturn');
+  return xmlList;
+};
+
+/**
+ * Find all the callers of a named widget.
+ * @param {string} name Name of widget.
+ * @param {!Blockly.Workspace} workspace The workspace to find callers in.
+ * @return {!Array.} Array of caller blocks.
+ */
+Blockly.Widgets.getCallers = function(name, workspace) {
+  var callers = [];
+  var blocks = workspace.getAllBlocks(false);
+  // Iterate through every block and check the name.
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getWidgetCall) {
+      var procName = blocks[i].getWidgetCall();
+      // Widget name may be null if the block is only half-built.
+      if (procName && Blockly.Names.equals(procName, name)) {
+        callers.push(blocks[i]);
+      }
+    }
+  }
+  return callers;
+};
+
+/**
+ * When a widget definition changes its parameters, find and edit all its
+ * callers.
+ * @param {!Blockly.Block} defBlock Widget definition block.
+ */
+Blockly.Widgets.mutateCallers = function(defBlock) {
+  var oldRecordUndo = Blockly.Events.recordUndo;
+  var name = defBlock.getWidgetDef()[0];
+  var xmlElement = defBlock.mutationToDom(true);
+  var callers = Blockly.Widgets.getCallers(name, defBlock.workspace);
+  for (var i = 0, caller; caller = callers[i]; i++) {
+    var oldMutationDom = caller.mutationToDom();
+    var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
+    caller.domToMutation(xmlElement);
+    var newMutationDom = caller.mutationToDom();
+    var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
+    if (oldMutation != newMutation) {
+      // Fire a mutation on every caller block.  But don't record this as an
+      // undo action since it is deterministically tied to the widget's
+      // definition mutation.
+      Blockly.Events.recordUndo = false;
+      Blockly.Events.fire(new Blockly.Events.BlockChange(
+          caller, 'mutation', null, oldMutation, newMutation));
+      Blockly.Events.recordUndo = oldRecordUndo;
+    }
+  }
+};
+
+/**
+ * Find the definition block for the named widget.
+ * @param {string} name Name of widget.
+ * @param {!Blockly.Workspace} workspace The workspace to search.
+ * @return {Blockly.Block} The widget definition block, or null not found.
+ */
+Blockly.Widgets.getDefinition = function(name, workspace) {
+  // Assume that a widget definition is a top block.
+  var blocks = workspace.getTopBlocks(false);
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getWidgetDef) {
+      var tuple = blocks[i].getWidgetDef();
+      if (tuple && Blockly.Names.equals(tuple[0], name)) {
+        return blocks[i];
+      }
+    }
+  }
+  return null;
+};
diff --git a/generators/lisp/widgets_code.js b/generators/lisp/widgets_code.js
new file mode 100644
index 00000000000..9be47514a9a
--- /dev/null
+++ b/generators/lisp/widgets_code.js
@@ -0,0 +1,169 @@
+/**
+ * @license
+ * Visual Blocks Language
+ *
+ * Copyright 2014 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Generating Lisp for Widget blocks.
+ * @author luppy@appkaki.com (Lee Lup Yuen)
+ */
+'use strict';
+
+goog.provide('Blockly.Lisp.widgets');
+
+goog.require('Blockly.Lisp');
+
+Blockly.Lisp['widgets_defreturn'] = function(block) {
+  // Define a Widget event with a return value.
+  var widgetName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('NAME'),
+    Blockly.Procedures.NAME_TYPE);  //  e.g. my_label
+
+  var branch = Blockly.Lisp.statementToCode(block, 'STACK');
+  if (Blockly.Lisp.STATEMENT_PREFIX) {
+    var id = block.id.replace(/\$/g, '$$$$');  // Issue 251.
+    branch = Blockly.Lisp.prefixLines(
+        Blockly.Lisp.STATEMENT_PREFIX.replace(/%1/g,
+        '\'' + id + '\''), Blockly.Lisp.INDENT) + branch;
+  }
+  if (Blockly.Lisp.INFINITE_LOOP_TRAP) {
+    branch = Blockly.Lisp.INFINITE_LOOP_TRAP.replace(/%1/g,
+        '\'' + block.id + '\'') + branch;
+  }
+
+  //  Get the return value and return type
+  var returnValue = Blockly.Lisp.valueToCode(block, 'RETURN',
+      Blockly.Lisp.ORDER_NONE) || '';
+  var returnType = returnValue ? 'dynamic' : 'void';
+
+  //  Get the event name, event description and return type
+  var eventName = returnValue ? 'show' : 'press';  //  TODO
+  var desc = '';
+  switch(eventName) {  //  TODO
+    case 'show':
+      returnType = 'ArgValue';
+      desc = '/// Callback function that will be called to create the formatted text for the label `' + widgetName + '`';
+      break;
+    case 'press':
+      returnType = 'void';
+      desc = '/// Callback function that will be called when the button `' + widgetName + '` is pressed';
+      break;
+  }
+  returnValue = Blockly.Lisp.INDENT + 'Ok(' + (returnValue || '()') + ')\n';
+
+  //  Get the function name
+  var funcName = ['on', widgetName, eventName].join('_');  //  e.g. on_my_label_show
+  funcName = funcName.split('__').join('::');  //  TODO: Convert sensor__func to sensor::func
+
+  var args = [];
+  for (var i = 0; i < block.arguments_.length; i++) {
+    //  Assemble the args and give them placeholder types: `arg1: _`
+    args[i] = Blockly.Lisp.variableDB_.getName(block.arguments_[i],
+        Blockly.Variables.NAME_TYPE) + ': _';
+  }
+  var code;
+  if (funcName.indexOf('::') >= 0) {
+    //  System function: Do nothing
+    //  Previously: code = '//  Import ' + funcName;
+    return null;
+  } else {
+    //  User-defined function: Define the function
+    code = [
+      desc + '\n',
+      //  Set the `infer_type` attribute so that the `infer_type` macro will infer the placeholder types.
+      '#[infer_type]  //  Infer the missing types\n',
+      'fn ', funcName,
+      '(', 
+        args.join(', '),
+      ') ',
+      '-> MynewtResult<', 
+      returnType == 'void' ? '()' : returnType, 
+      '> ',
+      '{\n',
+      Blockly.Lisp.INDENT, 'console::print("', funcName, '\\n");\n',
+      branch,
+      returnValue,
+      '}'
+    ].join('');  
+  }
+  code = Blockly.Lisp.scrub_(block, code);
+  // Add % so as not to collide with helper functions in definitions list.
+  Blockly.Lisp.definitions_['%' + funcName] = code;
+  return null;
+};
+
+// Defining a procedure without a return value uses the same generator as
+// a procedure with a return value.
+Blockly.Lisp['widgets_defnoreturn'] = Blockly.Lisp['widgets_defreturn'];
+
+Blockly.Lisp['widgets_callreturn'] = function(block) {
+  //  Call a procedure with a return value.
+  var funcName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('NAME'),
+    Blockly.Procedures.NAME_TYPE);
+  funcName = funcName.split('__').join('::');  //  TODO: Convert sensor__func to sensor::func
+  var args = [];
+  for (var i = 0; i < block.arguments_.length; i++) {
+    args[i] = Blockly.Lisp.valueToCode(block, 'ARG' + i,
+      Blockly.Lisp.ORDER_NONE) || 'null';
+    //  If function is `sensor::new_sensor_listener`, convert the third arg from string to function name, e.g.
+    //  sensor::new_sensor_listener(sensor_key, sensor_type, "handle_sensor_data") becomes
+    //  sensor::new_sensor_listener(sensor_key, sensor_type, handle_sensor_data) 
+    //  TODO: Need a better solution.
+    if (funcName === 'sensor::new_sensor_listener' && i === 2) {
+      args[i] = args[i].split('"').join('');
+    }
+  }
+  //  Generate the function call.
+  var code = funcName + '(' + args.join(', ') + ') ? ';
+
+  //  If function is `sensor_network::get_device_id`, return a reference: `&sensor_network::get_device_id`
+  //  TODO: `get_device_id` should return a reference
+  if (funcName === 'sensor_network::get_device_id') {
+    code = '&' + code;
+  }
+  return [code, Blockly.Lisp.ORDER_UNARY_POSTFIX];
+};
+
+Blockly.Lisp['widgets_callnoreturn'] = function(block) {
+  // Call a procedure with no return value.
+  var funcName = Blockly.Lisp.variableDB_.getName(block.getFieldValue('NAME'),
+      Blockly.Procedures.NAME_TYPE);  //  e.g. my_button
+  funcName = funcName.split('__').join('::');  //  TODO: Convert sensor__func to sensor::func
+  var args = [];
+  for (var i = 0; i < block.arguments_.length; i++) {
+    args[i] = Blockly.Lisp.valueToCode(block, 'ARG' + i,
+        Blockly.Lisp.ORDER_NONE) || 'null';
+  }
+  var code = funcName + '(' + args.join(', ') + ') ? ;\n';
+  return code;
+};
+
+Blockly.Lisp['widgets_ifreturn'] = function(block) {
+  // Conditionally return value from a procedure.
+  var condition = Blockly.Lisp.valueToCode(block, 'CONDITION',
+      Blockly.Lisp.ORDER_NONE) || 'false';
+  var code = 'if ' + condition + ' {\n';
+  if (block.hasReturnValue_) {
+    var value = Blockly.Lisp.valueToCode(block, 'VALUE',
+        Blockly.Lisp.ORDER_NONE) || 'null';
+    code += Blockly.Lisp.INDENT + 'return ' + value + ';\n';
+  } else {
+    code += Blockly.Lisp.INDENT + 'return;\n';
+  }
+  code += '}\n';
+  return code;
+};

From 1209e54e2c49288ba60ecc019bab8548f155229b Mon Sep 17 00:00:00 2001
From: lupyuen 
Date: Fri, 7 May 2021 19:12:02 +0800
Subject: [PATCH 05/48] Fixing menu

---
 core/workspace_svg.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index 3c668add120..3ccde2cb399 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -193,6 +193,15 @@ Blockly.WorkspaceSvg = function(
     this.addChangeListener(Blockly.Procedures.mutatorOpenListener);
   }
 
+  //// TODO
+  ////console.log('Blockly.Widgets', Blockly.Widgets);
+  console.log('Blockly.Widgets.flyoutCategory', Blockly.Widgets.flyoutCategory);
+  if (Blockly.Widgets && Blockly.Widgets.flyoutCategory) {
+    this.registerToolboxCategoryCallback("WIDGET",  ////  TODO
+        Blockly.Widgets.flyoutCategory);
+  }
+  ////
+
   /**
    * Object in charge of storing and updating the workspace theme.
    * @type {!Blockly.ThemeManager}

From 9dcea56708bebddd599f89174e61617fa50348d2 Mon Sep 17 00:00:00 2001
From: lupyuen 
Date: Sat, 8 May 2021 01:47:43 +0800
Subject: [PATCH 06/48] Fixing blocks

---
 core/workspace_svg.js |  3 +--
 demos/code/index.html | 37 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index 3ccde2cb399..d8551475079 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -194,8 +194,7 @@ Blockly.WorkspaceSvg = function(
   }
 
   //// TODO
-  ////console.log('Blockly.Widgets', Blockly.Widgets);
-  console.log('Blockly.Widgets.flyoutCategory', Blockly.Widgets.flyoutCategory);
+  ////console.log('Blockly.Widgets.flyoutCategory', Blockly.Widgets.flyoutCategory);
   if (Blockly.Widgets && Blockly.Widgets.flyoutCategory) {
     this.registerToolboxCategoryCallback("WIDGET",  ////  TODO
         Blockly.Widgets.flyoutCategory);
diff --git a/demos/code/index.html b/demos/code/index.html
index c5400c1d405..766302f3b3b 100644
--- a/demos/code/index.html
+++ b/demos/code/index.html
@@ -117,7 +117,27 @@ 

Blockly‏ >