diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000000..28c475a51c0
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: [lupyuen] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ['paypal.me/lupyuen'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/README.md b/README.md
index a5ecda70108..933e96493a0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,100 @@
-# Blockly for uLisp on RISC-V BL602
+# Blockly for uLisp on RISC-V BL602 (Support for BL602 Simulator in WebAssembly)
-See https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp
+Browser-based graphical development tool that runs uLisp scripts on BL602 RISC-V Board.
+
+Read the article...
+
+- [__uLisp and Blockly on PineCone BL602 RISC-V Board__](https://lupyuen.github.io/articles/lisp)
+
+Watch the demo on YouTube...
+
+- [__LED Demo__](https://youtu.be/RRhzW4j8BtI)
+
+- [__Blinky Demo__](https://youtu.be/LNkmUIv7ZZc)
+
+- [__BL602 Simulator Demo__](https://youtu.be/Ag2CERd1OzQ)
+
+Try it here...
+
+https://appkaki.github.io/blockly-ulisp/demos/code/
+
+uLisp Firmware needs to be installed on the BL602 Board...
+
+https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp
+
+Or try it with the BL602 Simulator in WebAssembly...
+
+https://appkaki.github.io/blockly-ulisp/demos/simulator/
+
+## Lisp Code Generator
+
+The following have been added into the existing [`generators`](generators) folder to generate Lisp code and to add blocks specific to uLisp...
+
+- [`generators/lisp.js`](generators/lisp.js): Main interface for Lisp Code Generator
+
+- [`generators/lisp`](generators/lisp): Lisp Code Generator for various blocks
+
+- [`generators/lisp/lisp_library.xml`](generators/lisp/lisp_library.xml): Blocks XML file used by Block Exporter to generate the custom blocks
+
+The Lisp Code Generator is __incomplete__. The only blocks supported are...
+
+1. Forever
+
+1. On Start
+
+1. Wait
+
+1. GPIO Digital Write
+
+The Lisp Code Generator is based on Visual Embedded Rust...
+
+https://lupyuen.github.io/articles/advanced-topics-for-visual-embedded-rust-programming
+
+## Demo for Lisp Code Generator
+
+Watch the demo on YouTube...
+
+- [__LED Demo__](https://youtu.be/RRhzW4j8BtI)
+
+- [__Blinky Demo__](https://youtu.be/LNkmUIv7ZZc)
+
+- [__BL602 Simulator Demo__](https://youtu.be/Ag2CERd1OzQ)
+
+Try it here...
+
+https://appkaki.github.io/blockly-ulisp/demos/code/
+
+Or try it with the BL602 Simulator in WebAssembly...
+
+https://appkaki.github.io/blockly-ulisp/demos/simulator/
+
+The Blockly demo at [`demos/code`](demos/code) has been customised to include the Lisp Code Generator...
+
+- [`demos/code/index.html`](demos/code/index.html): Customised to load the Lisp Code Generator and Lisp Blocks
+
+- [`demos/code/code.js`](demos/code/code.js): Customised to load the Lisp Code Generator and Lisp Blocks
+
+The Blockly demo calls the [__Web Serial API__](https://web.dev/serial/) to transfer the generated uLisp Script to BL602...
+
+https://github.com/AppKaki/blockly-ulisp/blob/master/demos/code/code.js#L641-L738
+
+We assume that BL602 is running the uLisp Firmware and connected to our computer via USB...
+
+https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp
+
+The BL602 Simulator HTML and JavaScript files are here...
+
+- [`demos/simulator/index.html`](demos/simulator/index.html): Customised to load the Lisp Code Generator, Lisp Blocks and BL602 Simulator
+
+- [`demos/simulator/code.js`](demos/simulator/code.js): Customised to load the Lisp Code Generator, Lisp Blocks and BL602 Simulator
+
+The BL602 Simulator includes uLisp compiled for WebAssembly...
+
+https://github.com/lupyuen/ulisp-bl602/tree/wasm
+
+Inspired by MakeCode for BBC micro:bit...
+
+https://makecode.microbit.org/
# Blockly [](https://travis-ci.org/google/blockly)
diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index 3c668add120..d8551475079 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -193,6 +193,14 @@ Blockly.WorkspaceSvg = function(
this.addChangeListener(Blockly.Procedures.mutatorOpenListener);
}
+ //// TODO
+ ////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}
diff --git a/demos/code/code.js b/demos/code/code.js
index 0c27653252f..8f980cef6b5 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') {
@@ -449,6 +451,28 @@ Code.init = function() {
Blockly.JavaScript.addReservedWords('code,timeouts,checkTimeout');
Code.loadBlocks('');
+
+ //// TODO: Added code here
+ // Load the Lisp Custom Blocks.
+ var blocks = lisp_blocks; // lisp_blocks defined in lisp_blocks.js
+ // For each Block...
+ blocks.forEach(block => {
+ // Register the Block with Blockly.
+ Blockly.Blocks[block.type] = {
+ init: function() {
+ this.jsonInit(block);
+ // Assign 'this' to a variable for use in the tooltip closure below.
+ var thisBlock = this;
+ /*
+ this.setTooltip(function() {
+ return 'Add a number to variable "%1".'.replace('%1',
+ thisBlock.getFieldValue('VAR'));
+ });
+ */
+ }
+ };
+ });
+ ////
if ('BlocklyStorage' in window) {
// Hook a save function onto unload.
@@ -546,11 +570,37 @@ Code.initLanguage = function() {
document.getElementById('trashButton').title = MSG['trashTooltip'];
};
+//// TODO: Added code for Web Serial API
+
/**
- * Execute the user's code.
- * Just a quick and dirty eval. Catch infinite loops.
+ * Execute the Lisp code on BL602 with Web Serial API
*/
Code.runJS = function() {
+ var code = Blockly.Lisp.workspaceToCode(Code.workspace);
+ console.log(code);
+
+ // Merge the code lines into commands so that all commands start with "("
+ var commands = [];
+ const lines = code.split("\n");
+ lines.forEach(line => {
+ // Skip blank lines
+ if (line.trim() == "") { return; }
+
+ if (line[0] == "(") {
+ // If this line starts with "(", add it as a command
+ commands.push(line);
+ } else {
+ // If this line doesn't start with "(", merge with previous line
+ const lastIndex = commands.length - 1;
+ commands[lastIndex] += " " + line;
+ }
+ });
+
+ // Run the merged commands
+ runCommands(commands);
+
+ /* Previously: Execute the user's code. Just a quick and dirty eval. Catch infinite loops.
+
Blockly.JavaScript.INFINITE_LOOP_TRAP = 'checkTimeout();\n';
var timeouts = 0;
var checkTimeout = function() {
@@ -564,7 +614,7 @@ Code.runJS = function() {
eval(code);
} catch (e) {
alert(MSG['badCode'].replace('%1', e));
- }
+ } */
};
/**
@@ -587,3 +637,102 @@ document.write('\n');
document.write('\n');
window.addEventListener('load', Code.init);
+
+///////////////////////////////////////////////////////////////////////////////
+// Web Serial Interface
+
+// Run a list of commands on BL602 via Web Serial API
+async function runCommands(commands) {
+ console.log(commands);
+
+ // For each merged command...
+ for (const command of commands) {
+ // Send an empty command and check that BL602 responds with "#"
+ await runWebSerialCommand(
+ "", // Command
+ "#" // Expected Response
+ );
+
+ // Send the actual command but don't wait for response
+ await runWebSerialCommand(
+ command, // Command
+ null // Don't wait for response
+ );
+
+ // Test the reboot command
+ /*
+ await runWebSerialCommand(
+ "reboot", // Command
+ "Init CLI" // Expected Response
+ );
+ */
+
+ // TODO: Handle no response or invalid response from BL602
+ // TODO: Show the BL602 response
+ }
+}
+
+// Web Serial Port
+var serialPort;
+
+// Run a command on BL602 via Web Serial API and wait for the expectedResponse (if not null)
+// Based on https://web.dev/serial/
+async function runWebSerialCommand(command, expectedResponse) {
+ // Check if Web Serial API is supported
+ if (!("serial" in navigator)) { alert("Web Serial API is not supported"); return; }
+
+ // Prompt user to select any serial port
+ if (!serialPort) { serialPort = await navigator.serial.requestPort(); }
+ if (!serialPort) { return; }
+
+ // Wait for the serial port to open at 2 Mbps
+ await serialPort.open({ baudRate: 2000000 });
+
+ // Capture the events for closing the read and write streams
+ var writableStreamClosed = null;
+ var readableStreamClosed = null;
+
+ // Send command to BL602
+ {
+ // Open a write stream
+ console.log("Writing to BL602: " + command + "...");
+ const textEncoder = new TextEncoderStream();
+ writableStreamClosed = textEncoder.readable.pipeTo(serialPort.writable);
+ const writer = textEncoder.writable.getWriter();
+
+ // Write the command
+ await writer.write(command + "\r");
+
+ // Close the write stream
+ writer.close();
+ }
+
+ // Read response from BL602
+ if (expectedResponse) {
+ // Open a read stream
+ console.log("Reading from BL602...");
+ const textDecoder = new TextDecoderStream();
+ readableStreamClosed = serialPort.readable.pipeTo(textDecoder.writable);
+ const reader = textDecoder.readable.getReader();
+
+ // Listen to data coming from the serial device
+ while (true) {
+ const { value, done } = await reader.read();
+ if (!done) { console.log(value); }
+
+ // If the stream has ended, or the data contains expected response, we stop
+ if (done || value.indexOf(expectedResponse) >= 0) { break; }
+ }
+
+ // Close the read stream
+ reader.cancel();
+ }
+
+ // Wait for read and write streams to be closed
+ if (readableStreamClosed) { await readableStreamClosed.catch(() => { /* Ignore the error */ }); }
+ if (writableStreamClosed) { await writableStreamClosed; }
+
+ // Close the port
+ await serialPort.close();
+ console.log("runWebSerial: OK");
+}
diff --git a/demos/code/index.html b/demos/code/index.html
index 9fd858aeffa..ca4147b9d69 100644
--- a/demos/code/index.html
+++ b/demos/code/index.html
@@ -4,16 +4,50 @@
-
Blockly Demo:
+ uLisp Blockly
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -36,6 +70,8 @@ Blockly >
...
+ Lisp
+
JavaScript
Python
@@ -72,6 +108,7 @@
+
@@ -80,16 +117,43 @@ Blockly >
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -118,6 +182,17 @@
+
+
+
+
+
+
+
+
+
+
+
123
@@ -385,6 +460,7 @@
+
diff --git a/demos/simulator/code.js b/demos/simulator/code.js
new file mode 100644
index 00000000000..fcd8005097e
--- /dev/null
+++ b/demos/simulator/code.js
@@ -0,0 +1,742 @@
+/**
+ * @license
+ * Copyright 2012 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview JavaScript for Blockly's Code demo.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+/**
+ * Create a namespace for the application.
+ */
+var Code = {};
+
+/**
+ * Lookup for names of supported languages. Keys should be in ISO 639 format.
+ */
+Code.LANGUAGE_NAME = {
+ 'ar': 'العربية',
+ 'be-tarask': 'Taraškievica',
+ 'br': 'Brezhoneg',
+ 'ca': 'Català',
+ 'cs': 'Česky',
+ 'da': 'Dansk',
+ 'de': 'Deutsch',
+ 'el': 'Ελληνικά',
+ 'en': 'English',
+ 'es': 'Español',
+ 'et': 'Eesti',
+ 'fa': 'فارسی',
+ 'fr': 'Français',
+ 'he': 'עברית',
+ 'hrx': 'Hunsrik',
+ 'hu': 'Magyar',
+ 'ia': 'Interlingua',
+ 'is': 'Íslenska',
+ 'it': 'Italiano',
+ 'ja': '日本語',
+ 'kab': 'Kabyle',
+ 'ko': '한국어',
+ 'mk': 'Македонски',
+ 'ms': 'Bahasa Melayu',
+ 'nb': 'Norsk Bokmål',
+ 'nl': 'Nederlands, Vlaams',
+ 'oc': 'Lenga d\'òc',
+ 'pl': 'Polski',
+ 'pms': 'Piemontèis',
+ 'pt-br': 'Português Brasileiro',
+ 'ro': 'Română',
+ 'ru': 'Русский',
+ 'sc': 'Sardu',
+ 'sk': 'Slovenčina',
+ 'sr': 'Српски',
+ 'sv': 'Svenska',
+ 'ta': 'தமிழ்',
+ 'th': 'ภาษาไทย',
+ 'tlh': 'tlhIngan Hol',
+ 'tr': 'Türkçe',
+ 'uk': 'Українська',
+ 'vi': 'Tiếng Việt',
+ 'zh-hans': '简体中文',
+ 'zh-hant': '正體中文'
+};
+
+/**
+ * List of RTL languages.
+ */
+Code.LANGUAGE_RTL = ['ar', 'fa', 'he', 'lki'];
+
+/**
+ * Blockly's main workspace.
+ * @type {Blockly.WorkspaceSvg}
+ */
+Code.workspace = null;
+
+/**
+ * Extracts a parameter from the URL.
+ * If the parameter is absent default_value is returned.
+ * @param {string} name The name of the parameter.
+ * @param {string} defaultValue Value to return if parameter not found.
+ * @return {string} The parameter value or the default value if not found.
+ */
+Code.getStringParamFromUrl = function(name, defaultValue) {
+ var val = location.search.match(new RegExp('[?&]' + name + '=([^&]+)'));
+ return val ? decodeURIComponent(val[1].replace(/\+/g, '%20')) : defaultValue;
+};
+
+/**
+ * Get the language of this user from the URL.
+ * @return {string} User's language.
+ */
+Code.getLang = function() {
+ var lang = Code.getStringParamFromUrl('lang', '');
+ if (Code.LANGUAGE_NAME[lang] === undefined) {
+ // Default to English.
+ lang = 'en';
+ }
+ return lang;
+};
+
+/**
+ * Is the current language (Code.LANG) an RTL language?
+ * @return {boolean} True if RTL, false if LTR.
+ */
+Code.isRtl = function() {
+ return Code.LANGUAGE_RTL.indexOf(Code.LANG) != -1;
+};
+
+/**
+ * Load blocks saved on App Engine Storage or in session/local storage.
+ * @param {string} defaultXml Text representation of default blocks.
+ */
+Code.loadBlocks = function(defaultXml) {
+ try {
+ var loadOnce = window.sessionStorage.loadOnceBlocks;
+ } catch(e) {
+ // Firefox sometimes throws a SecurityError when accessing sessionStorage.
+ // Restarting Firefox fixes this, so it looks like a bug.
+ var loadOnce = null;
+ }
+ if ('BlocklyStorage' in window && window.location.hash.length > 1) {
+ // An href with #key trigers an AJAX call to retrieve saved blocks.
+ BlocklyStorage.retrieveXml(window.location.hash.substring(1));
+ } else if (loadOnce) {
+ // Language switching stores the blocks during the reload.
+ delete window.sessionStorage.loadOnceBlocks;
+ var xml = Blockly.Xml.textToDom(loadOnce);
+ Blockly.Xml.domToWorkspace(xml, Code.workspace);
+ } else if (defaultXml) {
+ // Load the editor with default starting blocks.
+ var xml = Blockly.Xml.textToDom(defaultXml);
+ Blockly.Xml.domToWorkspace(xml, Code.workspace);
+ } else if ('BlocklyStorage' in window) {
+ // Restore saved blocks in a separate thread so that subsequent
+ // initialization is not affected from a failed load.
+ window.setTimeout(BlocklyStorage.restoreBlocks, 0);
+ }
+};
+
+/**
+ * Save the blocks and reload with a different language.
+ */
+Code.changeLanguage = function() {
+ // Store the blocks for the duration of the reload.
+ // MSIE 11 does not support sessionStorage on file:// URLs.
+ if (window.sessionStorage) {
+ var xml = Blockly.Xml.workspaceToDom(Code.workspace);
+ var text = Blockly.Xml.domToText(xml);
+ window.sessionStorage.loadOnceBlocks = text;
+ }
+
+ var languageMenu = document.getElementById('languageMenu');
+ var newLang = encodeURIComponent(
+ languageMenu.options[languageMenu.selectedIndex].value);
+ var search = window.location.search;
+ if (search.length <= 1) {
+ search = '?lang=' + newLang;
+ } else if (search.match(/[?&]lang=[^&]*/)) {
+ search = search.replace(/([?&]lang=)[^&]*/, '$1' + newLang);
+ } else {
+ search = search.replace(/\?/, '?lang=' + newLang + '&');
+ }
+
+ window.location = window.location.protocol + '//' +
+ window.location.host + window.location.pathname + search;
+};
+
+/**
+ * Changes the output language by clicking the tab matching
+ * the selected language in the codeMenu.
+ */
+Code.changeCodingLanguage = function() {
+ var codeMenu = document.getElementById('code_menu');
+ Code.tabClick(codeMenu.options[codeMenu.selectedIndex].value);
+}
+
+/**
+ * Bind a function to a button's click event.
+ * On touch enabled browsers, ontouchend is treated as equivalent to onclick.
+ * @param {!Element|string} el Button element or ID thereof.
+ * @param {!Function} func Event handler to bind.
+ */
+Code.bindClick = function(el, func) {
+ if (typeof el == 'string') {
+ el = document.getElementById(el);
+ }
+ el.addEventListener('click', func, true);
+ el.addEventListener('touchend', func, true);
+};
+
+/**
+ * Load the Prettify CSS and JavaScript.
+ */
+Code.importPrettify = function() {
+ var script = document.createElement('script');
+ script.setAttribute('src', 'https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js');
+ document.head.appendChild(script);
+};
+
+/**
+ * Compute the absolute coordinates and dimensions of an HTML element.
+ * @param {!Element} element Element to match.
+ * @return {!Object} Contains height, width, x, and y properties.
+ * @private
+ */
+Code.getBBox_ = function(element) {
+ var height = element.offsetHeight;
+ var width = element.offsetWidth;
+ var x = 0;
+ var y = 0;
+ do {
+ x += element.offsetLeft;
+ y += element.offsetTop;
+ element = element.offsetParent;
+ } while (element);
+ return {
+ height: height,
+ width: width,
+ x: x,
+ y: y
+ };
+};
+
+/**
+ * User's language (e.g. "en").
+ * @type {string}
+ */
+Code.LANG = Code.getLang();
+
+/**
+ * List of tab names.
+ * @private
+ */
+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', 'Lisp', 'JavaScript', 'PHP', 'Python', 'Dart', 'Lua', 'XML',
+];
+
+Code.selected = 'blocks';
+
+/**
+ * Switch the visible pane when a tab is clicked.
+ * @param {string} clickedName Name of tab clicked.
+ */
+Code.tabClick = function(clickedName) {
+ // If the XML tab was open, save and render the content.
+ if (document.getElementById('tab_xml').classList.contains('tabon')) {
+ var xmlTextarea = document.getElementById('content_xml');
+ var xmlText = xmlTextarea.value;
+ var xmlDom = null;
+ try {
+ xmlDom = Blockly.Xml.textToDom(xmlText);
+ } catch (e) {
+ var q =
+ window.confirm(MSG['badXml'].replace('%1', e));
+ if (!q) {
+ // Leave the user on the XML tab.
+ return;
+ }
+ }
+ if (xmlDom) {
+ Code.workspace.clear();
+ Blockly.Xml.domToWorkspace(xmlDom, Code.workspace);
+ }
+ }
+
+ if (document.getElementById('tab_blocks').classList.contains('tabon')) {
+ Code.workspace.setVisible(false);
+ }
+ // Deselect all tabs and hide all panes.
+ for (var i = 0; i < Code.TABS_.length; i++) {
+ var name = Code.TABS_[i];
+ var tab = document.getElementById('tab_' + name);
+ tab.classList.add('taboff');
+ tab.classList.remove('tabon');
+ document.getElementById('content_' + name).style.visibility = 'hidden';
+ }
+
+ // Select the active tab.
+ Code.selected = clickedName;
+ var selectedTab = document.getElementById('tab_' + clickedName);
+ selectedTab.classList.remove('taboff');
+ selectedTab.classList.add('tabon');
+ // Show the selected pane.
+ document.getElementById('content_' + clickedName).style.visibility =
+ 'visible';
+ Code.renderContent();
+ // The code menu tab is on if the blocks tab is off.
+ var codeMenuTab = document.getElementById('tab_code');
+ if (clickedName == 'blocks') {
+ Code.workspace.setVisible(true);
+ codeMenuTab.className = 'taboff';
+ } else {
+ codeMenuTab.className = 'tabon';
+ }
+ // Sync the menu's value with the clicked tab value if needed.
+ var codeMenu = document.getElementById('code_menu');
+ for (var i = 0; i < codeMenu.options.length; i++) {
+ if (codeMenu.options[i].value == clickedName) {
+ codeMenu.selectedIndex = i;
+ break;
+ }
+ }
+ Blockly.svgResize(Code.workspace);
+};
+
+/**
+ * Populate the currently selected pane with content generated from the blocks.
+ */
+Code.renderContent = function() {
+ var content = document.getElementById('content_' + Code.selected);
+ // Initialize the pane.
+ if (content.id == 'content_xml') {
+ var xmlTextarea = document.getElementById('content_xml');
+ var xmlDom = Blockly.Xml.workspaceToDom(Code.workspace);
+ 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') {
+ Code.attemptCodeGeneration(Blockly.Python);
+ } else if (content.id == 'content_php') {
+ Code.attemptCodeGeneration(Blockly.PHP);
+ } else if (content.id == 'content_dart') {
+ Code.attemptCodeGeneration(Blockly.Dart);
+ } else if (content.id == 'content_lua') {
+ Code.attemptCodeGeneration(Blockly.Lua);
+ }
+ if (typeof PR == 'object') {
+ PR.prettyPrint();
+ }
+};
+
+/**
+ * Attempt to generate the code and display it in the UI, pretty printed.
+ * @param generator {!Blockly.Generator} The generator to use.
+ */
+Code.attemptCodeGeneration = function(generator) {
+ var content = document.getElementById('content_' + Code.selected);
+ content.textContent = '';
+ if (Code.checkAllGeneratorFunctionsDefined(generator)) {
+ var code = generator.workspaceToCode(Code.workspace);
+ content.textContent = code;
+ // Remove the 'prettyprinted' class, so that Prettify will recalculate.
+ content.className = content.className.replace('prettyprinted', '');
+ }
+};
+
+/**
+ * Check whether all blocks in use have generator functions.
+ * @param generator {!Blockly.Generator} The generator to use.
+ */
+Code.checkAllGeneratorFunctionsDefined = function(generator) {
+ var blocks = Code.workspace.getAllBlocks(false);
+ var missingBlockGenerators = [];
+ for (var i = 0; i < blocks.length; i++) {
+ var blockType = blocks[i].type;
+ if (!generator[blockType]) {
+ if (missingBlockGenerators.indexOf(blockType) == -1) {
+ missingBlockGenerators.push(blockType);
+ }
+ }
+ }
+
+ var valid = missingBlockGenerators.length == 0;
+ if (!valid) {
+ var msg = 'The generator code for the following blocks not specified for ' +
+ generator.name_ + ':\n - ' + missingBlockGenerators.join('\n - ');
+ Blockly.alert(msg); // Assuming synchronous. No callback.
+ }
+ return valid;
+};
+
+/**
+ * Initialize Blockly. Called on page load.
+ */
+Code.init = function() {
+ Code.initLanguage();
+
+ var rtl = Code.isRtl();
+ var container = document.getElementById('content_area');
+ var onresize = function(e) {
+ var bBox = Code.getBBox_(container);
+ for (var i = 0; i < Code.TABS_.length; i++) {
+ var el = document.getElementById('content_' + Code.TABS_[i]);
+ el.style.top = bBox.y + 'px';
+ el.style.left = bBox.x + 'px';
+ // Height and width need to be set, read back, then set again to
+ // compensate for scrollbars.
+ el.style.height = bBox.height + 'px';
+ el.style.height = (2 * bBox.height - el.offsetHeight) + 'px';
+ el.style.width = bBox.width + 'px';
+ el.style.width = (2 * bBox.width - el.offsetWidth) + 'px';
+ }
+ // Make the 'Blocks' tab line up with the toolbox.
+ if (Code.workspace && Code.workspace.getToolbox().width) {
+ document.getElementById('tab_blocks').style.minWidth =
+ (Code.workspace.getToolbox().width - 38) + 'px';
+ // Account for the 19 pixel margin and on each side.
+ }
+ };
+ window.addEventListener('resize', onresize, false);
+
+ // The toolbox XML specifies each category name using Blockly's messaging
+ // format (eg. ``).
+ // These message keys need to be defined in `Blockly.Msg` in order to
+ // be decoded by the library. Therefore, we'll use the `MSG` dictionary that's
+ // been defined for each language to import each category name message
+ // into `Blockly.Msg`.
+ // TODO: Clean up the message files so this is done explicitly instead of
+ // through this for-loop.
+ for (var messageKey in MSG) {
+ if (messageKey.indexOf('cat') == 0) {
+ Blockly.Msg[messageKey.toUpperCase()] = MSG[messageKey];
+ }
+ }
+
+ // Construct the toolbox XML, replacing translated variable names.
+ var toolboxText = document.getElementById('toolbox').outerHTML;
+ toolboxText = toolboxText.replace(/(^|[^%]){(\w+)}/g,
+ function(m, p1, p2) {return p1 + MSG[p2];});
+ var toolboxXml = Blockly.Xml.textToDom(toolboxText);
+
+ Code.workspace = Blockly.inject('content_blocks',
+ {grid:
+ {spacing: 25,
+ length: 3,
+ colour: '#ccc',
+ snap: true},
+ media: '../../media/',
+ rtl: rtl,
+ toolbox: toolboxXml,
+ zoom:
+ {controls: true,
+ wheel: true}
+ });
+
+ // Add to reserved word list: Local variables in execution environment (runJS)
+ // and the infinite loop detection function.
+ Blockly.JavaScript.addReservedWords('code,timeouts,checkTimeout');
+
+ Code.loadBlocks('');
+
+ //// TODO: Added code here
+ // Load the Lisp Custom Blocks.
+ var blocks = lisp_blocks; // lisp_blocks defined in lisp_blocks.js
+ // For each Block...
+ blocks.forEach(block => {
+ // Register the Block with Blockly.
+ Blockly.Blocks[block.type] = {
+ init: function() {
+ this.jsonInit(block);
+ // Assign 'this' to a variable for use in the tooltip closure below.
+ var thisBlock = this;
+ /*
+ this.setTooltip(function() {
+ return 'Add a number to variable "%1".'.replace('%1',
+ thisBlock.getFieldValue('VAR'));
+ });
+ */
+ }
+ };
+ });
+ ////
+
+ if ('BlocklyStorage' in window) {
+ // Hook a save function onto unload.
+ BlocklyStorage.backupOnUnload(Code.workspace);
+ }
+
+ Code.tabClick(Code.selected);
+
+ Code.bindClick('trashButton',
+ function() {Code.discard(); Code.renderContent();});
+ Code.bindClick('runButton', Code.runJS);
+ // Disable the link button if page isn't backed by App Engine storage.
+ var linkButton = document.getElementById('linkButton');
+ if ('BlocklyStorage' in window) {
+ BlocklyStorage['HTTPREQUEST_ERROR'] = MSG['httpRequestError'];
+ BlocklyStorage['LINK_ALERT'] = MSG['linkAlert'];
+ BlocklyStorage['HASH_ERROR'] = MSG['hashError'];
+ BlocklyStorage['XML_ERROR'] = MSG['xmlError'];
+ Code.bindClick(linkButton,
+ function() {BlocklyStorage.link(Code.workspace);});
+ } else if (linkButton) {
+ linkButton.className = 'disabled';
+ }
+
+ for (var i = 0; i < Code.TABS_.length; i++) {
+ var name = Code.TABS_[i];
+ Code.bindClick('tab_' + name,
+ function(name_) {return function() {Code.tabClick(name_);};}(name));
+ }
+ Code.bindClick('tab_code', function(e) {
+ if (e.target !== document.getElementById('tab_code')) {
+ // Prevent clicks on child codeMenu from triggering a tab click.
+ return;
+ }
+ Code.changeCodingLanguage();
+ });
+
+ onresize();
+ Blockly.svgResize(Code.workspace);
+
+ // Lazy-load the syntax-highlighting.
+ window.setTimeout(Code.importPrettify, 1);
+};
+
+/**
+ * Initialize the page language.
+ */
+Code.initLanguage = function() {
+ // Set the HTML's language and direction.
+ var rtl = Code.isRtl();
+ document.dir = rtl ? 'rtl' : 'ltr';
+ document.head.parentElement.setAttribute('lang', Code.LANG);
+
+ // Sort languages alphabetically.
+ var languages = [];
+ for (var lang in Code.LANGUAGE_NAME) {
+ languages.push([Code.LANGUAGE_NAME[lang], lang]);
+ }
+ var comp = function(a, b) {
+ // Sort based on first argument ('English', 'Русский', '简体字', etc).
+ if (a[0] > b[0]) return 1;
+ if (a[0] < b[0]) return -1;
+ return 0;
+ };
+ languages.sort(comp);
+ // Populate the language selection menu.
+ var languageMenu = document.getElementById('languageMenu');
+ languageMenu.options.length = 0;
+ for (var i = 0; i < languages.length; i++) {
+ var tuple = languages[i];
+ var lang = tuple[tuple.length - 1];
+ var option = new Option(tuple[0], lang);
+ if (lang == Code.LANG) {
+ option.selected = true;
+ }
+ languageMenu.options.add(option);
+ }
+ languageMenu.addEventListener('change', Code.changeLanguage, true);
+
+ // Populate the coding language selection menu.
+ var codeMenu = document.getElementById('code_menu');
+ codeMenu.options.length = 0;
+ for (var i = 1; i < Code.TABS_.length; i++) {
+ codeMenu.options.add(new Option(Code.TABS_DISPLAY_[i], Code.TABS_[i]));
+ }
+ codeMenu.addEventListener('change', Code.changeCodingLanguage);
+
+ // Inject language strings.
+ document.title += ' ' + MSG['title'];
+ document.getElementById('title').textContent = MSG['title'];
+ document.getElementById('tab_blocks').textContent = MSG['blocks'];
+
+ document.getElementById('linkButton').title = MSG['linkTooltip'];
+ document.getElementById('runButton').title = MSG['runTooltip'];
+ document.getElementById('trashButton').title = MSG['trashTooltip'];
+};
+
+//// TODO: Added code for Web Serial API
+
+/**
+ * Execute the Lisp code on BL602 with Web Serial API
+ */
+Code.runJS = function() {
+ var code = Blockly.Lisp.workspaceToCode(Code.workspace);
+ console.log(code);
+
+ runScript(code); //// TODO: Defined in index.html
+
+ /* //// TODO
+ // Merge the code lines into commands so that all commands start with "("
+ var commands = [];
+ const lines = code.split("\n");
+ lines.forEach(line => {
+ // Skip blank lines
+ if (line.trim() == "") { return; }
+
+ if (line[0] == "(") {
+ // If this line starts with "(", add it as a command
+ commands.push(line);
+ } else {
+ // If this line doesn't start with "(", merge with previous line
+ const lastIndex = commands.length - 1;
+ commands[lastIndex] += " " + line;
+ }
+ });
+
+ // Run the merged commands
+ runCommands(commands);
+ */
+
+ /* Previously: Execute the user's code. Just a quick and dirty eval. Catch infinite loops.
+
+ Blockly.JavaScript.INFINITE_LOOP_TRAP = 'checkTimeout();\n';
+ var timeouts = 0;
+ var checkTimeout = function() {
+ if (timeouts++ > 1000000) {
+ throw MSG['timeout'];
+ }
+ };
+ var code = Blockly.JavaScript.workspaceToCode(Code.workspace);
+ Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
+ try {
+ eval(code);
+ } catch (e) {
+ alert(MSG['badCode'].replace('%1', e));
+ } */
+};
+
+/**
+ * Discard all blocks from the workspace.
+ */
+Code.discard = function() {
+ var count = Code.workspace.getAllBlocks(false).length;
+ if (count < 2 ||
+ window.confirm(Blockly.Msg['DELETE_ALL_BLOCKS'].replace('%1', count))) {
+ Code.workspace.clear();
+ if (window.location.hash) {
+ window.location.hash = '';
+ }
+ }
+};
+
+// Load the Code demo's language strings.
+document.write('\n');
+// Load Blockly's language strings.
+document.write('\n');
+
+window.addEventListener('load', Code.init);
+
+///////////////////////////////////////////////////////////////////////////////
+// Web Serial Interface
+
+// Run a list of commands on BL602 via Web Serial API
+async function runCommands(commands) {
+ console.log(commands);
+
+ // For each merged command...
+ for (const command of commands) {
+ // Send an empty command and check that BL602 responds with "#"
+ await runWebSerialCommand(
+ "", // Command
+ "#" // Expected Response
+ );
+
+ // Send the actual command but don't wait for response
+ await runWebSerialCommand(
+ command, // Command
+ null // Don't wait for response
+ );
+
+ // Test the reboot command
+ /*
+ await runWebSerialCommand(
+ "reboot", // Command
+ "Init CLI" // Expected Response
+ );
+ */
+
+ // TODO: Handle no response or invalid response from BL602
+ // TODO: Show the BL602 response
+ }
+}
+
+// Web Serial Port
+var serialPort;
+
+// Run a command on BL602 via Web Serial API and wait for the expectedResponse (if not null)
+// Based on https://web.dev/serial/
+async function runWebSerialCommand(command, expectedResponse) {
+ // Check if Web Serial API is supported
+ if (!("serial" in navigator)) { alert("Web Serial API is not supported"); return; }
+
+ // Prompt user to select any serial port
+ if (!serialPort) { serialPort = await navigator.serial.requestPort(); }
+ if (!serialPort) { return; }
+
+ // Wait for the serial port to open at 2 Mbps
+ await serialPort.open({ baudRate: 2000000 });
+
+ // Capture the events for closing the read and write streams
+ var writableStreamClosed = null;
+ var readableStreamClosed = null;
+
+ // Send command to BL602
+ {
+ // Open a write stream
+ console.log("Writing to BL602: " + command + "...");
+ const textEncoder = new TextEncoderStream();
+ writableStreamClosed = textEncoder.readable.pipeTo(serialPort.writable);
+ const writer = textEncoder.writable.getWriter();
+
+ // Write the command
+ await writer.write(command + "\r");
+
+ // Close the write stream
+ writer.close();
+ }
+
+ // Read response from BL602
+ if (expectedResponse) {
+ // Open a read stream
+ console.log("Reading from BL602...");
+ const textDecoder = new TextDecoderStream();
+ readableStreamClosed = serialPort.readable.pipeTo(textDecoder.writable);
+ const reader = textDecoder.readable.getReader();
+
+ // Listen to data coming from the serial device
+ while (true) {
+ const { value, done } = await reader.read();
+ if (!done) { console.log(value); }
+
+ // If the stream has ended, or the data contains expected response, we stop
+ if (done || value.indexOf(expectedResponse) >= 0) { break; }
+ }
+
+ // Close the read stream
+ reader.cancel();
+ }
+
+ // Wait for read and write streams to be closed
+ if (readableStreamClosed) { await readableStreamClosed.catch(() => { /* Ignore the error */ }); }
+ if (writableStreamClosed) { await writableStreamClosed; }
+
+ // Close the port
+ await serialPort.close();
+ console.log("runWebSerial: OK");
+}
diff --git a/demos/simulator/icon.png b/demos/simulator/icon.png
new file mode 100644
index 00000000000..feaa92996a4
Binary files /dev/null and b/demos/simulator/icon.png differ
diff --git a/demos/simulator/icons.png b/demos/simulator/icons.png
new file mode 100644
index 00000000000..7cced7a7ea9
Binary files /dev/null and b/demos/simulator/icons.png differ
diff --git a/demos/simulator/index.html b/demos/simulator/index.html
new file mode 100644
index 00000000000..e64c23614d5
--- /dev/null
+++ b/demos/simulator/index.html
@@ -0,0 +1,725 @@
+
+
+
+
+
+
+ uLisp Blockly
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Privacy
+
+
+
+
+
+
+ ...
+
+ Lisp
+
+ JavaScript
+
+ Python
+
+ PHP
+
+ Lua
+
+ Dart
+
+ XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ 10
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 123
+
+
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+
+
+
+ 9
+
+
+
+
+
+
+ 45
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+ 3.1
+
+
+
+
+
+
+
+ 64
+
+
+
+
+ 10
+
+
+
+
+
+
+ 50
+
+
+
+
+ 1
+
+
+
+
+ 100
+
+
+
+
+
+
+ 1
+
+
+
+
+ 100
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ abc
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {textVariable}
+
+
+
+
+ abc
+
+
+
+
+
+
+ {textVariable}
+
+
+
+
+
+
+ {textVariable}
+
+
+
+
+
+
+ abc
+
+
+
+
+
+
+ abc
+
+
+
+
+
+
+ abc
+
+
+
+
+
+
+ abc
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+ {listVariable}
+
+
+
+
+
+
+ {listVariable}
+
+
+
+
+
+
+ {listVariable}
+
+
+
+
+
+
+ {listVariable}
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+ 50
+
+
+
+
+ 0
+
+
+
+
+
+
+ #ff0000
+
+
+
+
+ #3333ff
+
+
+
+
+ 0.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/simulator/msg/ar.js b/demos/simulator/msg/ar.js
new file mode 100644
index 00000000000..952198b21d6
--- /dev/null
+++ b/demos/simulator/msg/ar.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "كود",
+ blocks: "البلوكات",
+ linkTooltip: "احفظ ووصلة إلى البلوكات.",
+ runTooltip: "شغل البرنامج المعرف بواسطة البلوكات في مساحة العمل.",
+ badCode: "خطأ في البرنامج:\n %1",
+ timeout: "تم تجاوز الحد الأقصى لتكرارات التنفيذ .",
+ trashTooltip: "تجاهل كل البلوكات.",
+ catLogic: "منطق",
+ catLoops: "الحلقات",
+ catMath: "رياضيات",
+ catText: "نص",
+ catLists: "قوائم",
+ catColour: "لون",
+ catVariables: "متغيرات",
+ catFunctions: "إجراءات",
+ listVariable: "قائمة",
+ textVariable: "نص",
+ httpRequestError: "كانت هناك مشكلة مع هذا الطلب.",
+ linkAlert: "مشاركة كود بلوكلي الخاص بك مع هذا الرابط:\n %1",
+ hashError: "عذراً،ال '%1' لا تتوافق مع أي برنامج تم حفظه.",
+ xmlError: "تعذر تحميل الملف المحفوظة الخاصة بك. ربما تم إنشاؤه باستخدام إصدار مختلف من بلوكلي؟",
+ badXml: "خطأ في توزيع ال \"XML\":\n %1\n\nحدد 'موافق' للتخلي عن التغييرات أو 'إلغاء الأمر' لمواصلة تحرير ال\"XML\"."
+};
diff --git a/demos/simulator/msg/be-tarask.js b/demos/simulator/msg/be-tarask.js
new file mode 100644
index 00000000000..5c8d72264a0
--- /dev/null
+++ b/demos/simulator/msg/be-tarask.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Код",
+ blocks: "Блёкі",
+ linkTooltip: "Захаваць і зьвязаць з блёкамі.",
+ runTooltip: "Запусьціце праграму, вызначаную блёкамі ў працоўнай вобласьці.",
+ badCode: "Памылка праграмы:\n%1",
+ timeout: "Перавышана максымальная колькасьць ітэрацыяў.",
+ trashTooltip: "Выдаліць усе блёкі.",
+ catLogic: "Лёгіка",
+ catLoops: "Петлі",
+ catMath: "Матэматычныя формулы",
+ catText: "Тэкст",
+ catLists: "Сьпісы",
+ catColour: "Колер",
+ catVariables: "Зьменныя",
+ catFunctions: "Функцыі",
+ listVariable: "сьпіс",
+ textVariable: "тэкст",
+ httpRequestError: "Узьнікла праблема з запытам.",
+ linkAlert: "Падзяліцца Вашым блёкам праз гэтую спасылку:\n\n%1",
+ hashError: "Прабачце, '%1' не адпавядае ніводнай захаванай праграме.",
+ xmlError: "Не атрымалася загрузіць захаваны файл. Магчыма, ён быў створаны з іншай вэрсіяй Блёклі?",
+ badXml: "Памылка сынтаксічнага аналізу XML:\n%1\n\nАбярыце \"ОК\", каб адмовіцца ад зьменаў ці \"Скасаваць\" для далейшага рэдагаваньня XML."
+};
diff --git a/demos/simulator/msg/br.js b/demos/simulator/msg/br.js
new file mode 100644
index 00000000000..321e28e58dc
--- /dev/null
+++ b/demos/simulator/msg/br.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kod",
+ blocks: "Bloc'hoù",
+ linkTooltip: "Enrollañ ha liammañ d'ar bloc'hadoù.",
+ runTooltip: "Lañsañ ar programm termenet gant ar bloc'hadoù en takad labour.",
+ badCode: "Fazi programm :\n%1",
+ timeout: "Tizhet eo bet an niver brasañ a iteradurioù seveniñ aotreet.",
+ trashTooltip: "Disteurel an holl vloc'hoù.",
+ catLogic: "Poell",
+ catLoops: "Boukloù",
+ catMath: "Matematik",
+ catText: "Testenn",
+ catLists: "Rolloù",
+ catColour: "Liv",
+ catVariables: "Argemmennoù",
+ catFunctions: "Arc'hwelioù",
+ listVariable: "roll",
+ textVariable: "testenn",
+ httpRequestError: "Ur gudenn zo gant ar reked.",
+ linkAlert: "Rannañ ho ploc'hoù gant al liamm-mañ :\n\n%1",
+ hashError: "Digarezit. \"%1\" ne glot gant programm enrollet ebet.",
+ xmlError: "Ne c'haller ket kargañ ho restr enrollet. Marteze e oa bet krouet gant ur stumm disheñvel eus Blockly ?",
+ badXml: "Fazi dielfennañ XML :\n%1\n\nDibabit \"Mat eo\" evit dilezel ar c'hemmoù-se pe \"Nullañ\" evit kemmañ an XML c'hoazh."
+};
diff --git a/demos/simulator/msg/ca.js b/demos/simulator/msg/ca.js
new file mode 100644
index 00000000000..2b6d9738eed
--- /dev/null
+++ b/demos/simulator/msg/ca.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Codi",
+ blocks: "Blocs",
+ linkTooltip: "Desa i enllaça als blocs.",
+ runTooltip: "Executa el programa definit pels blocs de l'àrea de treball.",
+ badCode: "Error de programa:\n %1",
+ timeout: "S'ha superat el nombre màxim d'iteracions d'execució.",
+ trashTooltip: "Descarta tots els blocs.",
+ catLogic: "Lògica",
+ catLoops: "Bucles",
+ catMath: "Matemàtiques",
+ catText: "Text",
+ catLists: "Llistes",
+ catColour: "Color",
+ catVariables: "Variables",
+ catFunctions: "Procediments",
+ listVariable: "llista",
+ textVariable: "text",
+ httpRequestError: "Hi ha hagut un problema amb la sol·licitud.",
+ linkAlert: "Comparteix els teus blocs amb aquest enllaç: %1",
+ hashError: "Ho sentim, '%1' no es correspon amb cap fitxer desat de Blockly.",
+ xmlError: "No s'ha pogut carregar el teu fitxer desat. Potser va ser creat amb una versió diferent de Blockly?",
+ badXml: "Error d'anàlisi XML:\n%1\n\nSeleccioneu 'Acceptar' per abandonar els vostres canvis, o 'Cancel·lar' per continuar editant l'XML."
+};
diff --git a/demos/simulator/msg/cs.js b/demos/simulator/msg/cs.js
new file mode 100644
index 00000000000..1026511e3a7
--- /dev/null
+++ b/demos/simulator/msg/cs.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kód",
+ blocks: "Bloky",
+ linkTooltip: "Ulož a spoj bloky..",
+ runTooltip: "",
+ badCode: "Chyba programu:\n%1",
+ timeout: "Maximum execution iterations exceeded.",
+ trashTooltip: "Zahodit všechny bloky.",
+ catLogic: "Logika",
+ catLoops: "Smyčky",
+ catMath: "Matematika",
+ catText: "Text",
+ catLists: "Seznamy",
+ catColour: "Barva",
+ catVariables: "Proměnné",
+ catFunctions: "Procedury",
+ listVariable: "seznam",
+ textVariable: "text",
+ httpRequestError: "Došlo k potížím s požadavkem.",
+ linkAlert: "Sdílej bloky tímto odkazem: \n\n%1",
+ hashError: "Omlouváme se, '%1' nesouhlasí s žádným z uložených souborů.",
+ xmlError: "Nepodařilo se uložit vás soubor. Pravděpodobně byl vytvořen jinou verzí Blockly?",
+ badXml: "Chyba parsování XML:\n%1\n\nVybrat \"OK\" pro zahození vašich změn nebo 'Cancel' k dalšímu upravování XML."
+};
diff --git a/demos/simulator/msg/da.js b/demos/simulator/msg/da.js
new file mode 100644
index 00000000000..089450b2db9
--- /dev/null
+++ b/demos/simulator/msg/da.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kode",
+ blocks: "Blokke",
+ linkTooltip: "Gem og link til blokke.",
+ runTooltip: "Kør programmet, der er defineret af blokkene i arbejdsområdet.",
+ badCode: "Programfejl:\n%1",
+ timeout: "Maksimale antal udførelsesgentagelser overskredet.",
+ trashTooltip: "Kassér alle blokke.",
+ catLogic: "Logik",
+ catLoops: "Løkker",
+ catMath: "Matematik",
+ catText: "Tekst",
+ catLists: "Lister",
+ catColour: "Farve",
+ catVariables: "Variabler",
+ catFunctions: "Funktioner",
+ listVariable: "liste",
+ textVariable: "tekst",
+ httpRequestError: "Der var et problem med forespørgslen.",
+ linkAlert: "Del dine blokke med dette link:\n\n%1",
+ hashError: "Beklager, '%1' passer ikke med nogen gemt Blockly fil.",
+ xmlError: "Kunne ikke hente din gemte fil. Måske er den lavet med en anden udgave af Blockly?",
+ badXml: "Fejl under fortolkningen af XML:\n%1\n\nVælg 'OK' for at opgive dine ændringer eller 'Afbryd' for at redigere XML-filen yderligere."
+};
diff --git a/demos/simulator/msg/de.js b/demos/simulator/msg/de.js
new file mode 100644
index 00000000000..422f9187d19
--- /dev/null
+++ b/demos/simulator/msg/de.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Code",
+ blocks: "Bausteine",
+ linkTooltip: "Speichern und auf Bausteine verlinken.",
+ runTooltip: "Das Programm ausführen, das von den Bausteinen im Arbeitsbereich definiert ist.",
+ badCode: "Programmfehler:\n%1",
+ timeout: "Die maximalen Ausführungswiederholungen wurden überschritten.",
+ trashTooltip: "Alle Bausteine verwerfen.",
+ catLogic: "Logik",
+ catLoops: "Schleifen",
+ catMath: "Mathematik",
+ catText: "Text",
+ catLists: "Listen",
+ catColour: "Farbe",
+ catVariables: "Variablen",
+ catFunctions: "Funktionen",
+ listVariable: "Liste",
+ textVariable: "Text",
+ httpRequestError: "Mit der Anfrage gab es ein Problem.",
+ linkAlert: "Teile deine Bausteine mit diesem Link:\n\n%1",
+ hashError: "„%1“ stimmt leider mit keinem gespeicherten Programm überein.",
+ xmlError: "Deine gespeicherte Datei konnte nicht geladen werden. Vielleicht wurde sie mit einer anderen Version von Blockly erstellt.",
+ badXml: "Fehler beim Parsen von XML:\n%1\n\nWähle 'OK' zum Verwerfen deiner Änderungen oder 'Abbrechen' zum weiteren Bearbeiten des XML."
+};
diff --git a/demos/simulator/msg/el.js b/demos/simulator/msg/el.js
new file mode 100644
index 00000000000..c63b7a6851f
--- /dev/null
+++ b/demos/simulator/msg/el.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Κώδικας",
+ blocks: "Μπλοκ",
+ linkTooltip: "Αποθηκεύει και συνδέει σε μπλοκ.",
+ runTooltip: "Εκτελεί το πρόγραμμα που ορίζεται από τα μπλοκ στον χώρο εργασίας.",
+ badCode: "Σφάλμα προγράμματος:\n%1",
+ timeout: "Υπέρβαση μέγιστου αριθμού επαναλήψεων.",
+ trashTooltip: "Απόρριψη όλων των μπλοκ.",
+ catLogic: "Λογική",
+ catLoops: "Επαναλήψεις",
+ catMath: "Μαθηματικά",
+ catText: "Κείμενο",
+ catLists: "Λίστες",
+ catColour: "Χρώμα",
+ catVariables: "Μεταβλητές",
+ catFunctions: "Συναρτήσεις",
+ listVariable: "λίστα",
+ textVariable: "κείμενο",
+ httpRequestError: "Υπήρξε πρόβλημα με το αίτημα.",
+ linkAlert: "Κοινοποίησε τα μπλοκ σου με αυτόν τον σύνδεσμο:\n\n%1",
+ hashError: "Λυπάμαι, το «%1» δεν αντιστοιχεί σε κανένα αποθηκευμένο πρόγραμμα.",
+ xmlError: "Δεν μπορώ να φορτώσω το αποθηκευμένο αρχείο σου. Μήπως δημιουργήθηκε από μία παλιότερη έκδοση του Blockly;",
+ badXml: "Σφάλμα ανάλυσης XML:\n%1\n\nΕπίλεξε «Εντάξει» για να εγκαταλείψεις τις αλλαγές σου ή «Ακύρωση» για να επεξεργαστείς το XML κι άλλο."
+};
diff --git a/demos/simulator/msg/en.js b/demos/simulator/msg/en.js
new file mode 100644
index 00000000000..d853826f0ae
--- /dev/null
+++ b/demos/simulator/msg/en.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Code",
+ blocks: "Blocks",
+ linkTooltip: "Save and link to blocks.",
+ runTooltip: "Run the program defined by the blocks in the workspace.",
+ badCode: "Program error:\n%1",
+ timeout: "Maximum execution iterations exceeded.",
+ trashTooltip: "Discard all blocks.",
+ catLogic: "Logic",
+ catLoops: "Loops",
+ catMath: "Math",
+ catText: "Text",
+ catLists: "Lists",
+ catColour: "Colour",
+ catVariables: "Variables",
+ catFunctions: "Functions",
+ listVariable: "list",
+ textVariable: "text",
+ httpRequestError: "There was a problem with the request.",
+ linkAlert: "Share your blocks with this public link. We\'ll delete them if not used for a year. They are not associated with your account and handled as per Google\'s Privacy Policy. Please be sure not to include any private information.:\n\n%1",
+ hashError: "Sorry, '%1' doesn't correspond with any saved program.",
+ xmlError: "Could not load your saved file. Perhaps it was created with a different version of Blockly?",
+ badXml: "Error parsing XML:\n%1\n\nSelect 'OK' to abandon your changes or 'Cancel' to further edit the XML."
+};
diff --git a/demos/simulator/msg/es.js b/demos/simulator/msg/es.js
new file mode 100644
index 00000000000..24358e368df
--- /dev/null
+++ b/demos/simulator/msg/es.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Código",
+ blocks: "Bloques",
+ linkTooltip: "Guarda conexión a los bloques.",
+ runTooltip: "Ejecute el programa definido por los bloques en el área de trabajo.",
+ badCode: "Error del programa:\n%1",
+ timeout: "Se excedio el máximo de iteraciones ejecutadas permitidas.",
+ trashTooltip: "Descartar todos los bloques.",
+ catLogic: "Lógica",
+ catLoops: "Secuencias",
+ catMath: "Matemáticas",
+ catText: "Texto",
+ catLists: "Listas",
+ catColour: "Color",
+ catVariables: "Variables",
+ catFunctions: "Funciones",
+ listVariable: "lista",
+ textVariable: "texto",
+ httpRequestError: "Hubo un problema con la petición.",
+ linkAlert: "Comparte tus bloques con este enlace:\n\n%1",
+ hashError: "«%1» no corresponde con ningún programa guardado.",
+ xmlError: "No se pudo cargar el archivo guardado. ¿Quizá fue creado con otra versión de Blockly?",
+ badXml: "Error de análisis XML:\n%1\n\nSelecciona OK para abandonar tus cambios o Cancelar para seguir editando el XML."
+};
diff --git a/demos/simulator/msg/et.js b/demos/simulator/msg/et.js
new file mode 100644
index 00000000000..320c3917426
--- /dev/null
+++ b/demos/simulator/msg/et.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kood",
+ blocks: "Plokid",
+ linkTooltip: "Salvesta ja tekita link plokkidele.",
+ runTooltip: "Käivita töölaual olevate plokkidega defineeritud programm.",
+ badCode: "Viga programmis:\n%1",
+ timeout: "Käivitatavate iteratsioonide maksimaalne arv on ületatud.",
+ trashTooltip: "Eemalda kõik plokid.",
+ catLogic: "Loogika",
+ catLoops: "Kordus",
+ catMath: "Matemaatika",
+ catText: "Tekst",
+ catLists: "Loendid",
+ catColour: "Värv",
+ catVariables: "Muutujad",
+ catFunctions: "Funktsioonid",
+ listVariable: "loend",
+ textVariable: "tekst",
+ httpRequestError: "Probleem päringuga.",
+ linkAlert: "Oma plokke saad jagada selle lingiga:\n\n%1",
+ hashError: "Vabandust, kuid '%1' ei vasta ühelegi salvestatud programmile.",
+ xmlError: "Su salvestatud faili ei õnnestunud laadida. Võibolla on see loodud mõne teise Blockly versiooniga?",
+ badXml: "Viga XML-i parsimisel:\n%1\n\nTehtud muudatustest loobumiseks vajuta 'OK', XML-i muudatuste tegemise jätkamiseks 'Katkesta'."
+};
diff --git a/demos/simulator/msg/fa.js b/demos/simulator/msg/fa.js
new file mode 100644
index 00000000000..96dd966b003
--- /dev/null
+++ b/demos/simulator/msg/fa.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "کد",
+ blocks: "بلوکها",
+ linkTooltip: "ذخیره و پیوند به بلوکها.",
+ runTooltip: "اجرای برنامهٔ تعریفشده توسط بلوکها در فضای کار.",
+ badCode: "خطای برنامه:\n%1",
+ timeout: "حداکثر تکرارهای اجرا رد شدهاست.",
+ trashTooltip: "دورریختن همهٔ بلوکها.",
+ catLogic: "منطق",
+ catLoops: "حلقهها",
+ catMath: "ریاضی",
+ catText: "متن",
+ catLists: "فهرستها",
+ catColour: "رنگ",
+ catVariables: "متغییرها",
+ catFunctions: "توابع",
+ listVariable: "فهرست",
+ textVariable: "متن",
+ httpRequestError: "مشکلی با درخواست وجود داشت.",
+ linkAlert: "اشتراکگذاری بلاکهایتان با این پیوند:\n\n%1",
+ hashError: "شرمنده، «%1» با هیچ برنامهٔ ذخیرهشدهای تطبیق پیدا نکرد.",
+ xmlError: "نتوانست پروندهٔ ذخیرهٔ شما بارگیری شود. احتمالاً با نسخهٔ متفاوتی از بلوکی درست شدهاست؟",
+ badXml: "خطای تجزیهٔ اکسامال:\n%1\n\n«باشد» را برای ذخیره و «فسخ» را برای ویرایش بیشتر اکسامال انتخاب کنید."
+};
diff --git a/demos/simulator/msg/fr.js b/demos/simulator/msg/fr.js
new file mode 100644
index 00000000000..60181131adb
--- /dev/null
+++ b/demos/simulator/msg/fr.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Code",
+ blocks: "Blocs",
+ linkTooltip: "Sauvegarder et lier aux blocs.",
+ runTooltip: "Lancer le programme défini par les blocs dans l’espace de travail.",
+ badCode: "Erreur du programme :\n%1",
+ timeout: "Nombre maximum d’itérations d’exécution dépassé.",
+ trashTooltip: "Jeter tous les blocs.",
+ catLogic: "Logique",
+ catLoops: "Boucles",
+ catMath: "Math",
+ catText: "Texte",
+ catLists: "Listes",
+ catColour: "Couleur",
+ catVariables: "Variables",
+ catFunctions: "Fonctions",
+ listVariable: "liste",
+ textVariable: "texte",
+ httpRequestError: "Il y a eu un problème avec la demande.",
+ linkAlert: "Partagez vos blocs grâce à ce lien:\n\n%1",
+ hashError: "Désolé, '%1' ne correspond à aucun programme sauvegardé.",
+ xmlError: "Impossible de charger le fichier de sauvegarde. Peut être a t-il été créé avec une autre version de Blockly?",
+ badXml: "Erreur d’analyse du XML :\n%1\n\nSélectionner 'OK' pour abandonner vos modifications ou 'Annuler' pour continuer à modifier le XML."
+};
diff --git a/demos/simulator/msg/he.js b/demos/simulator/msg/he.js
new file mode 100644
index 00000000000..dc2826620db
--- /dev/null
+++ b/demos/simulator/msg/he.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "קוד",
+ blocks: "קטעי קוד",
+ linkTooltip: "שמירה וקישור לקטעי קוד.",
+ runTooltip: "הרצת התכנית שהוגדרה על ידי קטעי הקוד שבמרחב העבודה.",
+ badCode: "שגיאה בתכנית: %1",
+ timeout: "חריגה ממספר פעולות חוזרות אפשריות.",
+ trashTooltip: "השלך את כל קטעי הקוד.",
+ catLogic: "לוגיקה",
+ catLoops: "לולאות",
+ catMath: "מתמטיקה",
+ catText: "טקסט",
+ catLists: "רשימות",
+ catColour: "צבע",
+ catVariables: "משתנים",
+ catFunctions: "פונקציות",
+ listVariable: "רשימה",
+ textVariable: "טקסט",
+ httpRequestError: "הבקשה נכשלה.",
+ linkAlert: "ניתן לשתף את קטעי הקוד שלך באמצעות קישור זה:\n\n%1",
+ hashError: "לצערנו, '%1' איננו מתאים לאף אחת מהתוכניות השמורות",
+ xmlError: "נסיון הטעינה של הקובץ השמור שלך נכשל. האם ייתכן שהוא נוצר בגרסא שונה של בלוקלי?",
+ badXml: "תקלה בפענוח XML:\n\n%1\n\nנא לבחור 'אישור' כדי לנטוש את השינויים שלך או 'ביטול' כדי להמשיך ולערוך את ה־XML."
+};
diff --git a/demos/simulator/msg/hrx.js b/demos/simulator/msg/hrx.js
new file mode 100644
index 00000000000..ca0dea3fc3c
--- /dev/null
+++ b/demos/simulator/msg/hrx.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Code",
+ blocks: "Bausten",
+ linkTooltip: "Speichre und auf Bausten verlinke.",
+ runTooltip: "Das Programm ausfüahre, das von den Bausten im Oorweitsbereich definiert ist.",
+ badCode: "Programmfehler:\n%1",
+ timeout: "Die maximale Ausführungswiederholunge woore üwerschritt.",
+ trashTooltip: "All Bausten verwerfe.",
+ catLogic: "Logik",
+ catLoops: "Schleife",
+ catMath: "Mathematik",
+ catText: "Text",
+ catLists: "Liste",
+ catColour: "Farreb",
+ catVariables: "Variable",
+ catFunctions: "Funktione",
+ listVariable: "List",
+ textVariable: "Text",
+ httpRequestError: "Mit der Oonfroch hots en Problem geb.",
+ linkAlert: "Tel von dein Bausten mit dem Link:\n\n%1",
+ hashError: "„%1“ stimmt leider mit kenem üweren gespeicherte Programm.",
+ xmlError: "Dein gespeicherte Datei könnt net gelood sin. Vielleicht woard se mit ener annre Version von Blockly erstellt.",
+ badXml: "Fehler beim Parse von XML:\n%1\n\nWähle 'OK' zum Verwerfe von deiner Ändrunge orrer 'Abbreche' zum XML weiter beoorbeite."
+};
diff --git a/demos/simulator/msg/hu.js b/demos/simulator/msg/hu.js
new file mode 100644
index 00000000000..eee1868634a
--- /dev/null
+++ b/demos/simulator/msg/hu.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kódszerkesztő",
+ blocks: "Blokkok",
+ linkTooltip: "Hivatkozás létrehozása",
+ runTooltip: "Program futtatása.",
+ badCode: "Program hiba:\n%1",
+ timeout: "A program elérte a maximális végrehajtási időt.",
+ trashTooltip: "Összes blokk törlése.",
+ catLogic: "Logikai műveletek",
+ catLoops: "Ciklusok",
+ catMath: "Matematikai műveletek",
+ catText: "Sztring műveletek",
+ catLists: "Listakezelés",
+ catColour: "Színek",
+ catVariables: "Változók",
+ catFunctions: "Eljárások",
+ listVariable: "lista",
+ textVariable: "szöveg",
+ httpRequestError: "A kéréssel kapcsolatban probléma merült fel.",
+ linkAlert: "Ezzel a hivatkozással tudod megosztani a programodat:\n\n%1",
+ hashError: "Sajnos a '%1' hivatkozás nem tartozik egyetlen programhoz sem.",
+ xmlError: "A programodat nem lehet betölteni. Elképzelhető, hogy a Blockly egy másik verziójában készült?",
+ badXml: "Hiba az XML feldolgozásakor:\n%1\n\nVáltozások elvetése?"
+};
diff --git a/demos/simulator/msg/ia.js b/demos/simulator/msg/ia.js
new file mode 100644
index 00000000000..5938458bb93
--- /dev/null
+++ b/demos/simulator/msg/ia.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Codice",
+ blocks: "Blocos",
+ linkTooltip: "Salveguardar e ligar a blocos.",
+ runTooltip: "Executar le programma definite per le blocos in le spatio de travalio.",
+ badCode: "Error del programma:\n%1",
+ timeout: "Le numero de iterationes executate ha excedite le maximo.",
+ trashTooltip: "Abandonar tote le blocos.",
+ catLogic: "Logica",
+ catLoops: "Buclas",
+ catMath: "Mathematica",
+ catText: "Texto",
+ catLists: "Listas",
+ catColour: "Color",
+ catVariables: "Variabiles",
+ catFunctions: "Functiones",
+ listVariable: "lista",
+ textVariable: "texto",
+ httpRequestError: "Il habeva un problema con le requesta.",
+ linkAlert: "Divide tu blocos con iste ligamine:\n\n%1",
+ hashError: "Infelicemente, '%1' non corresponde a alcun programma salveguardate.",
+ xmlError: "Impossibile cargar le file salveguardate. Pote esser que illo ha essite create con un altere version de Blockly?",
+ badXml: "Error de analyse del XML:\n%1\n\nSelige 'OK' pro abandonar le modificationes o 'Cancellar' pro continuar a modificar le codice XML."
+};
diff --git a/demos/simulator/msg/is.js b/demos/simulator/msg/is.js
new file mode 100644
index 00000000000..fa5c40d80a3
--- /dev/null
+++ b/demos/simulator/msg/is.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kóði",
+ blocks: "Kubbar",
+ linkTooltip: "Vista og tengja við kubba.",
+ runTooltip: "Keyra forritið sem kubbarnir á vinnusvæðinu mynda.",
+ badCode: "Villa í forriti:\n%1",
+ timeout: "Forritið hefur endurtekið sig of oft.",
+ trashTooltip: "Fleygja öllum kubbum.",
+ catLogic: "Rökvísi",
+ catLoops: "Lykkjur",
+ catMath: "Reikningur",
+ catText: "Texti",
+ catLists: "Listar",
+ catColour: "Litir",
+ catVariables: "Breytur",
+ catFunctions: "Stefjur",
+ listVariable: "listi",
+ textVariable: "texti",
+ httpRequestError: "Það kom upp vandamál með beiðnina.",
+ linkAlert: "Deildu kubbunum þínum með þessari krækju:",
+ hashError: "Því miður, '%1' passar ekki við neitt vistað forrit.",
+ xmlError: "Gat ekki hlaðið vistuðu skrána þína. Var hún kannske búin til í annarri útgáfu af Blockly?",
+ badXml: "Villa við úrvinnslu XML:\n%1\n\nVeldu 'Í lagi' til að sleppa breytingum eða 'Hætta við' til að halda áfram með XML."
+};
diff --git a/demos/simulator/msg/it.js b/demos/simulator/msg/it.js
new file mode 100644
index 00000000000..cd69b66b318
--- /dev/null
+++ b/demos/simulator/msg/it.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Codice",
+ blocks: "Blocchi",
+ linkTooltip: "Salva e collega ai blocchi.",
+ runTooltip: "Esegui il programma definito dai blocchi nell'area di lavoro.",
+ badCode: "Errore programma:\n%1",
+ timeout: "È stato superato il numero massimo consentito di interazioni eseguite.",
+ trashTooltip: "Elimina tutti i blocchi.",
+ catLogic: "Logica",
+ catLoops: "Cicli",
+ catMath: "Matematica",
+ catText: "Testo",
+ catLists: "Elenchi",
+ catColour: "Colore",
+ catVariables: "Variabili",
+ catFunctions: "Funzioni",
+ listVariable: "elenco",
+ textVariable: "testo",
+ httpRequestError: "La richiesta non è stata soddisfatta.",
+ linkAlert: "Condividi i tuoi blocchi con questo collegamento:\n\n%1",
+ hashError: "Mi spiace, '%1' non corrisponde ad alcun programma salvato.",
+ xmlError: "Non è stato possibile caricare il documento. Forse è stato creato con una versione diversa di Blockly?",
+ badXml: "Errore durante l'analisi XML:\n%1\n\nSeleziona 'OK' per abbandonare le modifiche o 'Annulla' per continuare a modificare l'XML."
+};
diff --git a/demos/simulator/msg/ja.js b/demos/simulator/msg/ja.js
new file mode 100644
index 00000000000..6d50419825d
--- /dev/null
+++ b/demos/simulator/msg/ja.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "コード",
+ blocks: "ブロック",
+ linkTooltip: "ブロックの状態を保存してリンクを取得します。",
+ runTooltip: "ブロックで作成したプログラムを実行します。",
+ badCode: "プログラムのエラー:\n%1",
+ timeout: "命令の実行回数が制限値を超えました。",
+ trashTooltip: "すべてのブロックを消します。",
+ catLogic: "論理",
+ catLoops: "繰り返し",
+ catMath: "数学",
+ catText: "テキスト",
+ catLists: "リスト",
+ catColour: "色",
+ catVariables: "変数",
+ catFunctions: "関数",
+ listVariable: "リスト",
+ textVariable: "テキスト",
+ httpRequestError: "ネットワーク接続のエラーです。",
+ linkAlert: "ブロックの状態をこのリンクで共有できます:\n\n%1",
+ hashError: "すみません。「%1」という名前のプログラムは保存されていません。",
+ xmlError: "保存されたファイルを読み込めませんでした。別のバージョンのブロックリーで作成された可能性があります。",
+ badXml: "XML のエラーです:\n%1\n\nXML の変更をやめるには「OK」、編集を続けるには「キャンセル」を選んでください。"
+};
diff --git a/demos/simulator/msg/kab.js b/demos/simulator/msg/kab.js
new file mode 100644
index 00000000000..648ff7fb837
--- /dev/null
+++ b/demos/simulator/msg/kab.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Tangalt",
+ blocks: "Iḥedran",
+ linkTooltip: "Sekles sakin cudd ar iḥedran.",
+ runTooltip: "Senker ahil i d-sbadun iḥedran di tallunt n umahil.",
+ badCode: "Tuccḍa deg ahil :\n%1",
+ timeout: "Amḍan afellay n wallusen n uselkem iɛeddan.",
+ trashTooltip: "Ḍeggeṛ akk iḥedran.",
+ catLogic: "Tameẓla",
+ catLoops: "Tineddicin",
+ catMath: "Tusnakt",
+ catText: "Aḍris",
+ catLists: "Tibdarin",
+ catColour: "Ini",
+ catVariables: "Imuttiyen",
+ catFunctions: "Tiwiriwin",
+ listVariable: "tabdart",
+ textVariable: "aḍris",
+ httpRequestError: "yella ugur deg usuter.",
+ linkAlert: "Bḍu iḥedran-ik s useqdec n useɣwen-agi:\n\n%1",
+ hashError: "Suref-aɣ, '%1' ur imenṭaḍ ulad d yiwen n wahil yettwaskelsen.",
+ xmlError: "Ur izmir ara ad d-isali afaylu n usekles. Ahatyettwarna s lqem-nniḍen n Blockly?",
+ badXml: "Tuccḍa di tesleḍt n XML :\n%1\n\nFren 'IH' akken ad tzgleḍ asnifel-ik 'Sefsex' akken ad tkemmleḍ asnifel n XML."
+};
diff --git a/demos/simulator/msg/ko.js b/demos/simulator/msg/ko.js
new file mode 100644
index 00000000000..990cb6f236b
--- /dev/null
+++ b/demos/simulator/msg/ko.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "코드",
+ blocks: "블록",
+ linkTooltip: "블록을 저장하고 링크를 가져옵니다.",
+ runTooltip: "작업 공간에서 블록으로 정의된 프로그램을 실행합니다.",
+ badCode: "프로그램 오류:\n%1",
+ timeout: "최대 실행 반복을 초과했습니다.",
+ trashTooltip: "모든 블록을 버립니다.",
+ catLogic: "논리",
+ catLoops: "반복",
+ catMath: "수학",
+ catText: "텍스트",
+ catLists: "목록",
+ catColour: "색",
+ catVariables: "변수",
+ catFunctions: "기능",
+ listVariable: "목록",
+ textVariable: "텍스트",
+ httpRequestError: "요청에 문제가 있습니다.",
+ linkAlert: "다음 링크로 블록을 공유하세요:\n\n%1",
+ hashError: "죄송하지만 '%1'은 어떤 저장된 프로그램으로 일치하지 않습니다.",
+ xmlError: "저장된 파일을 불러올 수 없습니다. 혹시 블록리의 다른 버전으로 만들었습니까?",
+ badXml: "XML 구문 분석 오류:\n%1\n\n바뀜을 포기하려면 '확인'을 선택하고 XML을 더 편집하려면 '취소'를 선택하세요."
+};
diff --git a/demos/simulator/msg/mk.js b/demos/simulator/msg/mk.js
new file mode 100644
index 00000000000..cc344a69ca2
--- /dev/null
+++ b/demos/simulator/msg/mk.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Код",
+ blocks: "Блокчиња",
+ linkTooltip: "Зачувај и стави врска до блокчињата.",
+ runTooltip: "Пушти го програмот определен од блокчињата во работниот простор.",
+ badCode: "Грешка во програмот:\n%1",
+ timeout: "Го надминавте допуштениот број на повторувања во извршувањето.",
+ trashTooltip: "Отстрани ги сите блокчиња.",
+ catLogic: "Логика",
+ catLoops: "Јамки",
+ catMath: "Математика",
+ catText: "Текст",
+ catLists: "Списоци",
+ catColour: "Боја",
+ catVariables: "Променливи",
+ catFunctions: "Функции",
+ listVariable: "список",
+ textVariable: "текст",
+ httpRequestError: "Се појави проблем во барањето.",
+ linkAlert: "Споделете ги вашите блокчиња со оваа врска:\n\n%1",
+ hashError: "„%1“ не одговара на ниеден зачуван програм.",
+ xmlError: "Не можев да ја вчитам зачуваната податотека. Да не сте ја создале со друга верзија на Blockly?",
+ badXml: "Грешка при расчленувањето на XML:\n%1\n\nСтиснете на „ОК“ за да ги напуштите промените или на „Откажи“ ако сакате уште да ја уредувате XML-податотеката."
+};
diff --git a/demos/simulator/msg/ms.js b/demos/simulator/msg/ms.js
new file mode 100644
index 00000000000..87ee8deef40
--- /dev/null
+++ b/demos/simulator/msg/ms.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kod",
+ blocks: "Blok",
+ linkTooltip: "Simpan dan pautkan kepada blok.",
+ runTooltip: "Jalankan aturcara yang ditetapkan oleh blok-blok di dalam ruang kerja.",
+ badCode: "Ralat aturcara:\n%1",
+ timeout: "Takat maksimum lelaran pelaksanaan dicecah.",
+ trashTooltip: "Buang semua Blok.",
+ catLogic: "Logik",
+ catLoops: "Gelung",
+ catMath: "Matematik",
+ catText: "Teks",
+ catLists: "Senarai",
+ catColour: "Warna",
+ catVariables: "Pemboleh ubah",
+ catFunctions: "Fungsi",
+ listVariable: "senarai",
+ textVariable: "teks",
+ httpRequestError: "Permintaan itu terdapat masalah.",
+ linkAlert: "Kongsikan blok-blok anda dengan pautan ini:\n\n%1",
+ hashError: "Maaf, '%1' tidak berpadanan dengan sebarang aturcara yang disimpan.",
+ xmlError: "Fail simpanan anda tidak dapat dimuatkan. Jangan-jangan ia dicipta dengan versi Blockly yang berlainan?",
+ badXml: "Ralat ketika menghuraikan XML:\n%1\n\nPilih 'OK' untuk melucutkan suntingan anda atau 'Batal' untuk bersambung menyunting XML-nya."
+};
diff --git a/demos/simulator/msg/nb.js b/demos/simulator/msg/nb.js
new file mode 100644
index 00000000000..60ac0394d63
--- /dev/null
+++ b/demos/simulator/msg/nb.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kode",
+ blocks: "Blokker",
+ linkTooltip: "Lagre og lenke til blokker.",
+ runTooltip: "Kjør programmet definert av blokken i arbeidsområdet.",
+ badCode: "Programfeil:\n%1",
+ timeout: "Det maksimale antallet utførte looper er oversteget.",
+ trashTooltip: "Fjern alle blokker",
+ catLogic: "Logikk",
+ catLoops: "Looper",
+ catMath: "Matte",
+ catText: "Tekst",
+ catLists: "Lister",
+ catColour: "Farge",
+ catVariables: "Variabler",
+ catFunctions: "Funksjoner",
+ listVariable: "Liste",
+ textVariable: "Tekst",
+ httpRequestError: "Det oppsto et problem med forespørselen din",
+ linkAlert: "Del dine blokker med denne lenken:\n\n%1",
+ hashError: "Beklager, '%1' samsvarer ikke med noe lagret program.",
+ xmlError: "Kunne ikke laste inn filen. Kanskje den ble laget med en annen versjon av Blockly?",
+ badXml: "Feil ved parsering av XML:\n%1\n\nVelg 'OK' for å avbryte endringene eller 'Cancel' for å fortsette å redigere XML-koden."
+};
diff --git a/demos/simulator/msg/nl.js b/demos/simulator/msg/nl.js
new file mode 100644
index 00000000000..339a5e8827b
--- /dev/null
+++ b/demos/simulator/msg/nl.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Code",
+ blocks: "Blokken",
+ linkTooltip: "Opslaan en koppelen naar blokken.",
+ runTooltip: "Voer het programma uit dat met de blokken in de werkruimte is gemaakt.",
+ badCode: "Programmafout:\n%1",
+ timeout: "Het maximale aantal iteraties is overschreden.",
+ trashTooltip: "Alle blokken verwijderen",
+ catLogic: "Logica",
+ catLoops: "Lussen",
+ catMath: "Formules",
+ catText: "Tekst",
+ catLists: "Lijsten",
+ catColour: "Kleur",
+ catVariables: "Variabelen",
+ catFunctions: "Functies",
+ listVariable: "lijst",
+ textVariable: "tekst",
+ httpRequestError: "Er is een probleem opgetreden tijdens het verwerken van het verzoek.",
+ linkAlert: "Deel uw blokken via deze koppeling:\n\n%1",
+ hashError: "\"%1\" komt helaas niet overeen met een opgeslagen bestand.",
+ xmlError: "Uw opgeslagen bestand kan niet geladen worden. Is het misschien gemaakt met een andere versie van Blockly?",
+ badXml: "Fout tijdens het verwerken van de XML:\n%1\n\nSelecteer \"OK\" om uw wijzigingen te negeren of \"Annuleren\" om de XML verder te bewerken."
+};
diff --git a/demos/simulator/msg/oc.js b/demos/simulator/msg/oc.js
new file mode 100644
index 00000000000..c184ac8fb27
--- /dev/null
+++ b/demos/simulator/msg/oc.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Còde",
+ blocks: "Blòts",
+ linkTooltip: "Salva e liga als blòts.",
+ runTooltip: "Aviar lo programa definit pels blòts dins l’espaci de trabalh.",
+ badCode: "Error del programa :\n%1",
+ timeout: "Nombre maximum d’iteracions d’execucion depassat.",
+ trashTooltip: "Getar totes los blòts.",
+ catLogic: "Logic",
+ catLoops: "Boclas",
+ catMath: "Math",
+ catText: "Tèxte",
+ catLists: "Listas",
+ catColour: "Color",
+ catVariables: "Variablas",
+ catFunctions: "Foncions",
+ listVariable: "lista",
+ textVariable: "tèxte",
+ httpRequestError: "I a agut un problèma amb la demanda.",
+ linkAlert: "Partejatz vòstres blòts gràcia a aqueste ligam :\n\n%1",
+ hashError: "O planhèm, '%1' correspond pas a un fichièr Blockly salvament.",
+ xmlError: "Impossible de cargar lo fichièr de salvament. Benlèu qu'es estat creat amb una autra version de Blockly ?",
+ badXml: "Error d’analisi del XML :\n%1\n\nSeleccionar 'D'acòrdi' per abandonar vòstras modificacions o 'Anullar' per modificar encara lo XML."
+};
diff --git a/demos/simulator/msg/pl.js b/demos/simulator/msg/pl.js
new file mode 100644
index 00000000000..83bb05d2f9f
--- /dev/null
+++ b/demos/simulator/msg/pl.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kod",
+ blocks: "Bloki",
+ linkTooltip: "Zapisz i podlinkuj do bloków",
+ runTooltip: "Uruchom program zdefinowany przez bloki w obszarze roboczym",
+ badCode: "Błąd programu:\n%1",
+ timeout: "Maksymalna liczba iteracji wykonywań przekroczona",
+ trashTooltip: "Odrzuć wszystkie bloki.",
+ catLogic: "Logika",
+ catLoops: "Pętle",
+ catMath: "Matematyka",
+ catText: "Tekst",
+ catLists: "Listy",
+ catColour: "Kolor",
+ catVariables: "Zmienne",
+ catFunctions: "Funkcje",
+ listVariable: "lista",
+ textVariable: "tekst",
+ httpRequestError: "Wystąpił problem z żądaniem.",
+ linkAlert: "Udpostępnij swoje bloki korzystając z poniższego linku : \n\n\n%1",
+ hashError: "Przepraszamy, \"%1\" nie odpowiada żadnemu zapisanemu programowi.",
+ xmlError: "Nie można załadować zapisanego pliku. Być może został utworzony za pomocą innej wersji Blockly?",
+ badXml: "Błąd parsowania XML : \n%1\n\nZaznacz 'OK' aby odrzucić twoje zmiany lub 'Cancel', żeby w przyszłości edytować XML."
+};
diff --git a/demos/simulator/msg/pms.js b/demos/simulator/msg/pms.js
new file mode 100644
index 00000000000..a7704f764d0
--- /dev/null
+++ b/demos/simulator/msg/pms.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Còdes",
+ blocks: "Blòch",
+ linkTooltip: "Argistré e lijé ai blòch.",
+ runTooltip: "Fé andé ël programa definì dai blòch ant lë spassi ëd travaj.",
+ badCode: "Eror dël programa:\n%1",
+ timeout: "Nùmer màssim d'arpetission d'esecussion sorpassà.",
+ trashTooltip: "Scarté tuti ij blòch.",
+ catLogic: "Lògica",
+ catLoops: "Liasse",
+ catMath: "Matemàtica",
+ catText: "Test",
+ catLists: "Liste",
+ catColour: "Color",
+ catVariables: "Variàbij",
+ catFunctions: "Fonsion",
+ listVariable: "lista",
+ textVariable: "test",
+ httpRequestError: "A-i é staje un problema con l'arcesta.",
+ linkAlert: "Ch'a partagia ij sò blòch grassie a sta liura: %1",
+ hashError: "An dëspias, '%1 a corëspond a gnun programa salvà.",
+ xmlError: "A l'é nen podusse carié so archivi salvà. Miraco a l'é stàit creà con na version diferenta ëd Blockly?",
+ badXml: "Eror d'anàlisi dl'XML:\n%1\n\nSelessioné 'Va bin' për lassé perde toe modìfiche o 'Anulé' për modifiché ancora l'XML."
+};
diff --git a/demos/simulator/msg/pt-br.js b/demos/simulator/msg/pt-br.js
new file mode 100644
index 00000000000..9b18d636006
--- /dev/null
+++ b/demos/simulator/msg/pt-br.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Código",
+ blocks: "Blocos",
+ linkTooltip: "Salvar e ligar aos blocos.",
+ runTooltip: "Execute o programa definido pelos blocos na área de trabalho.",
+ badCode: "Erro no programa:\n%1",
+ timeout: "Máximo de iterações de execução excedido.",
+ trashTooltip: "Descartar todos os blocos.",
+ catLogic: "Lógica",
+ catLoops: "Laços",
+ catMath: "Matemática",
+ catText: "Texto",
+ catLists: "Listas",
+ catColour: "Cor",
+ catVariables: "Variáveis",
+ catFunctions: "Funções",
+ listVariable: "lista",
+ textVariable: "texto",
+ httpRequestError: "Houve um problema com a requisição.",
+ linkAlert: "Compartilhe seus blocos com este link:\n\n%1",
+ hashError: "Desculpe, '%1' não corresponde a um programa salvo.",
+ xmlError: "Não foi possível carregar seu arquivo salvo. Talvez ele tenha sido criado com uma versão diferente do Blockly?",
+ badXml: "Erro de análise XML:\n%1\n\nSelecione 'OK' para abandonar suas mudanças ou 'Cancelar' para editar o XML."
+};
diff --git a/demos/simulator/msg/ro.js b/demos/simulator/msg/ro.js
new file mode 100644
index 00000000000..cd3de11d790
--- /dev/null
+++ b/demos/simulator/msg/ro.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Cod",
+ blocks: "Blocuri",
+ linkTooltip: "Salvează și adaugă la blocuri.",
+ runTooltip: "Execută programul definit de către blocuri în spațiul de lucru.",
+ badCode: "Eroare de program:\n%1",
+ timeout: "Numărul maxim de iterații a fost depășit.",
+ trashTooltip: "Șterge toate blocurile.",
+ catLogic: "Logic",
+ catLoops: "Bucle",
+ catMath: "Matematică",
+ catText: "Text",
+ catLists: "Liste",
+ catColour: "Culoare",
+ catVariables: "Variabile",
+ catFunctions: "Funcții",
+ listVariable: "listă",
+ textVariable: "text",
+ httpRequestError: "A apărut o problemă la solicitare.",
+ linkAlert: "Distribuie-ți blocurile folosind această legătură:\n\n%1",
+ hashError: "Scuze, „%1” nu corespunde nici unui program salvat.",
+ xmlError: "Sistemul nu a putut încărca fișierul salvat. Poate că a fost creat cu o altă versiune de Blockly?",
+ badXml: "Eroare de parsare XML:\n%1\n\nAlege „OK” pentru a renunța la modificările efectuate sau „Revocare” pentru a modifica în continuare fișierul XML."
+};
diff --git a/demos/simulator/msg/ru.js b/demos/simulator/msg/ru.js
new file mode 100644
index 00000000000..389e9052a33
--- /dev/null
+++ b/demos/simulator/msg/ru.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Код",
+ blocks: "Блоки",
+ linkTooltip: "Сохранить и показать ссылку на блоки.",
+ runTooltip: "Запустить программу, заданную блоками в рабочей области.",
+ badCode: "Ошибка программы:\n%1",
+ timeout: "Превышено максимальное количество итераций.",
+ trashTooltip: "Удалить все блоки.",
+ catLogic: "Логические",
+ catLoops: "Циклы",
+ catMath: "Математика",
+ catText: "Текст",
+ catLists: "Списки",
+ catColour: "Цвет",
+ catVariables: "Переменные",
+ catFunctions: "Функции",
+ listVariable: "список",
+ textVariable: "текст",
+ httpRequestError: "Произошла проблема при запросе.",
+ linkAlert: "Поделитесь своими блоками по этой ссылке:\n\n%1",
+ hashError: "К сожалению, «%1» не соответствует ни одному сохраненному файлу Блокли.",
+ xmlError: "Не удалось загрузить ваш сохраненный файл. Возможно, он был создан в другой версии Блокли?",
+ badXml: "Ошибка синтаксического анализа XML:\n%1\n\nВыберите 'ОК', чтобы отказаться от изменений или 'Cancel' для дальнейшего редактирования XML."
+};
diff --git a/demos/simulator/msg/sc.js b/demos/simulator/msg/sc.js
new file mode 100644
index 00000000000..82f94d61d05
--- /dev/null
+++ b/demos/simulator/msg/sc.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Còdixi",
+ blocks: "Brocus",
+ linkTooltip: "Sarva e alliòngia a is brocus.",
+ runTooltip: "Arròllia su programa cumpostu de is brocus in s'àrea de traballu.",
+ badCode: "Errori in su Programa:\n%1",
+ timeout: "Giai lòmpius a su màssimu numeru de repicus.",
+ trashTooltip: "Boganci totu is brocus.",
+ catLogic: "Lògica",
+ catLoops: "Lòrigas",
+ catMath: "Matemàtica",
+ catText: "Testu",
+ catLists: "Lista",
+ catColour: "Colori",
+ catVariables: "Variabilis",
+ catFunctions: "Funtzionis",
+ listVariable: "lista",
+ textVariable: "testu",
+ httpRequestError: "Ddui fut unu problema cun sa pregunta",
+ linkAlert: "Poni is brocus tuus in custu acàpiu:\n\n%1",
+ hashError: "Mi dispraxit, '%1' non torrat a pari cun nimancu unu de is programas sarvaus.",
+ xmlError: "Non potzu carrigai su file sarvau. Fortzis est stètiu fatu cun d-una versioni diferenti de Blockly?",
+ badXml: "Errori in s'anàlisi XML:\n%1\n\nCraca 'OK' po perdi is mudàntzias 'Anudda' po sighì a scriri su XML."
+};
diff --git a/demos/simulator/msg/sk.js b/demos/simulator/msg/sk.js
new file mode 100644
index 00000000000..3917df293ee
--- /dev/null
+++ b/demos/simulator/msg/sk.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kód",
+ blocks: "Bloky",
+ linkTooltip: "Uložiť a zdieľať odkaz na tento program.",
+ runTooltip: "Spustiť program, zložený z dielcov na pracovnej ploche.",
+ badCode: "Chyba v programe:\n%1",
+ timeout: "Bol prekročený maximálny počet opakovaní.",
+ trashTooltip: "Zahodiť všetky dielce.",
+ catLogic: "Logika",
+ catLoops: "Cykly",
+ catMath: "Matematické",
+ catText: "Text",
+ catLists: "Zoznamy",
+ catColour: "Farby",
+ catVariables: "Premenné",
+ catFunctions: "Funkcie",
+ listVariable: "zoznam",
+ textVariable: "text",
+ httpRequestError: "Problém so spracovaním požiadavky.",
+ linkAlert: "Zdieľať tento program skopírovaním odkazu\n\n%1",
+ hashError: "Prepáč, '%1' nie je meno žiadnemu uloženému programu.",
+ xmlError: "Nebolo možné načítať uložený súbor. Možno bol vytvorený v inej verzii Blocky.",
+ badXml: "Chyba pri parsovaní XML:\n%1\n\nStlačte 'OK' ak chcete zrušiť zmeny alebo 'Zrušiť' pre pokračovanie v úpravách XML."
+};
diff --git a/demos/simulator/msg/sr.js b/demos/simulator/msg/sr.js
new file mode 100644
index 00000000000..f9a026ca088
--- /dev/null
+++ b/demos/simulator/msg/sr.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Кôд",
+ blocks: "Блокови",
+ linkTooltip: "Сачувајте и повежите са блоковима.",
+ runTooltip: "Покрените програм заснован на блоковима у радном простору.",
+ badCode: "Грешка у програму:\n%1",
+ timeout: "Достигнут је максималан број понављања у извршавању.",
+ trashTooltip: "Одбаците све блокове.",
+ catLogic: "Логика",
+ catLoops: "Петље",
+ catMath: "Математика",
+ catText: "Текст",
+ catLists: "Спискови",
+ catColour: "Боја",
+ catVariables: "Променљиве",
+ catFunctions: "Процедуре",
+ listVariable: "списак",
+ textVariable: "текст",
+ httpRequestError: "Дошло је до проблема у захтеву.",
+ linkAlert: "Делите своје блокове овом везом:\n\n%1",
+ hashError: "„%1“ не одговара ниједном сачуваном програму.",
+ xmlError: "Не могу да учитам сачувану датотеку. Можда је направљена другом верзијом Blockly-ја.",
+ badXml: "Грешка при рашчлањивању XML-а:\n%1\n\nПритисните „У реду“ да напустите измене или „Откажи“ да наставите са уређивањем XML датотеке."
+};
diff --git a/demos/simulator/msg/sv.js b/demos/simulator/msg/sv.js
new file mode 100644
index 00000000000..4134a4c7dcd
--- /dev/null
+++ b/demos/simulator/msg/sv.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kod",
+ blocks: "Block",
+ linkTooltip: "Spara och länka till block.",
+ runTooltip: "Kör programmet som definierats av blocken i arbetsytan.",
+ badCode: "Programfel:\n%1",
+ timeout: "Det maximala antalet utförda loopar har överskridits.",
+ trashTooltip: "Släng alla block.",
+ catLogic: "Logik",
+ catLoops: "Loopar",
+ catMath: "Matematik",
+ catText: "Text",
+ catLists: "Listor",
+ catColour: "Färg",
+ catVariables: "Variabler",
+ catFunctions: "Funktioner",
+ listVariable: "lista",
+ textVariable: "text",
+ httpRequestError: "Det uppstod ett problem med begäran.",
+ linkAlert: "Dela dina block med denna länk: \n\n%1",
+ hashError: "Tyvärr, '%1' överensstämmer inte med något sparat program.",
+ xmlError: "Kunde inte läsa din sparade fil. Den skapades kanske med en annan version av Blockly?",
+ badXml: "Fel vid parsning av XML:\n%1\n\nKlicka på 'OK' för att strunta i dina ändringar eller 'Avbryt' för att fortsätta redigera XML-koden."
+};
diff --git a/demos/simulator/msg/ta.js b/demos/simulator/msg/ta.js
new file mode 100644
index 00000000000..ff472e2b7e2
--- /dev/null
+++ b/demos/simulator/msg/ta.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "கணினி நிரல்", //Code
+ blocks: "நிரல் துண்டு", //block
+ linkTooltip: "சேமித்து நிரல் துண்டிற்கு இணைக்க", //save and link to block
+ runTooltip: "பணிமனை நினைவகத்தில் இயக்குக", //Run the program defined by the blocks in the workspace.
+ badCode: "கணினி நிரல் கோளாறு:\n%1",
+ timeout: "அதிகபட்ச அடுக்கின் அளவை மீரியது", //max iters reached/exceeded
+ trashTooltip: "நீக்கு",
+ catLogic: "தர்க வகை",
+ catLoops: "மடக்கு வாக்கியம்",
+ catMath: "கணிதம்",
+ catText: "உரை",
+ catLists: "பட்டியல்",
+ catColour: "வண்ணம்",
+ catVariables: "மாறிகள்",
+ catFunctions: "சார்புகள்",
+ listVariable: "பட்டியல் மாறி",
+ textVariable: "உரை சரம்",
+ httpRequestError: "இந்த செயலை இயக்குவதில் கோளாறு ஏற்பட்டது",
+ linkAlert: "இந்த சுட்டி வழியாக நிரல் துண்டுகளை பகிரவும்:\n\n%1",
+ hashError: "'%1' : இது சேமித்த நிரலாக தெரியவில்லை.",
+ xmlError: "உங்களது நிரலை காணவில்லை; வேறு Blockly அத்தியாயத்தில் சேமித்தீரா?",
+ badXml: "XML பகுப்பதில் கோளாறு:\n%1\n\nOK' கிளிக் செய்தால் மாற்றங்கள் இழப்பீர்கள்; பிழைகளுடன் தொடர 'Cancel' கிளிக் செய்யவும்."
+};
diff --git a/demos/simulator/msg/th.js b/demos/simulator/msg/th.js
new file mode 100644
index 00000000000..1ac36245bdd
--- /dev/null
+++ b/demos/simulator/msg/th.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "เขียนโปรแกรม",
+ blocks: "บล็อก",
+ linkTooltip: "บันทึกและสร้างลิงก์มายังบล็อกเหล่านี้",
+ runTooltip: "เรียกใช้โปรแกรมตามที่กำหนดไว้ด้วยบล็อกที่อยู่ในพื้นที่ทำงาน",
+ badCode: "โปรแกรมเกิดข้อผิดพลาด:\n%1",
+ timeout: "โปรแกรมทำงานซ้ำคำสั่งเดิมมากเกินไป",
+ trashTooltip: "ยกเลิกบล็อกทั้งหมด",
+ catLogic: "ตรรกะ",
+ catLoops: "การวนซ้ำ",
+ catMath: "คณิตศาสตร์",
+ catText: "ข้อความ",
+ catLists: "รายการ",
+ catColour: "สี",
+ catVariables: "ตัวแปร",
+ catFunctions: "ฟังก์ชัน",
+ listVariable: "รายการ",
+ textVariable: "ข้อความ",
+ httpRequestError: "มีปัญหาเกี่ยวกับการร้องขอ",
+ linkAlert: "แบ่งปันบล็อกของคุณด้วยลิงก์นี้:\n\n%1",
+ hashError: "เสียใจด้วย '%1' ไม่ตรงกับโปรแกรมใดๆ ที่เคยบันทึกเอาไว้เลย",
+ xmlError: "ไม่สามารถโหลดไฟล์ที่บันทึกไว้ของคุณได้ บางทีมันอาจจะถูกสร้างขึ้นด้วย Blockly รุ่นอื่นที่แตกต่างกัน?",
+ badXml: "เกิดข้อผิดพลาดในการแยกวิเคราะห์ XML:\n%1\n\nเลือก 'ตกลง' เพื่อละทิ้งการเปลี่ยนแปลงต่างๆ ที่ทำไว้ หรือเลือก 'ยกเลิก' เพื่อแก้ไข XML ต่อไป"
+};
diff --git a/demos/simulator/msg/tlh.js b/demos/simulator/msg/tlh.js
new file mode 100644
index 00000000000..8d0c056f930
--- /dev/null
+++ b/demos/simulator/msg/tlh.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "ngoq",
+ blocks: "ngoghmey",
+ linkTooltip: "",
+ runTooltip: "",
+ badCode: "Qagh:\n%1",
+ timeout: "tlhoy nI'qu' poH.",
+ trashTooltip: "",
+ catLogic: "meq",
+ catLoops: "vIHtaHbogh ghomey",
+ catMath: "mI'QeD",
+ catText: "ghItlhHommey",
+ catLists: "tetlhmey",
+ catColour: "rItlh",
+ catVariables: "lIwmey",
+ catFunctions: "mIwmey",
+ listVariable: "tetlh",
+ textVariable: "ghItlhHom",
+ httpRequestError: "Qapbe' tlhobmeH QIn.",
+ linkAlert: "latlhvaD ngoghmeylIj DangeHmeH Quvvam yIlo':\n\n%1",
+ hashError: "Do'Ha', ngogh nab pollu'pu'bogh 'oHbe'law' \"%1\"'e'.",
+ xmlError: "ngogh nablIj pollu'pu'bogh chu'qa'laHbe' vay'. chaq pollu'pu'DI' ghunmeH ngogh pIm lo'lu'pu'.",
+ badXml: "XML yajchu'laHbe' vay':\n%1\n\nchoHmeylIj DalonmeH \"ruch\" yIwIv pagh XML DachoHqa'meH \"qIl\" yIwIv."
+};
diff --git a/demos/simulator/msg/tr.js b/demos/simulator/msg/tr.js
new file mode 100644
index 00000000000..14483009222
--- /dev/null
+++ b/demos/simulator/msg/tr.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Kod",
+ blocks: "Bloklar",
+ linkTooltip: "Blokları ve bağlantı adresini kaydet.",
+ runTooltip: "Çalışma alanında bloklar tarafından tanımlanan programını çalıştırın.",
+ badCode: "Program hatası:\n %1",
+ timeout: "Maksimum yürütme yinelemeleri aşıldı.",
+ trashTooltip: "Bütün blokları at.",
+ catLogic: "Mantık",
+ catLoops: "Döngüler",
+ catMath: "Matematik",
+ catText: "Metin",
+ catLists: "Listeler",
+ catColour: "Renk",
+ catVariables: "Değişkenler",
+ catFunctions: "İşlevler",
+ listVariable: "liste",
+ textVariable: "metin",
+ httpRequestError: "İstek ile ilgili bir problem var.",
+ linkAlert: "Bloklarını bu bağlantı ile paylaş:\n\n%1",
+ hashError: "Üzgünüz, '%1' hiç bir kaydedilmiş program ile uyuşmuyor.",
+ xmlError: "Kaydedilen dosyanız yüklenemiyor\nBlockly'nin önceki sürümü ile kaydedilmiş olabilir mi?",
+ badXml: "XML ayrıştırma hatası:\n%1\n\nDeğişikliklerden vazgeçmek için 'Tamam'ı, düzenlemeye devam etmek için 'İptal' seçeneğini seçiniz."
+};
diff --git a/demos/simulator/msg/uk.js b/demos/simulator/msg/uk.js
new file mode 100644
index 00000000000..6b346272152
--- /dev/null
+++ b/demos/simulator/msg/uk.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Код",
+ blocks: "Блоки",
+ linkTooltip: "Зберегти і пов'язати з блоками.",
+ runTooltip: "Запустіть програму, визначену блоками у робочій області.",
+ badCode: "Помилка програми:\n%1",
+ timeout: "Максимальне виконання ітерацій перевищено.",
+ trashTooltip: "Відкинути всі блоки.",
+ catLogic: "Логіка",
+ catLoops: "Петлі",
+ catMath: "Математика",
+ catText: "Текст",
+ catLists: "Списки",
+ catColour: "Колір",
+ catVariables: "Змінні",
+ catFunctions: "Функції",
+ listVariable: "список",
+ textVariable: "текст",
+ httpRequestError: "Виникла проблема із запитом.",
+ linkAlert: "Поділитися вашим блоками через посилання:\n\n%1",
+ hashError: "На жаль, \"%1\" не відповідає жодній збереженій програмі.",
+ xmlError: "Не вдалося завантажити ваш збережений файл. Можливо, він був створений з іншої версії Blockly?",
+ badXml: "Помилка синтаксичного аналізу XML:\n%1\n\nВиберіть \"Гаразд\", щоб відмовитися від змін або 'Скасувати' для подальшого редагування XML."
+};
diff --git a/demos/simulator/msg/vi.js b/demos/simulator/msg/vi.js
new file mode 100644
index 00000000000..952d024b4ce
--- /dev/null
+++ b/demos/simulator/msg/vi.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "Chương trình",
+ blocks: "Các mảnh",
+ linkTooltip: "Lưu và lấy địa chỉ liên kết.",
+ runTooltip: "Chạy chương trình.",
+ badCode: "'Lỗi chương trình:\n%1",
+ timeout: "Đã vượt quá số lần lặp cho phép.",
+ trashTooltip: "Xóa tất cả mọi mảnh.",
+ catLogic: "Logic",
+ catLoops: "Vòng lặp",
+ catMath: "Công thức toán",
+ catText: "Văn bản",
+ catLists: "Danh sách",
+ catColour: "Màu",
+ catVariables: "Biến",
+ catFunctions: "Hàm",
+ listVariable: "danh sách",
+ textVariable: "văn bản",
+ httpRequestError: "Hoạt động bị trục trặc, không thực hiện được yêu cầu của bạn.",
+ linkAlert: "Chia sẻ chương trình của bạn với liên kết sau:\n\n %1",
+ hashError: "Không tìm thấy chương trình được lưu ở '%1'.",
+ xmlError: "Không mở được chương trình của bạn. Có thể nó nằm trong một phiên bản khác của Blockly?",
+ badXml: "Lỗi sử lý XML:\n %1\n\nChọn 'OK' để từ bỏ các thay đổi hoặc 'Hủy' để tiếp tục chỉnh sửa các XML."
+};
diff --git a/demos/simulator/msg/zh-hans.js b/demos/simulator/msg/zh-hans.js
new file mode 100644
index 00000000000..abf8a6589b0
--- /dev/null
+++ b/demos/simulator/msg/zh-hans.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "代码",
+ blocks: "块",
+ linkTooltip: "保存模块并生成链接。",
+ runTooltip: "于工作区中运行块所定义的程式。",
+ badCode: "程序错误:\n%1",
+ timeout: "超过最大执行行数。",
+ trashTooltip: "放弃所有块。",
+ catLogic: "逻辑",
+ catLoops: "循环",
+ catMath: "数学",
+ catText: "文本",
+ catLists: "列表",
+ catColour: "颜色",
+ catVariables: "变量",
+ catFunctions: "函数",
+ listVariable: "列表",
+ textVariable: "文本",
+ httpRequestError: "请求存在问题。",
+ linkAlert: "通过这个链接分享您的模块:\n\n%1",
+ hashError: "对不起,没有任何已保存的程序对应'%1' 。",
+ xmlError: "无法载入您保存的文件。您是否使用其他版本的Blockly创建该文件的?",
+ badXml: "XML解析错误:\n%1\n\n选择“确定”以取消您对XML的修改,或选择“取消”以继续编辑XML。"
+};
diff --git a/demos/simulator/msg/zh-hant.js b/demos/simulator/msg/zh-hant.js
new file mode 100644
index 00000000000..48a8b527272
--- /dev/null
+++ b/demos/simulator/msg/zh-hant.js
@@ -0,0 +1,24 @@
+var MSG = {
+ title: "程式碼",
+ blocks: "積木",
+ linkTooltip: "儲存積木組並提供連結。",
+ runTooltip: "於工作區中執行積木組所定義的程式。",
+ badCode: "程式錯誤:\n%1",
+ timeout: "超過最大執行數。",
+ trashTooltip: "捨棄所有積木。",
+ catLogic: "邏輯",
+ catLoops: "迴圈",
+ catMath: "數學式",
+ catText: "文字",
+ catLists: "列表",
+ catColour: "顏色",
+ catVariables: "變量",
+ catFunctions: "流程",
+ listVariable: "列表",
+ textVariable: "文字",
+ httpRequestError: "命令出現錯誤。",
+ linkAlert: "透過此連結分享您的積木組:\n\n%1",
+ hashError: "對不起,「%1」並未對應任何已保存的程式。",
+ xmlError: "未能載入您保存的檔案。或許它是由其他版本的Blockly創建?",
+ badXml: "解析 XML 時出現錯誤:\n%1\n\n選擇'確定'以放棄您的更改,或選擇'取消'以進一步編輯 XML。"
+};
diff --git a/demos/simulator/pinecone.png b/demos/simulator/pinecone.png
new file mode 100644
index 00000000000..52e398c5b7f
Binary files /dev/null and b/demos/simulator/pinecone.png differ
diff --git a/demos/simulator/style.css b/demos/simulator/style.css
new file mode 100644
index 00000000000..ef7d9b4d373
--- /dev/null
+++ b/demos/simulator/style.css
@@ -0,0 +1,184 @@
+html, body {
+ height: 100%;
+}
+
+body {
+ background-color: #fff;
+ font-family: sans-serif;
+ margin: 0;
+ overflow: hidden;
+}
+
+.farSide {
+ text-align: right;
+}
+
+html[dir="RTL"] .farSide {
+ text-align: left;
+}
+
+/* Buttons */
+button {
+ margin: 5px;
+ padding: 10px;
+ border-radius: 4px;
+ border: 1px solid #ddd;
+ font-size: large;
+ background-color: #eee;
+ color: #000;
+}
+button.primary {
+ border: 1px solid #dd4b39;
+ background-color: #dd4b39;
+ color: #fff;
+}
+button.primary>img {
+ opacity: 1;
+}
+button>img {
+ opacity: 0.6;
+ vertical-align: text-bottom;
+}
+button:hover>img {
+ opacity: 1;
+}
+button:active {
+ border: 1px solid #888 !important;
+}
+button:hover {
+ box-shadow: 2px 2px 5px #888;
+}
+button.disabled:hover>img {
+ opacity: 0.6;
+}
+button.disabled {
+ display: none;
+}
+button.notext {
+ font-size: 10%;
+}
+
+h1 {
+ font-weight: normal;
+ font-size: 140%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+/* Tabs */
+#tabRow>td {
+ border: 1px solid #ccc;
+ border-bottom: none;
+}
+td.tabon {
+ border-bottom-color: #ddd !important;
+ background-color: #ddd;
+ padding: 5px 19px;
+ white-space: nowrap;
+}
+td.taboff {
+ cursor: pointer;
+ padding: 5px 19px;
+}
+td.taboff:hover {
+ background-color: #eee;
+}
+td.tabmin {
+ border-top-style: none !important;
+ border-left-style: none !important;
+ border-right-style: none !important;
+}
+td.tabmax {
+ border-top-style: none !important;
+ border-left-style: none !important;
+ border-right-style: none !important;
+ width: 99%;
+ padding-left: 10px;
+ padding-right: 10px;
+ text-align: right;
+}
+html[dir=rtl] td.tabmax {
+ text-align: left;
+}
+#tab_code {
+ display: none;
+}
+
+table {
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+td {
+ padding: 0;
+ vertical-align: top;
+}
+.content {
+ visibility: hidden;
+ margin: 0;
+ padding: 1ex;
+ position: absolute;
+ direction: ltr;
+}
+pre.content {
+ border: 1px solid #ccc;
+ overflow: scroll;
+}
+#content_blocks {
+ padding: 0;
+}
+.blocklySvg {
+ border-top: none !important;
+}
+#content_xml {
+ resize: none;
+ outline: none;
+ border: 1px solid #ccc;
+ font-family: monospace;
+ overflow: scroll;
+}
+#languageMenu {
+ vertical-align: top;
+ margin-left: 15px;
+ margin-right: 15px;
+ margin-top: 15px;
+}
+
+/* Buttons */
+button {
+ padding: 1px 10px;
+ margin: 1px 5px;
+}
+
+/* Sprited icons. */
+.icon21 {
+ height: 21px;
+ width: 21px;
+ background-image: url(icons.png);
+}
+.trash {
+ background-position: 0px 0px;
+}
+.link {
+ background-position: -21px 0px;
+}
+.run {
+ background-position: -42px 0px;
+}
+
+@media (max-width: 710px) {
+ .tab_collapse {
+ display: none;
+ }
+ #tab_code {
+ display: table-cell;
+ }
+}
+
+/* Privacy link */
+.privacyLink {
+ font-family: Roboto, Arial, Helvetica, sans-serif;
+ font-size: small;
+ text-decoration: none;
+}
diff --git a/demos/simulator/ulisp.html b/demos/simulator/ulisp.html
new file mode 100644
index 00000000000..bd55db0fd03
--- /dev/null
+++ b/demos/simulator/ulisp.html
@@ -0,0 +1,1484 @@
+
+
+
+
+
+ uLisp WebAssembly
+
+
+
+
+ image/svg+xml
+
+
+
+ Downloading...
+
+
+ Resize canvas
+ Lock/hide mouse pointer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/simulator/ulisp.js b/demos/simulator/ulisp.js
new file mode 100644
index 00000000000..b896f3e9973
--- /dev/null
+++ b/demos/simulator/ulisp.js
@@ -0,0 +1,2370 @@
+
+
+// The Module object: Our interface to the outside world. We import
+// and export values on it. There are various ways Module can be used:
+// 1. Not defined. We create it here
+// 2. A function parameter, function(Module) { ..generated code.. }
+// 3. pre-run appended it, var Module = {}; ..generated code..
+// 4. External script tag defines var Module.
+// We need to check if Module already exists (e.g. case 3 above).
+// Substitution will be replaced with actual code on later stage of the build,
+// this way Closure Compiler will not mangle it (e.g. case 4. above).
+// Note that if you want to run closure, and also to use Module
+// after the generated code, you will need to define var Module = {};
+// before the code. Then that object will be used in the code, and you
+// can continue to use Module afterwards as well.
+var Module = typeof Module !== 'undefined' ? Module : {};
+
+
+
+// --pre-jses are emitted after the Module integration code, so that they can
+// refer to Module (if they choose; they can also define Module)
+// {{PRE_JSES}}
+
+// Sometimes an existing Module object exists with properties
+// meant to overwrite the default module functionality. Here
+// we collect those properties and reapply _after_ we configure
+// the current environment's defaults to avoid having to be so
+// defensive during initialization.
+var moduleOverrides = {};
+var key;
+for (key in Module) {
+ if (Module.hasOwnProperty(key)) {
+ moduleOverrides[key] = Module[key];
+ }
+}
+
+var arguments_ = [];
+var thisProgram = './this.program';
+var quit_ = function(status, toThrow) {
+ throw toThrow;
+};
+
+// Determine the runtime environment we are in. You can customize this by
+// setting the ENVIRONMENT setting at compile time (see settings.js).
+
+var ENVIRONMENT_IS_WEB = false;
+var ENVIRONMENT_IS_WORKER = false;
+var ENVIRONMENT_IS_NODE = false;
+var ENVIRONMENT_IS_SHELL = false;
+ENVIRONMENT_IS_WEB = typeof window === 'object';
+ENVIRONMENT_IS_WORKER = typeof importScripts === 'function';
+// N.b. Electron.js environment is simultaneously a NODE-environment, but
+// also a web environment.
+ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
+ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
+
+if (Module['ENVIRONMENT']) {
+ throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -s ENVIRONMENT=web or -s ENVIRONMENT=node)');
+}
+
+
+
+// `/` should be present at the end if `scriptDirectory` is not empty
+var scriptDirectory = '';
+function locateFile(path) {
+ if (Module['locateFile']) {
+ return Module['locateFile'](path, scriptDirectory);
+ }
+ return scriptDirectory + path;
+}
+
+// Hooks that are implemented differently in different runtime environments.
+var read_,
+ readAsync,
+ readBinary,
+ setWindowTitle;
+
+var nodeFS;
+var nodePath;
+
+if (ENVIRONMENT_IS_NODE) {
+ if (ENVIRONMENT_IS_WORKER) {
+ scriptDirectory = require('path').dirname(scriptDirectory) + '/';
+ } else {
+ scriptDirectory = __dirname + '/';
+ }
+
+
+
+
+read_ = function shell_read(filename, binary) {
+ if (!nodeFS) nodeFS = require('fs');
+ if (!nodePath) nodePath = require('path');
+ filename = nodePath['normalize'](filename);
+ return nodeFS['readFileSync'](filename, binary ? null : 'utf8');
+};
+
+readBinary = function readBinary(filename) {
+ var ret = read_(filename, true);
+ if (!ret.buffer) {
+ ret = new Uint8Array(ret);
+ }
+ assert(ret.buffer);
+ return ret;
+};
+
+
+
+ if (process['argv'].length > 1) {
+ thisProgram = process['argv'][1].replace(/\\/g, '/');
+ }
+
+ arguments_ = process['argv'].slice(2);
+
+ if (typeof module !== 'undefined') {
+ module['exports'] = Module;
+ }
+
+ process['on']('uncaughtException', function(ex) {
+ // suppress ExitStatus exceptions from showing an error
+ if (!(ex instanceof ExitStatus)) {
+ throw ex;
+ }
+ });
+
+ process['on']('unhandledRejection', abort);
+
+ quit_ = function(status) {
+ process['exit'](status);
+ };
+
+ Module['inspect'] = function () { return '[Emscripten Module object]'; };
+
+
+
+} else
+if (ENVIRONMENT_IS_SHELL) {
+
+
+ if (typeof read != 'undefined') {
+ read_ = function shell_read(f) {
+ return read(f);
+ };
+ }
+
+ readBinary = function readBinary(f) {
+ var data;
+ if (typeof readbuffer === 'function') {
+ return new Uint8Array(readbuffer(f));
+ }
+ data = read(f, 'binary');
+ assert(typeof data === 'object');
+ return data;
+ };
+
+ if (typeof scriptArgs != 'undefined') {
+ arguments_ = scriptArgs;
+ } else if (typeof arguments != 'undefined') {
+ arguments_ = arguments;
+ }
+
+ if (typeof quit === 'function') {
+ quit_ = function(status) {
+ quit(status);
+ };
+ }
+
+ if (typeof print !== 'undefined') {
+ // Prefer to use print/printErr where they exist, as they usually work better.
+ if (typeof console === 'undefined') console = /** @type{!Console} */({});
+ console.log = /** @type{!function(this:Console, ...*): undefined} */ (print);
+ console.warn = console.error = /** @type{!function(this:Console, ...*): undefined} */ (typeof printErr !== 'undefined' ? printErr : print);
+ }
+
+
+} else
+
+// Note that this includes Node.js workers when relevant (pthreads is enabled).
+// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and
+// ENVIRONMENT_IS_NODE.
+if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
+ if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled
+ scriptDirectory = self.location.href;
+ } else if (document.currentScript) { // web
+ scriptDirectory = document.currentScript.src;
+ }
+ // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.
+ // otherwise, slice off the final part of the url to find the script directory.
+ // if scriptDirectory does not contain a slash, lastIndexOf will return -1,
+ // and scriptDirectory will correctly be replaced with an empty string.
+ if (scriptDirectory.indexOf('blob:') !== 0) {
+ scriptDirectory = scriptDirectory.substr(0, scriptDirectory.lastIndexOf('/')+1);
+ } else {
+ scriptDirectory = '';
+ }
+
+
+ // Differentiate the Web Worker from the Node Worker case, as reading must
+ // be done differently.
+ {
+
+
+
+
+ read_ = function shell_read(url) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false);
+ xhr.send(null);
+ return xhr.responseText;
+ };
+
+ if (ENVIRONMENT_IS_WORKER) {
+ readBinary = function readBinary(url) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false);
+ xhr.responseType = 'arraybuffer';
+ xhr.send(null);
+ return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response));
+ };
+ }
+
+ readAsync = function readAsync(url, onload, onerror) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = function xhr_onload() {
+ if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0
+ onload(xhr.response);
+ return;
+ }
+ onerror();
+ };
+ xhr.onerror = onerror;
+ xhr.send(null);
+ };
+
+
+
+
+ }
+
+ setWindowTitle = function(title) { document.title = title };
+} else
+{
+ throw new Error('environment detection error');
+}
+
+
+// Set up the out() and err() hooks, which are how we can print to stdout or
+// stderr, respectively.
+var out = Module['print'] || console.log.bind(console);
+var err = Module['printErr'] || console.warn.bind(console);
+
+// Merge back in the overrides
+for (key in moduleOverrides) {
+ if (moduleOverrides.hasOwnProperty(key)) {
+ Module[key] = moduleOverrides[key];
+ }
+}
+// Free the object hierarchy contained in the overrides, this lets the GC
+// reclaim data used e.g. in memoryInitializerRequest, which is a large typed array.
+moduleOverrides = null;
+
+// Emit code to handle expected values on the Module object. This applies Module.x
+// to the proper local x. This has two benefits: first, we only emit it if it is
+// expected to arrive, and second, by using a local everywhere else that can be
+// minified.
+if (Module['arguments']) arguments_ = Module['arguments'];if (!Object.getOwnPropertyDescriptor(Module, 'arguments')) Object.defineProperty(Module, 'arguments', { configurable: true, get: function() { abort('Module.arguments has been replaced with plain arguments_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+if (Module['thisProgram']) thisProgram = Module['thisProgram'];if (!Object.getOwnPropertyDescriptor(Module, 'thisProgram')) Object.defineProperty(Module, 'thisProgram', { configurable: true, get: function() { abort('Module.thisProgram has been replaced with plain thisProgram (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+if (Module['quit']) quit_ = Module['quit'];if (!Object.getOwnPropertyDescriptor(Module, 'quit')) Object.defineProperty(Module, 'quit', { configurable: true, get: function() { abort('Module.quit has been replaced with plain quit_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+
+// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message
+// Assertions on removed incoming Module JS APIs.
+assert(typeof Module['memoryInitializerPrefixURL'] === 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead');
+assert(typeof Module['pthreadMainPrefixURL'] === 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead');
+assert(typeof Module['cdInitializerPrefixURL'] === 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead');
+assert(typeof Module['filePackagePrefixURL'] === 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead');
+assert(typeof Module['read'] === 'undefined', 'Module.read option was removed (modify read_ in JS)');
+assert(typeof Module['readAsync'] === 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)');
+assert(typeof Module['readBinary'] === 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)');
+assert(typeof Module['setWindowTitle'] === 'undefined', 'Module.setWindowTitle option was removed (modify setWindowTitle in JS)');
+assert(typeof Module['TOTAL_MEMORY'] === 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY');
+if (!Object.getOwnPropertyDescriptor(Module, 'read')) Object.defineProperty(Module, 'read', { configurable: true, get: function() { abort('Module.read has been replaced with plain read_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+if (!Object.getOwnPropertyDescriptor(Module, 'readAsync')) Object.defineProperty(Module, 'readAsync', { configurable: true, get: function() { abort('Module.readAsync has been replaced with plain readAsync (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+if (!Object.getOwnPropertyDescriptor(Module, 'readBinary')) Object.defineProperty(Module, 'readBinary', { configurable: true, get: function() { abort('Module.readBinary has been replaced with plain readBinary (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+if (!Object.getOwnPropertyDescriptor(Module, 'setWindowTitle')) Object.defineProperty(Module, 'setWindowTitle', { configurable: true, get: function() { abort('Module.setWindowTitle has been replaced with plain setWindowTitle (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js';
+var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js';
+var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js';
+var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js';
+
+
+
+
+
+
+// {{PREAMBLE_ADDITIONS}}
+
+var STACK_ALIGN = 16;
+
+function alignMemory(size, factor) {
+ if (!factor) factor = STACK_ALIGN; // stack alignment (16-byte) by default
+ return Math.ceil(size / factor) * factor;
+}
+
+function getNativeTypeSize(type) {
+ switch (type) {
+ case 'i1': case 'i8': return 1;
+ case 'i16': return 2;
+ case 'i32': return 4;
+ case 'i64': return 8;
+ case 'float': return 4;
+ case 'double': return 8;
+ default: {
+ if (type[type.length-1] === '*') {
+ return 4; // A pointer
+ } else if (type[0] === 'i') {
+ var bits = Number(type.substr(1));
+ assert(bits % 8 === 0, 'getNativeTypeSize invalid bits ' + bits + ', type ' + type);
+ return bits / 8;
+ } else {
+ return 0;
+ }
+ }
+ }
+}
+
+function warnOnce(text) {
+ if (!warnOnce.shown) warnOnce.shown = {};
+ if (!warnOnce.shown[text]) {
+ warnOnce.shown[text] = 1;
+ err(text);
+ }
+}
+
+
+
+
+// Wraps a JS function as a wasm function with a given signature.
+function convertJsFunctionToWasm(func, sig) {
+
+ // If the type reflection proposal is available, use the new
+ // "WebAssembly.Function" constructor.
+ // Otherwise, construct a minimal wasm module importing the JS function and
+ // re-exporting it.
+ if (typeof WebAssembly.Function === "function") {
+ var typeNames = {
+ 'i': 'i32',
+ 'j': 'i64',
+ 'f': 'f32',
+ 'd': 'f64'
+ };
+ var type = {
+ parameters: [],
+ results: sig[0] == 'v' ? [] : [typeNames[sig[0]]]
+ };
+ for (var i = 1; i < sig.length; ++i) {
+ type.parameters.push(typeNames[sig[i]]);
+ }
+ return new WebAssembly.Function(type, func);
+ }
+
+ // The module is static, with the exception of the type section, which is
+ // generated based on the signature passed in.
+ var typeSection = [
+ 0x01, // id: section,
+ 0x00, // length: 0 (placeholder)
+ 0x01, // count: 1
+ 0x60, // form: func
+ ];
+ var sigRet = sig.slice(0, 1);
+ var sigParam = sig.slice(1);
+ var typeCodes = {
+ 'i': 0x7f, // i32
+ 'j': 0x7e, // i64
+ 'f': 0x7d, // f32
+ 'd': 0x7c, // f64
+ };
+
+ // Parameters, length + signatures
+ typeSection.push(sigParam.length);
+ for (var i = 0; i < sigParam.length; ++i) {
+ typeSection.push(typeCodes[sigParam[i]]);
+ }
+
+ // Return values, length + signatures
+ // With no multi-return in MVP, either 0 (void) or 1 (anything else)
+ if (sigRet == 'v') {
+ typeSection.push(0x00);
+ } else {
+ typeSection = typeSection.concat([0x01, typeCodes[sigRet]]);
+ }
+
+ // Write the overall length of the type section back into the section header
+ // (excepting the 2 bytes for the section id and length)
+ typeSection[1] = typeSection.length - 2;
+
+ // Rest of the module is static
+ var bytes = new Uint8Array([
+ 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
+ 0x01, 0x00, 0x00, 0x00, // version: 1
+ ].concat(typeSection, [
+ 0x02, 0x07, // import section
+ // (import "e" "f" (func 0 (type 0)))
+ 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
+ 0x07, 0x05, // export section
+ // (export "f" (func 0 (type 0)))
+ 0x01, 0x01, 0x66, 0x00, 0x00,
+ ]));
+
+ // We can compile this wasm module synchronously because it is very small.
+ // This accepts an import (at "e.f"), that it reroutes to an export (at "f")
+ var module = new WebAssembly.Module(bytes);
+ var instance = new WebAssembly.Instance(module, {
+ 'e': {
+ 'f': func
+ }
+ });
+ var wrappedFunc = instance.exports['f'];
+ return wrappedFunc;
+}
+
+var freeTableIndexes = [];
+
+// Weak map of functions in the table to their indexes, created on first use.
+var functionsInTableMap;
+
+// Add a wasm function to the table.
+function addFunctionWasm(func, sig) {
+ var table = wasmTable;
+
+ // Check if the function is already in the table, to ensure each function
+ // gets a unique index. First, create the map if this is the first use.
+ if (!functionsInTableMap) {
+ functionsInTableMap = new WeakMap();
+ for (var i = 0; i < table.length; i++) {
+ var item = table.get(i);
+ // Ignore null values.
+ if (item) {
+ functionsInTableMap.set(item, i);
+ }
+ }
+ }
+ if (functionsInTableMap.has(func)) {
+ return functionsInTableMap.get(func);
+ }
+
+ // It's not in the table, add it now.
+
+
+ var ret;
+ // Reuse a free index if there is one, otherwise grow.
+ if (freeTableIndexes.length) {
+ ret = freeTableIndexes.pop();
+ } else {
+ ret = table.length;
+ // Grow the table
+ try {
+ table.grow(1);
+ } catch (err) {
+ if (!(err instanceof RangeError)) {
+ throw err;
+ }
+ throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.';
+ }
+ }
+
+ // Set the new value.
+ try {
+ // Attempting to call this with JS function will cause of table.set() to fail
+ table.set(ret, func);
+ } catch (err) {
+ if (!(err instanceof TypeError)) {
+ throw err;
+ }
+ assert(typeof sig !== 'undefined', 'Missing signature argument to addFunction');
+ var wrapped = convertJsFunctionToWasm(func, sig);
+ table.set(ret, wrapped);
+ }
+
+ functionsInTableMap.set(func, ret);
+
+ return ret;
+}
+
+function removeFunctionWasm(index) {
+ functionsInTableMap.delete(wasmTable.get(index));
+ freeTableIndexes.push(index);
+}
+
+// 'sig' parameter is required for the llvm backend but only when func is not
+// already a WebAssembly function.
+function addFunction(func, sig) {
+ assert(typeof func !== 'undefined');
+
+ return addFunctionWasm(func, sig);
+}
+
+function removeFunction(index) {
+ removeFunctionWasm(index);
+}
+
+
+
+
+
+
+
+
+
+function makeBigInt(low, high, unsigned) {
+ return unsigned ? ((+((low>>>0)))+((+((high>>>0)))*4294967296.0)) : ((+((low>>>0)))+((+((high|0)))*4294967296.0));
+}
+
+var tempRet0 = 0;
+
+var setTempRet0 = function(value) {
+ tempRet0 = value;
+};
+
+var getTempRet0 = function() {
+ return tempRet0;
+};
+
+function getCompilerSetting(name) {
+ throw 'You must build with -s RETAIN_COMPILER_SETTINGS=1 for getCompilerSetting or emscripten_get_compiler_setting to work';
+}
+
+
+
+
+// === Preamble library stuff ===
+
+// Documentation for the public APIs defined in this file must be updated in:
+// site/source/docs/api_reference/preamble.js.rst
+// A prebuilt local version of the documentation is available at:
+// site/build/text/docs/api_reference/preamble.js.txt
+// You can also build docs locally as HTML or other formats in site/
+// An online HTML version (which may be of a different version of Emscripten)
+// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html
+
+
+var wasmBinary;if (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];if (!Object.getOwnPropertyDescriptor(Module, 'wasmBinary')) Object.defineProperty(Module, 'wasmBinary', { configurable: true, get: function() { abort('Module.wasmBinary has been replaced with plain wasmBinary (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+var noExitRuntime;if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime'];if (!Object.getOwnPropertyDescriptor(Module, 'noExitRuntime')) Object.defineProperty(Module, 'noExitRuntime', { configurable: true, get: function() { abort('Module.noExitRuntime has been replaced with plain noExitRuntime (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+
+
+if (typeof WebAssembly !== 'object') {
+ abort('no native wasm support detected');
+}
+
+
+
+
+// In MINIMAL_RUNTIME, setValue() and getValue() are only available when building with safe heap enabled, for heap safety checking.
+// In traditional runtime, setValue() and getValue() are always available (although their use is highly discouraged due to perf penalties)
+
+/** @param {number} ptr
+ @param {number} value
+ @param {string} type
+ @param {number|boolean=} noSafe */
+function setValue(ptr, value, type, noSafe) {
+ type = type || 'i8';
+ if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit
+ switch(type) {
+ case 'i1': HEAP8[((ptr)>>0)]=value; break;
+ case 'i8': HEAP8[((ptr)>>0)]=value; break;
+ case 'i16': HEAP16[((ptr)>>1)]=value; break;
+ case 'i32': HEAP32[((ptr)>>2)]=value; break;
+ case 'i64': (tempI64 = [value>>>0,(tempDouble=value,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((ptr)>>2)]=tempI64[0],HEAP32[(((ptr)+(4))>>2)]=tempI64[1]); break;
+ case 'float': HEAPF32[((ptr)>>2)]=value; break;
+ case 'double': HEAPF64[((ptr)>>3)]=value; break;
+ default: abort('invalid type for setValue: ' + type);
+ }
+}
+
+/** @param {number} ptr
+ @param {string} type
+ @param {number|boolean=} noSafe */
+function getValue(ptr, type, noSafe) {
+ type = type || 'i8';
+ if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit
+ switch(type) {
+ case 'i1': return HEAP8[((ptr)>>0)];
+ case 'i8': return HEAP8[((ptr)>>0)];
+ case 'i16': return HEAP16[((ptr)>>1)];
+ case 'i32': return HEAP32[((ptr)>>2)];
+ case 'i64': return HEAP32[((ptr)>>2)];
+ case 'float': return HEAPF32[((ptr)>>2)];
+ case 'double': return HEAPF64[((ptr)>>3)];
+ default: abort('invalid type for getValue: ' + type);
+ }
+ return null;
+}
+
+
+
+
+
+
+// Wasm globals
+
+var wasmMemory;
+var wasmTable;
+
+
+//========================================
+// Runtime essentials
+//========================================
+
+// whether we are quitting the application. no code should run after this.
+// set in exit() and abort()
+var ABORT = false;
+
+// set by exit() and abort(). Passed to 'onExit' handler.
+// NOTE: This is also used as the process return code code in shell environments
+// but only when noExitRuntime is false.
+var EXITSTATUS = 0;
+
+/** @type {function(*, string=)} */
+function assert(condition, text) {
+ if (!condition) {
+ abort('Assertion failed: ' + text);
+ }
+}
+
+// Returns the C function with a specified identifier (for C++, you need to do manual name mangling)
+function getCFunc(ident) {
+ var func = Module['_' + ident]; // closure exported function
+ assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported');
+ return func;
+}
+
+// C calling interface.
+/** @param {string|null=} returnType
+ @param {Array=} argTypes
+ @param {Arguments|Array=} args
+ @param {Object=} opts */
+function ccall(ident, returnType, argTypes, args, opts) {
+ // For fast lookup of conversion functions
+ var toC = {
+ 'string': function(str) {
+ var ret = 0;
+ if (str !== null && str !== undefined && str !== 0) { // null string
+ // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'
+ var len = (str.length << 2) + 1;
+ ret = stackAlloc(len);
+ stringToUTF8(str, ret, len);
+ }
+ return ret;
+ },
+ 'array': function(arr) {
+ var ret = stackAlloc(arr.length);
+ writeArrayToMemory(arr, ret);
+ return ret;
+ }
+ };
+
+ function convertReturnValue(ret) {
+ if (returnType === 'string') return UTF8ToString(ret);
+ if (returnType === 'boolean') return Boolean(ret);
+ return ret;
+ }
+
+ var func = getCFunc(ident);
+ var cArgs = [];
+ var stack = 0;
+ assert(returnType !== 'array', 'Return type should not be "array".');
+ if (args) {
+ for (var i = 0; i < args.length; i++) {
+ var converter = toC[argTypes[i]];
+ if (converter) {
+ if (stack === 0) stack = stackSave();
+ cArgs[i] = converter(args[i]);
+ } else {
+ cArgs[i] = args[i];
+ }
+ }
+ }
+ var ret = func.apply(null, cArgs);
+
+ ret = convertReturnValue(ret);
+ if (stack !== 0) stackRestore(stack);
+ return ret;
+}
+
+/** @param {string=} returnType
+ @param {Array=} argTypes
+ @param {Object=} opts */
+function cwrap(ident, returnType, argTypes, opts) {
+ return function() {
+ return ccall(ident, returnType, argTypes, arguments, opts);
+ }
+}
+
+// We used to include malloc/free by default in the past. Show a helpful error in
+// builds with assertions.
+
+var ALLOC_NORMAL = 0; // Tries to use _malloc()
+var ALLOC_STACK = 1; // Lives for the duration of the current function call
+
+// allocate(): This is for internal use. You can use it yourself as well, but the interface
+// is a little tricky (see docs right below). The reason is that it is optimized
+// for multiple syntaxes to save space in generated code. So you should
+// normally not use allocate(), and instead allocate memory using _malloc(),
+// initialize it with setValue(), and so forth.
+// @slab: An array of data.
+// @allocator: How to allocate memory, see ALLOC_*
+/** @type {function((Uint8Array|Array), number)} */
+function allocate(slab, allocator) {
+ var ret;
+ assert(typeof allocator === 'number', 'allocate no longer takes a type argument')
+ assert(typeof slab !== 'number', 'allocate no longer takes a number as arg0')
+
+ if (allocator == ALLOC_STACK) {
+ ret = stackAlloc(slab.length);
+ } else {
+ ret = _malloc(slab.length);
+ }
+
+ if (slab.subarray || slab.slice) {
+ HEAPU8.set(/** @type {!Uint8Array} */(slab), ret);
+ } else {
+ HEAPU8.set(new Uint8Array(slab), ret);
+ }
+ return ret;
+}
+
+
+
+
+// runtime_strings.js: Strings related runtime functions that are part of both MINIMAL_RUNTIME and regular runtime.
+
+// Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the given array that contains uint8 values, returns
+// a copy of that string as a Javascript String object.
+
+var UTF8Decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf8') : undefined;
+
+/**
+ * @param {number} idx
+ * @param {number=} maxBytesToRead
+ * @return {string}
+ */
+function UTF8ArrayToString(heap, idx, maxBytesToRead) {
+ var endIdx = idx + maxBytesToRead;
+ var endPtr = idx;
+ // TextDecoder needs to know the byte length in advance, it doesn't stop on null terminator by itself.
+ // Also, use the length info to avoid running tiny strings through TextDecoder, since .subarray() allocates garbage.
+ // (As a tiny code save trick, compare endPtr against endIdx using a negation, so that undefined means Infinity)
+ while (heap[endPtr] && !(endPtr >= endIdx)) ++endPtr;
+
+ if (endPtr - idx > 16 && heap.subarray && UTF8Decoder) {
+ return UTF8Decoder.decode(heap.subarray(idx, endPtr));
+ } else {
+ var str = '';
+ // If building with TextDecoder, we have already computed the string length above, so test loop end condition against that
+ while (idx < endPtr) {
+ // For UTF8 byte structure, see:
+ // http://en.wikipedia.org/wiki/UTF-8#Description
+ // https://www.ietf.org/rfc/rfc2279.txt
+ // https://tools.ietf.org/html/rfc3629
+ var u0 = heap[idx++];
+ if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; }
+ var u1 = heap[idx++] & 63;
+ if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; }
+ var u2 = heap[idx++] & 63;
+ if ((u0 & 0xF0) == 0xE0) {
+ u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
+ } else {
+ if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte 0x' + u0.toString(16) + ' encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!');
+ u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heap[idx++] & 63);
+ }
+
+ if (u0 < 0x10000) {
+ str += String.fromCharCode(u0);
+ } else {
+ var ch = u0 - 0x10000;
+ str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
+ }
+ }
+ }
+ return str;
+}
+
+// Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the emscripten HEAP, returns a
+// copy of that string as a Javascript String object.
+// maxBytesToRead: an optional length that specifies the maximum number of bytes to read. You can omit
+// this parameter to scan the string until the first \0 byte. If maxBytesToRead is
+// passed, and the string at [ptr, ptr+maxBytesToReadr[ contains a null byte in the
+// middle, then the string will cut short at that byte index (i.e. maxBytesToRead will
+// not produce a string of exact length [ptr, ptr+maxBytesToRead[)
+// N.B. mixing frequent uses of UTF8ToString() with and without maxBytesToRead may
+// throw JS JIT optimizations off, so it is worth to consider consistently using one
+// style or the other.
+/**
+ * @param {number} ptr
+ * @param {number=} maxBytesToRead
+ * @return {string}
+ */
+function UTF8ToString(ptr, maxBytesToRead) {
+ return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : '';
+}
+
+// Copies the given Javascript String object 'str' to the given byte array at address 'outIdx',
+// encoded in UTF8 form and null-terminated. The copy will require at most str.length*4+1 bytes of space in the HEAP.
+// Use the function lengthBytesUTF8 to compute the exact number of bytes (excluding null terminator) that this function will write.
+// Parameters:
+// str: the Javascript string to copy.
+// heap: the array to copy to. Each index in this array is assumed to be one 8-byte element.
+// outIdx: The starting offset in the array to begin the copying.
+// maxBytesToWrite: The maximum number of bytes this function can write to the array.
+// This count should include the null terminator,
+// i.e. if maxBytesToWrite=1, only the null terminator will be written and nothing else.
+// maxBytesToWrite=0 does not write any bytes to the output, not even the null terminator.
+// Returns the number of bytes written, EXCLUDING the null terminator.
+
+function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
+ if (!(maxBytesToWrite > 0)) // Parameter maxBytesToWrite is not optional. Negative values, 0, null, undefined and false each don't write out any bytes.
+ return 0;
+
+ var startIdx = outIdx;
+ var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator.
+ for (var i = 0; i < str.length; ++i) {
+ // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
+ // See http://unicode.org/faq/utf_bom.html#utf16-3
+ // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description and https://www.ietf.org/rfc/rfc2279.txt and https://tools.ietf.org/html/rfc3629
+ var u = str.charCodeAt(i); // possibly a lead surrogate
+ if (u >= 0xD800 && u <= 0xDFFF) {
+ var u1 = str.charCodeAt(++i);
+ u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF);
+ }
+ if (u <= 0x7F) {
+ if (outIdx >= endIdx) break;
+ heap[outIdx++] = u;
+ } else if (u <= 0x7FF) {
+ if (outIdx + 1 >= endIdx) break;
+ heap[outIdx++] = 0xC0 | (u >> 6);
+ heap[outIdx++] = 0x80 | (u & 63);
+ } else if (u <= 0xFFFF) {
+ if (outIdx + 2 >= endIdx) break;
+ heap[outIdx++] = 0xE0 | (u >> 12);
+ heap[outIdx++] = 0x80 | ((u >> 6) & 63);
+ heap[outIdx++] = 0x80 | (u & 63);
+ } else {
+ if (outIdx + 3 >= endIdx) break;
+ if (u >= 0x200000) warnOnce('Invalid Unicode code point 0x' + u.toString(16) + ' encountered when serializing a JS string to an UTF-8 string on the asm.js/wasm heap! (Valid unicode code points should be in range 0-0x1FFFFF).');
+ heap[outIdx++] = 0xF0 | (u >> 18);
+ heap[outIdx++] = 0x80 | ((u >> 12) & 63);
+ heap[outIdx++] = 0x80 | ((u >> 6) & 63);
+ heap[outIdx++] = 0x80 | (u & 63);
+ }
+ }
+ // Null-terminate the pointer to the buffer.
+ heap[outIdx] = 0;
+ return outIdx - startIdx;
+}
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in UTF8 form. The copy will require at most str.length*4+1 bytes of space in the HEAP.
+// Use the function lengthBytesUTF8 to compute the exact number of bytes (excluding null terminator) that this function will write.
+// Returns the number of bytes written, EXCLUDING the null terminator.
+
+function stringToUTF8(str, outPtr, maxBytesToWrite) {
+ assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!');
+ return stringToUTF8Array(str, HEAPU8,outPtr, maxBytesToWrite);
+}
+
+// Returns the number of bytes the given Javascript string takes if encoded as a UTF8 byte array, EXCLUDING the null terminator byte.
+function lengthBytesUTF8(str) {
+ var len = 0;
+ for (var i = 0; i < str.length; ++i) {
+ // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
+ // See http://unicode.org/faq/utf_bom.html#utf16-3
+ var u = str.charCodeAt(i); // possibly a lead surrogate
+ if (u >= 0xD800 && u <= 0xDFFF) u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt(++i) & 0x3FF);
+ if (u <= 0x7F) ++len;
+ else if (u <= 0x7FF) len += 2;
+ else if (u <= 0xFFFF) len += 3;
+ else len += 4;
+ }
+ return len;
+}
+
+
+
+
+
+// runtime_strings_extra.js: Strings related runtime functions that are available only in regular runtime.
+
+// Given a pointer 'ptr' to a null-terminated ASCII-encoded string in the emscripten HEAP, returns
+// a copy of that string as a Javascript String object.
+
+function AsciiToString(ptr) {
+ var str = '';
+ while (1) {
+ var ch = HEAPU8[((ptr++)>>0)];
+ if (!ch) return str;
+ str += String.fromCharCode(ch);
+ }
+}
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in ASCII form. The copy will require at most str.length+1 bytes of space in the HEAP.
+
+function stringToAscii(str, outPtr) {
+ return writeAsciiToMemory(str, outPtr, false);
+}
+
+// Given a pointer 'ptr' to a null-terminated UTF16LE-encoded string in the emscripten HEAP, returns
+// a copy of that string as a Javascript String object.
+
+var UTF16Decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : undefined;
+
+function UTF16ToString(ptr, maxBytesToRead) {
+ assert(ptr % 2 == 0, 'Pointer passed to UTF16ToString must be aligned to two bytes!');
+ var endPtr = ptr;
+ // TextDecoder needs to know the byte length in advance, it doesn't stop on null terminator by itself.
+ // Also, use the length info to avoid running tiny strings through TextDecoder, since .subarray() allocates garbage.
+ var idx = endPtr >> 1;
+ var maxIdx = idx + maxBytesToRead / 2;
+ // If maxBytesToRead is not passed explicitly, it will be undefined, and this
+ // will always evaluate to true. This saves on code size.
+ while (!(idx >= maxIdx) && HEAPU16[idx]) ++idx;
+ endPtr = idx << 1;
+
+ if (endPtr - ptr > 32 && UTF16Decoder) {
+ return UTF16Decoder.decode(HEAPU8.subarray(ptr, endPtr));
+ } else {
+ var i = 0;
+
+ var str = '';
+ while (1) {
+ var codeUnit = HEAP16[(((ptr)+(i*2))>>1)];
+ if (codeUnit == 0 || i == maxBytesToRead / 2) return str;
+ ++i;
+ // fromCharCode constructs a character from a UTF-16 code unit, so we can pass the UTF16 string right through.
+ str += String.fromCharCode(codeUnit);
+ }
+ }
+}
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in UTF16 form. The copy will require at most str.length*4+2 bytes of space in the HEAP.
+// Use the function lengthBytesUTF16() to compute the exact number of bytes (excluding null terminator) that this function will write.
+// Parameters:
+// str: the Javascript string to copy.
+// outPtr: Byte address in Emscripten HEAP where to write the string to.
+// maxBytesToWrite: The maximum number of bytes this function can write to the array. This count should include the null
+// terminator, i.e. if maxBytesToWrite=2, only the null terminator will be written and nothing else.
+// maxBytesToWrite<2 does not write any bytes to the output, not even the null terminator.
+// Returns the number of bytes written, EXCLUDING the null terminator.
+
+function stringToUTF16(str, outPtr, maxBytesToWrite) {
+ assert(outPtr % 2 == 0, 'Pointer passed to stringToUTF16 must be aligned to two bytes!');
+ assert(typeof maxBytesToWrite == 'number', 'stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!');
+ // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed.
+ if (maxBytesToWrite === undefined) {
+ maxBytesToWrite = 0x7FFFFFFF;
+ }
+ if (maxBytesToWrite < 2) return 0;
+ maxBytesToWrite -= 2; // Null terminator.
+ var startPtr = outPtr;
+ var numCharsToWrite = (maxBytesToWrite < str.length*2) ? (maxBytesToWrite / 2) : str.length;
+ for (var i = 0; i < numCharsToWrite; ++i) {
+ // charCodeAt returns a UTF-16 encoded code unit, so it can be directly written to the HEAP.
+ var codeUnit = str.charCodeAt(i); // possibly a lead surrogate
+ HEAP16[((outPtr)>>1)]=codeUnit;
+ outPtr += 2;
+ }
+ // Null-terminate the pointer to the HEAP.
+ HEAP16[((outPtr)>>1)]=0;
+ return outPtr - startPtr;
+}
+
+// Returns the number of bytes the given Javascript string takes if encoded as a UTF16 byte array, EXCLUDING the null terminator byte.
+
+function lengthBytesUTF16(str) {
+ return str.length*2;
+}
+
+function UTF32ToString(ptr, maxBytesToRead) {
+ assert(ptr % 4 == 0, 'Pointer passed to UTF32ToString must be aligned to four bytes!');
+ var i = 0;
+
+ var str = '';
+ // If maxBytesToRead is not passed explicitly, it will be undefined, and this
+ // will always evaluate to true. This saves on code size.
+ while (!(i >= maxBytesToRead / 4)) {
+ var utf32 = HEAP32[(((ptr)+(i*4))>>2)];
+ if (utf32 == 0) break;
+ ++i;
+ // Gotcha: fromCharCode constructs a character from a UTF-16 encoded code (pair), not from a Unicode code point! So encode the code point to UTF-16 for constructing.
+ // See http://unicode.org/faq/utf_bom.html#utf16-3
+ if (utf32 >= 0x10000) {
+ var ch = utf32 - 0x10000;
+ str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
+ } else {
+ str += String.fromCharCode(utf32);
+ }
+ }
+ return str;
+}
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in UTF32 form. The copy will require at most str.length*4+4 bytes of space in the HEAP.
+// Use the function lengthBytesUTF32() to compute the exact number of bytes (excluding null terminator) that this function will write.
+// Parameters:
+// str: the Javascript string to copy.
+// outPtr: Byte address in Emscripten HEAP where to write the string to.
+// maxBytesToWrite: The maximum number of bytes this function can write to the array. This count should include the null
+// terminator, i.e. if maxBytesToWrite=4, only the null terminator will be written and nothing else.
+// maxBytesToWrite<4 does not write any bytes to the output, not even the null terminator.
+// Returns the number of bytes written, EXCLUDING the null terminator.
+
+function stringToUTF32(str, outPtr, maxBytesToWrite) {
+ assert(outPtr % 4 == 0, 'Pointer passed to stringToUTF32 must be aligned to four bytes!');
+ assert(typeof maxBytesToWrite == 'number', 'stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!');
+ // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed.
+ if (maxBytesToWrite === undefined) {
+ maxBytesToWrite = 0x7FFFFFFF;
+ }
+ if (maxBytesToWrite < 4) return 0;
+ var startPtr = outPtr;
+ var endPtr = startPtr + maxBytesToWrite - 4;
+ for (var i = 0; i < str.length; ++i) {
+ // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap.
+ // See http://unicode.org/faq/utf_bom.html#utf16-3
+ var codeUnit = str.charCodeAt(i); // possibly a lead surrogate
+ if (codeUnit >= 0xD800 && codeUnit <= 0xDFFF) {
+ var trailSurrogate = str.charCodeAt(++i);
+ codeUnit = 0x10000 + ((codeUnit & 0x3FF) << 10) | (trailSurrogate & 0x3FF);
+ }
+ HEAP32[((outPtr)>>2)]=codeUnit;
+ outPtr += 4;
+ if (outPtr + 4 > endPtr) break;
+ }
+ // Null-terminate the pointer to the HEAP.
+ HEAP32[((outPtr)>>2)]=0;
+ return outPtr - startPtr;
+}
+
+// Returns the number of bytes the given Javascript string takes if encoded as a UTF16 byte array, EXCLUDING the null terminator byte.
+
+function lengthBytesUTF32(str) {
+ var len = 0;
+ for (var i = 0; i < str.length; ++i) {
+ // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap.
+ // See http://unicode.org/faq/utf_bom.html#utf16-3
+ var codeUnit = str.charCodeAt(i);
+ if (codeUnit >= 0xD800 && codeUnit <= 0xDFFF) ++i; // possibly a lead surrogate, so skip over the tail surrogate.
+ len += 4;
+ }
+
+ return len;
+}
+
+// Allocate heap space for a JS string, and write it there.
+// It is the responsibility of the caller to free() that memory.
+function allocateUTF8(str) {
+ var size = lengthBytesUTF8(str) + 1;
+ var ret = _malloc(size);
+ if (ret) stringToUTF8Array(str, HEAP8, ret, size);
+ return ret;
+}
+
+// Allocate stack space for a JS string, and write it there.
+function allocateUTF8OnStack(str) {
+ var size = lengthBytesUTF8(str) + 1;
+ var ret = stackAlloc(size);
+ stringToUTF8Array(str, HEAP8, ret, size);
+ return ret;
+}
+
+// Deprecated: This function should not be called because it is unsafe and does not provide
+// a maximum length limit of how many bytes it is allowed to write. Prefer calling the
+// function stringToUTF8Array() instead, which takes in a maximum length that can be used
+// to be secure from out of bounds writes.
+/** @deprecated
+ @param {boolean=} dontAddNull */
+function writeStringToMemory(string, buffer, dontAddNull) {
+ warnOnce('writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!');
+
+ var /** @type {number} */ lastChar, /** @type {number} */ end;
+ if (dontAddNull) {
+ // stringToUTF8Array always appends null. If we don't want to do that, remember the
+ // character that existed at the location where the null will be placed, and restore
+ // that after the write (below).
+ end = buffer + lengthBytesUTF8(string);
+ lastChar = HEAP8[end];
+ }
+ stringToUTF8(string, buffer, Infinity);
+ if (dontAddNull) HEAP8[end] = lastChar; // Restore the value under the null character.
+}
+
+function writeArrayToMemory(array, buffer) {
+ assert(array.length >= 0, 'writeArrayToMemory array must have a length (should be an array or typed array)')
+ HEAP8.set(array, buffer);
+}
+
+/** @param {boolean=} dontAddNull */
+function writeAsciiToMemory(str, buffer, dontAddNull) {
+ for (var i = 0; i < str.length; ++i) {
+ assert(str.charCodeAt(i) === str.charCodeAt(i)&0xff);
+ HEAP8[((buffer++)>>0)]=str.charCodeAt(i);
+ }
+ // Null-terminate the pointer to the HEAP.
+ if (!dontAddNull) HEAP8[((buffer)>>0)]=0;
+}
+
+
+
+// Memory management
+
+var PAGE_SIZE = 16384;
+var WASM_PAGE_SIZE = 65536;
+
+function alignUp(x, multiple) {
+ if (x % multiple > 0) {
+ x += multiple - (x % multiple);
+ }
+ return x;
+}
+
+var HEAP,
+/** @type {ArrayBuffer} */
+ buffer,
+/** @type {Int8Array} */
+ HEAP8,
+/** @type {Uint8Array} */
+ HEAPU8,
+/** @type {Int16Array} */
+ HEAP16,
+/** @type {Uint16Array} */
+ HEAPU16,
+/** @type {Int32Array} */
+ HEAP32,
+/** @type {Uint32Array} */
+ HEAPU32,
+/** @type {Float32Array} */
+ HEAPF32,
+/** @type {Float64Array} */
+ HEAPF64;
+
+function updateGlobalBufferAndViews(buf) {
+ buffer = buf;
+ Module['HEAP8'] = HEAP8 = new Int8Array(buf);
+ Module['HEAP16'] = HEAP16 = new Int16Array(buf);
+ Module['HEAP32'] = HEAP32 = new Int32Array(buf);
+ Module['HEAPU8'] = HEAPU8 = new Uint8Array(buf);
+ Module['HEAPU16'] = HEAPU16 = new Uint16Array(buf);
+ Module['HEAPU32'] = HEAPU32 = new Uint32Array(buf);
+ Module['HEAPF32'] = HEAPF32 = new Float32Array(buf);
+ Module['HEAPF64'] = HEAPF64 = new Float64Array(buf);
+}
+
+var STACK_BASE = 5323600,
+ STACKTOP = STACK_BASE,
+ STACK_MAX = 80720;
+
+assert(STACK_BASE % 16 === 0, 'stack must start aligned');
+
+
+
+var TOTAL_STACK = 5242880;
+if (Module['TOTAL_STACK']) assert(TOTAL_STACK === Module['TOTAL_STACK'], 'the stack size can no longer be determined at runtime')
+
+var INITIAL_INITIAL_MEMORY = Module['INITIAL_MEMORY'] || 16777216;if (!Object.getOwnPropertyDescriptor(Module, 'INITIAL_MEMORY')) Object.defineProperty(Module, 'INITIAL_MEMORY', { configurable: true, get: function() { abort('Module.INITIAL_MEMORY has been replaced with plain INITIAL_INITIAL_MEMORY (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)') } });
+
+assert(INITIAL_INITIAL_MEMORY >= TOTAL_STACK, 'INITIAL_MEMORY should be larger than TOTAL_STACK, was ' + INITIAL_INITIAL_MEMORY + '! (TOTAL_STACK=' + TOTAL_STACK + ')');
+
+// check for full engine support (use string 'subarray' to avoid closure compiler confusion)
+assert(typeof Int32Array !== 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray !== undefined && Int32Array.prototype.set !== undefined,
+ 'JS engine does not provide full typed array support');
+
+
+// In non-standalone/normal mode, we create the memory here.
+
+
+
+// Create the main memory. (Note: this isn't used in STANDALONE_WASM mode since the wasm
+// memory is created in the wasm, not in JS.)
+
+ if (Module['wasmMemory']) {
+ wasmMemory = Module['wasmMemory'];
+ } else
+ {
+ wasmMemory = new WebAssembly.Memory({
+ 'initial': INITIAL_INITIAL_MEMORY / WASM_PAGE_SIZE
+ ,
+ 'maximum': INITIAL_INITIAL_MEMORY / WASM_PAGE_SIZE
+ });
+ }
+
+
+if (wasmMemory) {
+ buffer = wasmMemory.buffer;
+}
+
+// If the user provides an incorrect length, just use that length instead rather than providing the user to
+// specifically provide the memory length with Module['INITIAL_MEMORY'].
+INITIAL_INITIAL_MEMORY = buffer.byteLength;
+assert(INITIAL_INITIAL_MEMORY % WASM_PAGE_SIZE === 0);
+updateGlobalBufferAndViews(buffer);
+
+
+
+
+
+
+
+
+
+
+// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode.
+function writeStackCookie() {
+ assert((STACK_MAX & 3) == 0);
+ // The stack grows downwards
+ HEAPU32[(STACK_MAX >> 2)+1] = 0x2135467;
+ HEAPU32[(STACK_MAX >> 2)+2] = 0x89BACDFE;
+ // Also test the global address 0 for integrity.
+ // We don't do this with ASan because ASan does its own checks for this.
+ HEAP32[0] = 0x63736d65; /* 'emsc' */
+}
+
+function checkStackCookie() {
+ var cookie1 = HEAPU32[(STACK_MAX >> 2)+1];
+ var cookie2 = HEAPU32[(STACK_MAX >> 2)+2];
+ if (cookie1 != 0x2135467 || cookie2 != 0x89BACDFE) {
+ abort('Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x' + cookie2.toString(16) + ' ' + cookie1.toString(16));
+ }
+ // Also test the global address 0 for integrity.
+ // We don't do this with ASan because ASan does its own checks for this.
+ if (HEAP32[0] !== 0x63736d65 /* 'emsc' */) abort('Runtime error: The application has corrupted its heap memory area (address zero)!');
+}
+
+
+
+
+
+// Endianness check (note: assumes compiler arch was little-endian)
+(function() {
+ var h16 = new Int16Array(1);
+ var h8 = new Int8Array(h16.buffer);
+ h16[0] = 0x6373;
+ if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian!';
+})();
+
+function abortFnPtrError(ptr, sig) {
+ abort("Invalid function pointer " + ptr + " called with signature '" + sig + "'. Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? Or calling a function with an incorrect type, which will fail? (it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this). Build with ASSERTIONS=2 for more info.");
+}
+
+
+
+var __ATPRERUN__ = []; // functions called before the runtime is initialized
+var __ATINIT__ = []; // functions called during startup
+var __ATMAIN__ = []; // functions called when main() is to be run
+var __ATEXIT__ = []; // functions called during shutdown
+var __ATPOSTRUN__ = []; // functions called after the main() is called
+
+var runtimeInitialized = false;
+var runtimeExited = false;
+
+
+function preRun() {
+
+ if (Module['preRun']) {
+ if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']];
+ while (Module['preRun'].length) {
+ addOnPreRun(Module['preRun'].shift());
+ }
+ }
+
+ callRuntimeCallbacks(__ATPRERUN__);
+}
+
+function initRuntime() {
+ checkStackCookie();
+ assert(!runtimeInitialized);
+ runtimeInitialized = true;
+
+ callRuntimeCallbacks(__ATINIT__);
+}
+
+function preMain() {
+ checkStackCookie();
+
+ callRuntimeCallbacks(__ATMAIN__);
+}
+
+function exitRuntime() {
+ checkStackCookie();
+ runtimeExited = true;
+}
+
+function postRun() {
+ checkStackCookie();
+
+ if (Module['postRun']) {
+ if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']];
+ while (Module['postRun'].length) {
+ addOnPostRun(Module['postRun'].shift());
+ }
+ }
+
+ callRuntimeCallbacks(__ATPOSTRUN__);
+}
+
+function addOnPreRun(cb) {
+ __ATPRERUN__.unshift(cb);
+}
+
+function addOnInit(cb) {
+ __ATINIT__.unshift(cb);
+}
+
+function addOnPreMain(cb) {
+ __ATMAIN__.unshift(cb);
+}
+
+function addOnExit(cb) {
+}
+
+function addOnPostRun(cb) {
+ __ATPOSTRUN__.unshift(cb);
+}
+
+
+
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
+
+assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill');
+assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill');
+assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill');
+assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill');
+
+
+
+// A counter of dependencies for calling run(). If we need to
+// do asynchronous work before running, increment this and
+// decrement it. Incrementing must happen in a place like
+// Module.preRun (used by emcc to add file preloading).
+// Note that you can add dependencies in preRun, even though
+// it happens right before run - run will be postponed until
+// the dependencies are met.
+var runDependencies = 0;
+var runDependencyWatcher = null;
+var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled
+var runDependencyTracking = {};
+
+function getUniqueRunDependency(id) {
+ var orig = id;
+ while (1) {
+ if (!runDependencyTracking[id]) return id;
+ id = orig + Math.random();
+ }
+}
+
+function addRunDependency(id) {
+ runDependencies++;
+
+ if (Module['monitorRunDependencies']) {
+ Module['monitorRunDependencies'](runDependencies);
+ }
+
+ if (id) {
+ assert(!runDependencyTracking[id]);
+ runDependencyTracking[id] = 1;
+ if (runDependencyWatcher === null && typeof setInterval !== 'undefined') {
+ // Check for missing dependencies every few seconds
+ runDependencyWatcher = setInterval(function() {
+ if (ABORT) {
+ clearInterval(runDependencyWatcher);
+ runDependencyWatcher = null;
+ return;
+ }
+ var shown = false;
+ for (var dep in runDependencyTracking) {
+ if (!shown) {
+ shown = true;
+ err('still waiting on run dependencies:');
+ }
+ err('dependency: ' + dep);
+ }
+ if (shown) {
+ err('(end of list)');
+ }
+ }, 10000);
+ }
+ } else {
+ err('warning: run dependency added without ID');
+ }
+}
+
+function removeRunDependency(id) {
+ runDependencies--;
+
+ if (Module['monitorRunDependencies']) {
+ Module['monitorRunDependencies'](runDependencies);
+ }
+
+ if (id) {
+ assert(runDependencyTracking[id]);
+ delete runDependencyTracking[id];
+ } else {
+ err('warning: run dependency removed without ID');
+ }
+ if (runDependencies == 0) {
+ if (runDependencyWatcher !== null) {
+ clearInterval(runDependencyWatcher);
+ runDependencyWatcher = null;
+ }
+ if (dependenciesFulfilled) {
+ var callback = dependenciesFulfilled;
+ dependenciesFulfilled = null;
+ callback(); // can add another dependenciesFulfilled
+ }
+ }
+}
+
+Module["preloadedImages"] = {}; // maps url to image data
+Module["preloadedAudios"] = {}; // maps url to audio data
+
+/** @param {string|number=} what */
+function abort(what) {
+ if (Module['onAbort']) {
+ Module['onAbort'](what);
+ }
+
+ what += '';
+ err(what);
+
+ ABORT = true;
+ EXITSTATUS = 1;
+
+ var output = 'abort(' + what + ') at ' + stackTrace();
+ what = output;
+
+ // Use a wasm runtime error, because a JS error might be seen as a foreign
+ // exception, which means we'd run destructors on it. We need the error to
+ // simply make the program stop.
+ var e = new WebAssembly.RuntimeError(what);
+
+ // Throw the error whether or not MODULARIZE is set because abort is used
+ // in code paths apart from instantiation where an exception is expected
+ // to be thrown when abort is called.
+ throw e;
+}
+
+var memoryInitializer = null;
+
+
+
+
+
+
+
+// show errors on likely calls to FS when it was not included
+var FS = {
+ error: function() {
+ abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -s FORCE_FILESYSTEM=1');
+ },
+ init: function() { FS.error() },
+ createDataFile: function() { FS.error() },
+ createPreloadedFile: function() { FS.error() },
+ createLazyFile: function() { FS.error() },
+ open: function() { FS.error() },
+ mkdev: function() { FS.error() },
+ registerDevice: function() { FS.error() },
+ analyzePath: function() { FS.error() },
+ loadFilesFromDB: function() { FS.error() },
+
+ ErrnoError: function ErrnoError() { FS.error() },
+};
+Module['FS_createDataFile'] = FS.createDataFile;
+Module['FS_createPreloadedFile'] = FS.createPreloadedFile;
+
+
+
+
+function hasPrefix(str, prefix) {
+ return String.prototype.startsWith ?
+ str.startsWith(prefix) :
+ str.indexOf(prefix) === 0;
+}
+
+// Prefix of data URIs emitted by SINGLE_FILE and related options.
+var dataURIPrefix = 'data:application/octet-stream;base64,';
+
+// Indicates whether filename is a base64 data URI.
+function isDataURI(filename) {
+ return hasPrefix(filename, dataURIPrefix);
+}
+
+var fileURIPrefix = "file://";
+
+// Indicates whether filename is delivered via file protocol (as opposed to http/https)
+function isFileURI(filename) {
+ return hasPrefix(filename, fileURIPrefix);
+}
+
+
+
+function createExportWrapper(name, fixedasm) {
+ return function() {
+ var displayName = name;
+ var asm = fixedasm;
+ if (!fixedasm) {
+ asm = Module['asm'];
+ }
+ assert(runtimeInitialized, 'native function `' + displayName + '` called before runtime initialization');
+ assert(!runtimeExited, 'native function `' + displayName + '` called after runtime exit (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
+ if (!asm[name]) {
+ assert(asm[name], 'exported native function `' + displayName + '` not found');
+ }
+ return asm[name].apply(null, arguments);
+ };
+}
+
+
+var wasmBinaryFile = 'ulisp.wasm';
+if (!isDataURI(wasmBinaryFile)) {
+ wasmBinaryFile = locateFile(wasmBinaryFile);
+}
+
+function getBinary() {
+ try {
+ if (wasmBinary) {
+ return new Uint8Array(wasmBinary);
+ }
+
+ if (readBinary) {
+ return readBinary(wasmBinaryFile);
+ } else {
+ throw "both async and sync fetching of the wasm failed";
+ }
+ }
+ catch (err) {
+ abort(err);
+ }
+}
+
+function getBinaryPromise() {
+ // If we don't have the binary yet, and have the Fetch api, use that;
+ // in some environments, like Electron's render process, Fetch api may be present, but have a different context than expected, let's only use it on the Web
+ if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && typeof fetch === 'function'
+ // Let's not use fetch to get objects over file:// as it's most likely Cordova which doesn't support fetch for file://
+ && !isFileURI(wasmBinaryFile)
+ ) {
+ return fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function(response) {
+ if (!response['ok']) {
+ throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
+ }
+ return response['arrayBuffer']();
+ }).catch(function () {
+ return getBinary();
+ });
+ }
+ // Otherwise, getBinary should be able to get it synchronously
+ return Promise.resolve().then(getBinary);
+}
+
+
+
+// Create the wasm instance.
+// Receives the wasm imports, returns the exports.
+function createWasm() {
+ // prepare imports
+ var info = {
+ 'env': asmLibraryArg,
+ 'wasi_snapshot_preview1': asmLibraryArg
+ };
+ // Load the wasm module and create an instance of using native support in the JS engine.
+ // handle a generated wasm instance, receiving its exports and
+ // performing other necessary setup
+ /** @param {WebAssembly.Module=} module*/
+ function receiveInstance(instance, module) {
+ var exports = instance.exports;
+
+
+
+
+ Module['asm'] = exports;
+
+ wasmTable = Module['asm']['__indirect_function_table'];
+ assert(wasmTable, "table not found in wasm exports");
+
+
+ removeRunDependency('wasm-instantiate');
+ }
+ // we can't run yet (except in a pthread, where we have a custom sync instantiator)
+ addRunDependency('wasm-instantiate');
+
+
+ // Async compilation can be confusing when an error on the page overwrites Module
+ // (for example, if the order of elements is wrong, and the one defining Module is
+ // later), so we save Module and check it later.
+ var trueModule = Module;
+ function receiveInstantiatedSource(output) {
+ // 'output' is a WebAssemblyInstantiatedSource object which has both the module and instance.
+ // receiveInstance() will swap in the exports (to Module.asm) so they can be called
+ assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?');
+ trueModule = null;
+ // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line.
+ // When the regression is fixed, can restore the above USE_PTHREADS-enabled path.
+ receiveInstance(output['instance']);
+ }
+
+
+ function instantiateArrayBuffer(receiver) {
+ return getBinaryPromise().then(function(binary) {
+ return WebAssembly.instantiate(binary, info);
+ }).then(receiver, function(reason) {
+ err('failed to asynchronously prepare wasm: ' + reason);
+
+
+ abort(reason);
+ });
+ }
+
+ // Prefer streaming instantiation if available.
+ function instantiateAsync() {
+ if (!wasmBinary &&
+ typeof WebAssembly.instantiateStreaming === 'function' &&
+ !isDataURI(wasmBinaryFile) &&
+ // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously.
+ !isFileURI(wasmBinaryFile) &&
+ typeof fetch === 'function') {
+ fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function (response) {
+ var result = WebAssembly.instantiateStreaming(response, info);
+ return result.then(receiveInstantiatedSource, function(reason) {
+ // We expect the most common failure cause to be a bad MIME type for the binary,
+ // in which case falling back to ArrayBuffer instantiation should work.
+ err('wasm streaming compile failed: ' + reason);
+ err('falling back to ArrayBuffer instantiation');
+ return instantiateArrayBuffer(receiveInstantiatedSource);
+ });
+ });
+ } else {
+ return instantiateArrayBuffer(receiveInstantiatedSource);
+ }
+ }
+ // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback
+ // to manually instantiate the Wasm module themselves. This allows pages to run the instantiation parallel
+ // to any other async startup actions they are performing.
+ if (Module['instantiateWasm']) {
+ try {
+ var exports = Module['instantiateWasm'](info, receiveInstance);
+ return exports;
+ } catch(e) {
+ err('Module.instantiateWasm callback failed with error: ' + e);
+ return false;
+ }
+ }
+
+ instantiateAsync();
+ return {}; // no exports yet; we'll fill them in later
+}
+
+// Globals used by JS i64 conversions
+var tempDouble;
+var tempI64;
+
+// === Body ===
+
+var ASM_CONSTS = {
+
+};
+
+
+
+
+
+// {{PRE_LIBRARY}}
+
+
+ function abortStackOverflow(allocSize) {
+ abort('Stack overflow! Attempted to allocate ' + allocSize + ' bytes on the stack, but stack has only ' + (STACK_MAX - stackSave() + allocSize) + ' bytes available!');
+ }
+
+ function callRuntimeCallbacks(callbacks) {
+ while(callbacks.length > 0) {
+ var callback = callbacks.shift();
+ if (typeof callback == 'function') {
+ callback(Module); // Pass the module as the first argument.
+ continue;
+ }
+ var func = callback.func;
+ if (typeof func === 'number') {
+ if (callback.arg === undefined) {
+ wasmTable.get(func)();
+ } else {
+ wasmTable.get(func)(callback.arg);
+ }
+ } else {
+ func(callback.arg === undefined ? null : callback.arg);
+ }
+ }
+ }
+
+ function demangle(func) {
+ warnOnce('warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling');
+ return func;
+ }
+
+ function demangleAll(text) {
+ var regex =
+ /\b_Z[\w\d_]+/g;
+ return text.replace(regex,
+ function(x) {
+ var y = demangle(x);
+ return x === y ? x : (y + ' [' + x + ']');
+ });
+ }
+
+ function dynCallLegacy(sig, ptr, args) {
+ assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
+ if (args && args.length) {
+ // j (64-bit integer) must be passed in as two numbers [low 32, high 32].
+ assert(args.length === sig.substring(1).replace(/j/g, '--').length);
+ } else {
+ assert(sig.length == 1);
+ }
+ if (args && args.length) {
+ return Module['dynCall_' + sig].apply(null, [ptr].concat(args));
+ }
+ return Module['dynCall_' + sig].call(null, ptr);
+ }
+ function dynCall(sig, ptr, args) {
+ // Without WASM_BIGINT support we cannot directly call function with i64 as
+ // part of thier signature, so we rely the dynCall functions generated by
+ // wasm-emscripten-finalize
+ if (sig.indexOf('j') != -1) {
+ return dynCallLegacy(sig, ptr, args);
+ }
+
+ return wasmTable.get(ptr).apply(null, args)
+ }
+
+ function jsStackTrace() {
+ var error = new Error();
+ if (!error.stack) {
+ // IE10+ special cases: It does have callstack info, but it is only populated if an Error object is thrown,
+ // so try that as a special-case.
+ try {
+ throw new Error();
+ } catch(e) {
+ error = e;
+ }
+ if (!error.stack) {
+ return '(no stack trace available)';
+ }
+ }
+ return error.stack.toString();
+ }
+
+ function stackTrace() {
+ var js = jsStackTrace();
+ if (Module['extraStackTrace']) js += '\n' + Module['extraStackTrace']();
+ return demangleAll(js);
+ }
+
+ function ___assert_fail(condition, filename, line, func) {
+ abort('Assertion failed: ' + UTF8ToString(condition) + ', at: ' + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']);
+ }
+
+ function _longjmp(env, value) {
+ _setThrew(env, value || 1);
+ throw 'longjmp';
+ }
+ function _emscripten_longjmp(a0,a1
+ ) {
+ return _longjmp(a0,a1);
+ }
+
+ function _emscripten_longjmp_jmpbuf(a0,a1
+ ) {
+ return _longjmp(a0,a1);
+ }
+
+ function _emscripten_memcpy_big(dest, src, num) {
+ HEAPU8.copyWithin(dest, src, src + num);
+ }
+
+ function _emscripten_get_heap_size() {
+ return HEAPU8.length;
+ }
+
+ function abortOnCannotGrowMemory(requestedSize) {
+ abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with -s INITIAL_MEMORY=X with X higher than the current value ' + HEAP8.length + ', (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ');
+ }
+ function _emscripten_resize_heap(requestedSize) {
+ requestedSize = requestedSize >>> 0;
+ abortOnCannotGrowMemory(requestedSize);
+ }
+
+ function flush_NO_FILESYSTEM() {
+ // flush anything remaining in the buffers during shutdown
+ if (typeof _fflush !== 'undefined') _fflush(0);
+ var buffers = SYSCALLS.buffers;
+ if (buffers[1].length) SYSCALLS.printChar(1, 10);
+ if (buffers[2].length) SYSCALLS.printChar(2, 10);
+ }
+
+ var PATH={splitPath:function(filename) {
+ var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+ return splitPathRe.exec(filename).slice(1);
+ },normalizeArray:function(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up; up--) {
+ parts.unshift('..');
+ }
+ }
+ return parts;
+ },normalize:function(path) {
+ var isAbsolute = path.charAt(0) === '/',
+ trailingSlash = path.substr(-1) === '/';
+ // Normalize the path
+ path = PATH.normalizeArray(path.split('/').filter(function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+ return (isAbsolute ? '/' : '') + path;
+ },dirname:function(path) {
+ var result = PATH.splitPath(path),
+ root = result[0],
+ dir = result[1];
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+ return root + dir;
+ },basename:function(path) {
+ // EMSCRIPTEN return '/'' for '/', not an empty string
+ if (path === '/') return '/';
+ path = PATH.normalize(path);
+ path = path.replace(/\/$/, "");
+ var lastSlash = path.lastIndexOf('/');
+ if (lastSlash === -1) return path;
+ return path.substr(lastSlash+1);
+ },extname:function(path) {
+ return PATH.splitPath(path)[3];
+ },join:function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return PATH.normalize(paths.join('/'));
+ },join2:function(l, r) {
+ return PATH.normalize(l + '/' + r);
+ }};
+ var SYSCALLS={mappings:{},buffers:[null,[],[]],printChar:function(stream, curr) {
+ var buffer = SYSCALLS.buffers[stream];
+ assert(buffer);
+ if (curr === 0 || curr === 10) {
+ (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
+ buffer.length = 0;
+ } else {
+ buffer.push(curr);
+ }
+ },varargs:undefined,get:function() {
+ assert(SYSCALLS.varargs != undefined);
+ SYSCALLS.varargs += 4;
+ var ret = HEAP32[(((SYSCALLS.varargs)-(4))>>2)];
+ return ret;
+ },getStr:function(ptr) {
+ var ret = UTF8ToString(ptr);
+ return ret;
+ },get64:function(low, high) {
+ if (low >= 0) assert(high === 0);
+ else assert(high === -1);
+ return low;
+ }};
+ function _fd_write(fd, iov, iovcnt, pnum) {
+ // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0
+ var num = 0;
+ for (var i = 0; i < iovcnt; i++) {
+ var ptr = HEAP32[(((iov)+(i*8))>>2)];
+ var len = HEAP32[(((iov)+(i*8 + 4))>>2)];
+ for (var j = 0; j < len; j++) {
+ SYSCALLS.printChar(fd, HEAPU8[ptr+j]);
+ }
+ num += len;
+ }
+ HEAP32[((pnum)>>2)]=num
+ return 0;
+ }
+
+ function _getTempRet0() {
+ return (getTempRet0() | 0);
+ }
+
+ function _setTempRet0($i) {
+ setTempRet0(($i) | 0);
+ }
+var ASSERTIONS = true;
+
+
+
+/** @type {function(string, boolean=, number=)} */
+function intArrayFromString(stringy, dontAddNull, length) {
+ var len = length > 0 ? length : lengthBytesUTF8(stringy)+1;
+ var u8array = new Array(len);
+ var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length);
+ if (dontAddNull) u8array.length = numBytesWritten;
+ return u8array;
+}
+
+function intArrayToString(array) {
+ var ret = [];
+ for (var i = 0; i < array.length; i++) {
+ var chr = array[i];
+ if (chr > 0xFF) {
+ if (ASSERTIONS) {
+ assert(false, 'Character code ' + chr + ' (' + String.fromCharCode(chr) + ') at offset ' + i + ' not in 0x00-0xFF.');
+ }
+ chr &= 0xFF;
+ }
+ ret.push(String.fromCharCode(chr));
+ }
+ return ret.join('');
+}
+
+
+
+__ATINIT__.push({ func: function() { ___wasm_call_ctors() } });
+var asmLibraryArg = { "__assert_fail": ___assert_fail, "emscripten_longjmp": _emscripten_longjmp, "emscripten_longjmp_jmpbuf": _emscripten_longjmp_jmpbuf, "emscripten_memcpy_big": _emscripten_memcpy_big, "emscripten_resize_heap": _emscripten_resize_heap, "fd_write": _fd_write, "getTempRet0": _getTempRet0, "invoke_iii": invoke_iii, "invoke_v": invoke_v, "invoke_vi": invoke_vi, "memory": wasmMemory, "setTempRet0": _setTempRet0 };
+var asm = createWasm();
+/** @type {function(...*):?} */
+var ___wasm_call_ctors = Module["___wasm_call_ctors"] = createExportWrapper("__wasm_call_ctors");
+
+/** @type {function(...*):?} */
+var _setup_ulisp = Module["_setup_ulisp"] = createExportWrapper("setup_ulisp");
+
+/** @type {function(...*):?} */
+var _execute_ulisp = Module["_execute_ulisp"] = createExportWrapper("execute_ulisp");
+
+/** @type {function(...*):?} */
+var _malloc = Module["_malloc"] = createExportWrapper("malloc");
+
+/** @type {function(...*):?} */
+var _saveSetjmp = Module["_saveSetjmp"] = createExportWrapper("saveSetjmp");
+
+/** @type {function(...*):?} */
+var _testSetjmp = Module["_testSetjmp"] = createExportWrapper("testSetjmp");
+
+/** @type {function(...*):?} */
+var _free = Module["_free"] = createExportWrapper("free");
+
+/** @type {function(...*):?} */
+var _clear_simulation_events = Module["_clear_simulation_events"] = createExportWrapper("clear_simulation_events");
+
+/** @type {function(...*):?} */
+var _get_simulation_events = Module["_get_simulation_events"] = createExportWrapper("get_simulation_events");
+
+/** @type {function(...*):?} */
+var ___errno_location = Module["___errno_location"] = createExportWrapper("__errno_location");
+
+/** @type {function(...*):?} */
+var _fflush = Module["_fflush"] = createExportWrapper("fflush");
+
+/** @type {function(...*):?} */
+var stackSave = Module["stackSave"] = createExportWrapper("stackSave");
+
+/** @type {function(...*):?} */
+var stackRestore = Module["stackRestore"] = createExportWrapper("stackRestore");
+
+/** @type {function(...*):?} */
+var stackAlloc = Module["stackAlloc"] = createExportWrapper("stackAlloc");
+
+/** @type {function(...*):?} */
+var _realloc = Module["_realloc"] = createExportWrapper("realloc");
+
+/** @type {function(...*):?} */
+var _setThrew = Module["_setThrew"] = createExportWrapper("setThrew");
+
+/** @type {function(...*):?} */
+var dynCall_jiji = Module["dynCall_jiji"] = createExportWrapper("dynCall_jiji");
+
+
+function invoke_v(index) {
+ var sp = stackSave();
+ try {
+ wasmTable.get(index)();
+ } catch(e) {
+ stackRestore(sp);
+ if (e !== e+0 && e !== 'longjmp') throw e;
+ _setThrew(1, 0);
+ }
+}
+
+function invoke_iii(index,a1,a2) {
+ var sp = stackSave();
+ try {
+ return wasmTable.get(index)(a1,a2);
+ } catch(e) {
+ stackRestore(sp);
+ if (e !== e+0 && e !== 'longjmp') throw e;
+ _setThrew(1, 0);
+ }
+}
+
+function invoke_vi(index,a1) {
+ var sp = stackSave();
+ try {
+ wasmTable.get(index)(a1);
+ } catch(e) {
+ stackRestore(sp);
+ if (e !== e+0 && e !== 'longjmp') throw e;
+ _setThrew(1, 0);
+ }
+}
+
+
+
+
+// === Auto-generated postamble setup entry stuff ===
+
+Module["intArrayFromString"] = intArrayFromString;
+if (!Object.getOwnPropertyDescriptor(Module, "intArrayToString")) Module["intArrayToString"] = function() { abort("'intArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ccall")) Module["ccall"] = function() { abort("'ccall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+Module["cwrap"] = cwrap;
+if (!Object.getOwnPropertyDescriptor(Module, "setValue")) Module["setValue"] = function() { abort("'setValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getValue")) Module["getValue"] = function() { abort("'getValue' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+Module["allocate"] = allocate;
+if (!Object.getOwnPropertyDescriptor(Module, "UTF8ArrayToString")) Module["UTF8ArrayToString"] = function() { abort("'UTF8ArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+Module["UTF8ToString"] = UTF8ToString;
+if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF8Array")) Module["stringToUTF8Array"] = function() { abort("'stringToUTF8Array' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF8")) Module["stringToUTF8"] = function() { abort("'stringToUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF8")) Module["lengthBytesUTF8"] = function() { abort("'lengthBytesUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stackTrace")) Module["stackTrace"] = function() { abort("'stackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addOnPreRun")) Module["addOnPreRun"] = function() { abort("'addOnPreRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addOnInit")) Module["addOnInit"] = function() { abort("'addOnInit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addOnPreMain")) Module["addOnPreMain"] = function() { abort("'addOnPreMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addOnExit")) Module["addOnExit"] = function() { abort("'addOnExit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addOnPostRun")) Module["addOnPostRun"] = function() { abort("'addOnPostRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeStringToMemory")) Module["writeStringToMemory"] = function() { abort("'writeStringToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeArrayToMemory")) Module["writeArrayToMemory"] = function() { abort("'writeArrayToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeAsciiToMemory")) Module["writeAsciiToMemory"] = function() { abort("'writeAsciiToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addRunDependency")) Module["addRunDependency"] = function() { abort("'addRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "removeRunDependency")) Module["removeRunDependency"] = function() { abort("'removeRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createFolder")) Module["FS_createFolder"] = function() { abort("'FS_createFolder' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createPath")) Module["FS_createPath"] = function() { abort("'FS_createPath' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createDataFile")) Module["FS_createDataFile"] = function() { abort("'FS_createDataFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createPreloadedFile")) Module["FS_createPreloadedFile"] = function() { abort("'FS_createPreloadedFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createLazyFile")) Module["FS_createLazyFile"] = function() { abort("'FS_createLazyFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createLink")) Module["FS_createLink"] = function() { abort("'FS_createLink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_createDevice")) Module["FS_createDevice"] = function() { abort("'FS_createDevice' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS_unlink")) Module["FS_unlink"] = function() { abort("'FS_unlink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you") };
+if (!Object.getOwnPropertyDescriptor(Module, "getLEB")) Module["getLEB"] = function() { abort("'getLEB' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getFunctionTables")) Module["getFunctionTables"] = function() { abort("'getFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "alignFunctionTables")) Module["alignFunctionTables"] = function() { abort("'alignFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "registerFunctions")) Module["registerFunctions"] = function() { abort("'registerFunctions' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "addFunction")) Module["addFunction"] = function() { abort("'addFunction' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "removeFunction")) Module["removeFunction"] = function() { abort("'removeFunction' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getFuncWrapper")) Module["getFuncWrapper"] = function() { abort("'getFuncWrapper' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "prettyPrint")) Module["prettyPrint"] = function() { abort("'prettyPrint' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "makeBigInt")) Module["makeBigInt"] = function() { abort("'makeBigInt' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "dynCall")) Module["dynCall"] = function() { abort("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getCompilerSetting")) Module["getCompilerSetting"] = function() { abort("'getCompilerSetting' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "print")) Module["print"] = function() { abort("'print' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "printErr")) Module["printErr"] = function() { abort("'printErr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getTempRet0")) Module["getTempRet0"] = function() { abort("'getTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "setTempRet0")) Module["setTempRet0"] = function() { abort("'setTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "callMain")) Module["callMain"] = function() { abort("'callMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "abort")) Module["abort"] = function() { abort("'abort' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stringToNewUTF8")) Module["stringToNewUTF8"] = function() { abort("'stringToNewUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "abortOnCannotGrowMemory")) Module["abortOnCannotGrowMemory"] = function() { abort("'abortOnCannotGrowMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "emscripten_realloc_buffer")) Module["emscripten_realloc_buffer"] = function() { abort("'emscripten_realloc_buffer' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ENV")) Module["ENV"] = function() { abort("'ENV' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_CODES")) Module["ERRNO_CODES"] = function() { abort("'ERRNO_CODES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ERRNO_MESSAGES")) Module["ERRNO_MESSAGES"] = function() { abort("'ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "setErrNo")) Module["setErrNo"] = function() { abort("'setErrNo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "DNS")) Module["DNS"] = function() { abort("'DNS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getHostByName")) Module["getHostByName"] = function() { abort("'getHostByName' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GAI_ERRNO_MESSAGES")) Module["GAI_ERRNO_MESSAGES"] = function() { abort("'GAI_ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "Protocols")) Module["Protocols"] = function() { abort("'Protocols' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "Sockets")) Module["Sockets"] = function() { abort("'Sockets' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getRandomDevice")) Module["getRandomDevice"] = function() { abort("'getRandomDevice' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "traverseStack")) Module["traverseStack"] = function() { abort("'traverseStack' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "UNWIND_CACHE")) Module["UNWIND_CACHE"] = function() { abort("'UNWIND_CACHE' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "withBuiltinMalloc")) Module["withBuiltinMalloc"] = function() { abort("'withBuiltinMalloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "readAsmConstArgsArray")) Module["readAsmConstArgsArray"] = function() { abort("'readAsmConstArgsArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "readAsmConstArgs")) Module["readAsmConstArgs"] = function() { abort("'readAsmConstArgs' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "mainThreadEM_ASM")) Module["mainThreadEM_ASM"] = function() { abort("'mainThreadEM_ASM' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "jstoi_q")) Module["jstoi_q"] = function() { abort("'jstoi_q' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "jstoi_s")) Module["jstoi_s"] = function() { abort("'jstoi_s' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getExecutableName")) Module["getExecutableName"] = function() { abort("'getExecutableName' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "listenOnce")) Module["listenOnce"] = function() { abort("'listenOnce' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "autoResumeAudioContext")) Module["autoResumeAudioContext"] = function() { abort("'autoResumeAudioContext' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "dynCallLegacy")) Module["dynCallLegacy"] = function() { abort("'dynCallLegacy' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getDynCaller")) Module["getDynCaller"] = function() { abort("'getDynCaller' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "dynCall")) Module["dynCall"] = function() { abort("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "callRuntimeCallbacks")) Module["callRuntimeCallbacks"] = function() { abort("'callRuntimeCallbacks' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "abortStackOverflow")) Module["abortStackOverflow"] = function() { abort("'abortStackOverflow' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "reallyNegative")) Module["reallyNegative"] = function() { abort("'reallyNegative' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "unSign")) Module["unSign"] = function() { abort("'unSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "reSign")) Module["reSign"] = function() { abort("'reSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "formatString")) Module["formatString"] = function() { abort("'formatString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "PATH")) Module["PATH"] = function() { abort("'PATH' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "PATH_FS")) Module["PATH_FS"] = function() { abort("'PATH_FS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SYSCALLS")) Module["SYSCALLS"] = function() { abort("'SYSCALLS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "syscallMmap2")) Module["syscallMmap2"] = function() { abort("'syscallMmap2' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "syscallMunmap")) Module["syscallMunmap"] = function() { abort("'syscallMunmap' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "flush_NO_FILESYSTEM")) Module["flush_NO_FILESYSTEM"] = function() { abort("'flush_NO_FILESYSTEM' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "JSEvents")) Module["JSEvents"] = function() { abort("'JSEvents' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "specialHTMLTargets")) Module["specialHTMLTargets"] = function() { abort("'specialHTMLTargets' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "maybeCStringToJsString")) Module["maybeCStringToJsString"] = function() { abort("'maybeCStringToJsString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "findEventTarget")) Module["findEventTarget"] = function() { abort("'findEventTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "findCanvasEventTarget")) Module["findCanvasEventTarget"] = function() { abort("'findCanvasEventTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "polyfillSetImmediate")) Module["polyfillSetImmediate"] = function() { abort("'polyfillSetImmediate' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "demangle")) Module["demangle"] = function() { abort("'demangle' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "demangleAll")) Module["demangleAll"] = function() { abort("'demangleAll' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "jsStackTrace")) Module["jsStackTrace"] = function() { abort("'jsStackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stackTrace")) Module["stackTrace"] = function() { abort("'stackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getEnvStrings")) Module["getEnvStrings"] = function() { abort("'getEnvStrings' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "checkWasiClock")) Module["checkWasiClock"] = function() { abort("'checkWasiClock' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeI53ToI64")) Module["writeI53ToI64"] = function() { abort("'writeI53ToI64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeI53ToI64Clamped")) Module["writeI53ToI64Clamped"] = function() { abort("'writeI53ToI64Clamped' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeI53ToI64Signaling")) Module["writeI53ToI64Signaling"] = function() { abort("'writeI53ToI64Signaling' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeI53ToU64Clamped")) Module["writeI53ToU64Clamped"] = function() { abort("'writeI53ToU64Clamped' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeI53ToU64Signaling")) Module["writeI53ToU64Signaling"] = function() { abort("'writeI53ToU64Signaling' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "readI53FromI64")) Module["readI53FromI64"] = function() { abort("'readI53FromI64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "readI53FromU64")) Module["readI53FromU64"] = function() { abort("'readI53FromU64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "convertI32PairToI53")) Module["convertI32PairToI53"] = function() { abort("'convertI32PairToI53' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "convertU32PairToI53")) Module["convertU32PairToI53"] = function() { abort("'convertU32PairToI53' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "exceptionLast")) Module["exceptionLast"] = function() { abort("'exceptionLast' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "exceptionCaught")) Module["exceptionCaught"] = function() { abort("'exceptionCaught' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ExceptionInfoAttrs")) Module["ExceptionInfoAttrs"] = function() { abort("'ExceptionInfoAttrs' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "ExceptionInfo")) Module["ExceptionInfo"] = function() { abort("'ExceptionInfo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "CatchInfo")) Module["CatchInfo"] = function() { abort("'CatchInfo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "exception_addRef")) Module["exception_addRef"] = function() { abort("'exception_addRef' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "exception_decRef")) Module["exception_decRef"] = function() { abort("'exception_decRef' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "Browser")) Module["Browser"] = function() { abort("'Browser' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "funcWrappers")) Module["funcWrappers"] = function() { abort("'funcWrappers' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "getFuncWrapper")) Module["getFuncWrapper"] = function() { abort("'getFuncWrapper' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "setMainLoop")) Module["setMainLoop"] = function() { abort("'setMainLoop' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "FS")) Module["FS"] = function() { abort("'FS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "mmapAlloc")) Module["mmapAlloc"] = function() { abort("'mmapAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "MEMFS")) Module["MEMFS"] = function() { abort("'MEMFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "TTY")) Module["TTY"] = function() { abort("'TTY' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "PIPEFS")) Module["PIPEFS"] = function() { abort("'PIPEFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SOCKFS")) Module["SOCKFS"] = function() { abort("'SOCKFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "tempFixedLengthArray")) Module["tempFixedLengthArray"] = function() { abort("'tempFixedLengthArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "miniTempWebGLFloatBuffers")) Module["miniTempWebGLFloatBuffers"] = function() { abort("'miniTempWebGLFloatBuffers' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "heapObjectForWebGLType")) Module["heapObjectForWebGLType"] = function() { abort("'heapObjectForWebGLType' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "heapAccessShiftForWebGLHeap")) Module["heapAccessShiftForWebGLHeap"] = function() { abort("'heapAccessShiftForWebGLHeap' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GL")) Module["GL"] = function() { abort("'GL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGet")) Module["emscriptenWebGLGet"] = function() { abort("'emscriptenWebGLGet' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "computeUnpackAlignedImageSize")) Module["computeUnpackAlignedImageSize"] = function() { abort("'computeUnpackAlignedImageSize' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetTexPixelData")) Module["emscriptenWebGLGetTexPixelData"] = function() { abort("'emscriptenWebGLGetTexPixelData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetUniform")) Module["emscriptenWebGLGetUniform"] = function() { abort("'emscriptenWebGLGetUniform' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "emscriptenWebGLGetVertexAttrib")) Module["emscriptenWebGLGetVertexAttrib"] = function() { abort("'emscriptenWebGLGetVertexAttrib' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "writeGLArray")) Module["writeGLArray"] = function() { abort("'writeGLArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "AL")) Module["AL"] = function() { abort("'AL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SDL_unicode")) Module["SDL_unicode"] = function() { abort("'SDL_unicode' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SDL_ttfContext")) Module["SDL_ttfContext"] = function() { abort("'SDL_ttfContext' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SDL_audio")) Module["SDL_audio"] = function() { abort("'SDL_audio' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SDL")) Module["SDL"] = function() { abort("'SDL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "SDL_gfx")) Module["SDL_gfx"] = function() { abort("'SDL_gfx' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GLUT")) Module["GLUT"] = function() { abort("'GLUT' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "EGL")) Module["EGL"] = function() { abort("'EGL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GLFW_Window")) Module["GLFW_Window"] = function() { abort("'GLFW_Window' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GLFW")) Module["GLFW"] = function() { abort("'GLFW' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "GLEW")) Module["GLEW"] = function() { abort("'GLEW' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "IDBStore")) Module["IDBStore"] = function() { abort("'IDBStore' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "runAndAbortIfError")) Module["runAndAbortIfError"] = function() { abort("'runAndAbortIfError' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "warnOnce")) Module["warnOnce"] = function() { abort("'warnOnce' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stackSave")) Module["stackSave"] = function() { abort("'stackSave' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stackRestore")) Module["stackRestore"] = function() { abort("'stackRestore' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stackAlloc")) Module["stackAlloc"] = function() { abort("'stackAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "AsciiToString")) Module["AsciiToString"] = function() { abort("'AsciiToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stringToAscii")) Module["stringToAscii"] = function() { abort("'stringToAscii' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "UTF16ToString")) Module["UTF16ToString"] = function() { abort("'UTF16ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF16")) Module["stringToUTF16"] = function() { abort("'stringToUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF16")) Module["lengthBytesUTF16"] = function() { abort("'lengthBytesUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "UTF32ToString")) Module["UTF32ToString"] = function() { abort("'UTF32ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "stringToUTF32")) Module["stringToUTF32"] = function() { abort("'stringToUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "lengthBytesUTF32")) Module["lengthBytesUTF32"] = function() { abort("'lengthBytesUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "allocateUTF8")) Module["allocateUTF8"] = function() { abort("'allocateUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+if (!Object.getOwnPropertyDescriptor(Module, "allocateUTF8OnStack")) Module["allocateUTF8OnStack"] = function() { abort("'allocateUTF8OnStack' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") };
+Module["writeStackCookie"] = writeStackCookie;
+Module["checkStackCookie"] = checkStackCookie;if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_NORMAL")) Object.defineProperty(Module, "ALLOC_NORMAL", { configurable: true, get: function() { abort("'ALLOC_NORMAL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } });
+if (!Object.getOwnPropertyDescriptor(Module, "ALLOC_STACK")) Object.defineProperty(Module, "ALLOC_STACK", { configurable: true, get: function() { abort("'ALLOC_STACK' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)") } });
+
+
+var calledRun;
+
+/**
+ * @constructor
+ * @this {ExitStatus}
+ */
+function ExitStatus(status) {
+ this.name = "ExitStatus";
+ this.message = "Program terminated with exit(" + status + ")";
+ this.status = status;
+}
+
+var calledMain = false;
+
+
+dependenciesFulfilled = function runCaller() {
+ // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false)
+ if (!calledRun) run();
+ if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled
+};
+
+
+
+
+
+/** @type {function(Array=)} */
+function run(args) {
+ args = args || arguments_;
+
+ if (runDependencies > 0) {
+ return;
+ }
+
+ writeStackCookie();
+
+ preRun();
+
+ if (runDependencies > 0) return; // a preRun added a dependency, run will be called later
+
+ function doRun() {
+ // run may have just been called through dependencies being fulfilled just in this very frame,
+ // or while the async setStatus time below was happening
+ if (calledRun) return;
+ calledRun = true;
+ Module['calledRun'] = true;
+
+ if (ABORT) return;
+
+ initRuntime();
+
+ preMain();
+
+ if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();
+
+ assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]');
+
+ postRun();
+ }
+
+ if (Module['setStatus']) {
+ Module['setStatus']('Running...');
+ setTimeout(function() {
+ setTimeout(function() {
+ Module['setStatus']('');
+ }, 1);
+ doRun();
+ }, 1);
+ } else
+ {
+ doRun();
+ }
+ if (!ABORT) checkStackCookie();
+}
+Module['run'] = run;
+
+function checkUnflushedContent() {
+ // Compiler settings do not allow exiting the runtime, so flushing
+ // the streams is not possible. but in ASSERTIONS mode we check
+ // if there was something to flush, and if so tell the user they
+ // should request that the runtime be exitable.
+ // Normally we would not even include flush() at all, but in ASSERTIONS
+ // builds we do so just for this check, and here we see if there is any
+ // content to flush, that is, we check if there would have been
+ // something a non-ASSERTIONS build would have not seen.
+ // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0
+ // mode (which has its own special function for this; otherwise, all
+ // the code is inside libc)
+ var print = out;
+ var printErr = err;
+ var has = false;
+ out = err = function(x) {
+ has = true;
+ }
+ try { // it doesn't matter if it fails
+ var flush = flush_NO_FILESYSTEM;
+ if (flush) flush();
+ } catch(e) {}
+ out = print;
+ err = printErr;
+ if (has) {
+ warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.');
+ warnOnce('(this may also be due to not including full filesystem support - try building with -s FORCE_FILESYSTEM=1)');
+ }
+}
+
+/** @param {boolean|number=} implicit */
+function exit(status, implicit) {
+ checkUnflushedContent();
+
+ // if this is just main exit-ing implicitly, and the status is 0, then we
+ // don't need to do anything here and can just leave. if the status is
+ // non-zero, though, then we need to report it.
+ // (we may have warned about this earlier, if a situation justifies doing so)
+ if (implicit && noExitRuntime && status === 0) {
+ return;
+ }
+
+ if (noExitRuntime) {
+ // if exit() was called, we may warn the user if the runtime isn't actually being shut down
+ if (!implicit) {
+ var msg = 'program exited (with status: ' + status + '), but EXIT_RUNTIME is not set, so halting execution but not exiting the runtime or preventing further async execution (build with EXIT_RUNTIME=1, if you want a true shutdown)';
+ err(msg);
+ }
+ } else {
+
+ EXITSTATUS = status;
+
+ exitRuntime();
+
+ if (Module['onExit']) Module['onExit'](status);
+
+ ABORT = true;
+ }
+
+ quit_(status, new ExitStatus(status));
+}
+
+if (Module['preInit']) {
+ if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']];
+ while (Module['preInit'].length > 0) {
+ Module['preInit'].pop()();
+ }
+}
+
+
+ noExitRuntime = true;
+
+run();
+
+
+
+
+
+
+// {{MODULE_ADDITIONS}}
+
+
+
diff --git a/demos/simulator/ulisp.wasm b/demos/simulator/ulisp.wasm
new file mode 100644
index 00000000000..67e4eff798c
Binary files /dev/null and b/demos/simulator/ulisp.wasm differ
diff --git a/generators/lisp.js b/generators/lisp.js
new file mode 100644
index 00000000000..6a29727e47f
--- /dev/null
+++ b/generators/lisp.js
@@ -0,0 +1,294 @@
+/**
+ * @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');
+
+// Indent with 2 spaces
+Blockly.Lisp.INDENT = ' ';
+
+/**
+ * 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) {
+ // 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;
+};
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..2caa48cd5ba
--- /dev/null
+++ b/generators/lisp/app_code.js
@@ -0,0 +1,101 @@
+/// Code Generator Functions for App Blocks
+
+Blockly.Lisp['on_start'] = function(block) {
+ // Run this code at startup. Inspired by MakeCode "on_start" and Arduino "setup".
+ var statements_stmts = Blockly.Lisp.statementToCode(block, 'STMTS');
+ var code = statements_stmts;
+ code = [
+ '( list ',
+ code + ')',
+ ].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/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/lisp_blocks.js b/generators/lisp/lisp_blocks.js
new file mode 100644
index 00000000000..edf26314afb
--- /dev/null
+++ b/generators/lisp/lisp_blocks.js
@@ -0,0 +1,288 @@
+/// Custom blocks exported from Block Exporter based on lisp_library.xml.
+/// See lisp_functions.js for Code Generator Functions.
+var lisp_blocks =
+// Begin Block Exporter
+[{
+ "type": "digital_toggle_pin",
+ "message0": "digital toggle pin %1",
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "PIN",
+ "options": [
+ [
+ "11",
+ "11"
+ ],
+ [
+ "12",
+ "12"
+ ],
+ [
+ "13",
+ "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": [
+ [
+ "11",
+ "11"
+ ],
+ [
+ "12",
+ "12"
+ ],
+ [
+ "13",
+ "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": [
+ [
+ "11",
+ "11"
+ ],
+ [
+ "12",
+ "12"
+ ],
+ [
+ "13",
+ "13"
+ ]
+ ]
+ },
+ {
+ "type": "field_dropdown",
+ "name": "VALUE",
+ "options": [
+ [
+ "LOW",
+ ":low"
+ ],
+ [
+ "HIGH",
+ ":high"
+ ]
+ ]
+ }
+ ],
+ "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/lisp_coap.js b/generators/lisp/lisp_coap.js
new file mode 100644
index 00000000000..1f69b9220a7
--- /dev/null
+++ b/generators/lisp/lisp_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/lisp_functions.js b/generators/lisp/lisp_functions.js
new file mode 100644
index 00000000000..53abdf5535f
--- /dev/null
+++ b/generators/lisp/lisp_functions.js
@@ -0,0 +1,89 @@
+/// Code Generator Functions for Custom Blocks in lisp_blocks.js.
+/// Initially exported by Block Exporter from lisp_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) {
+ // Run this code at forever in a loop. Inspired by MakeCode "forever" and Arduino "loop".
+ var statements_stmts = Blockly.Lisp.statementToCode(block, 'STMTS');
+ var code = statements_stmts;
+ code = [
+ '( loop ',
+ code + ')',
+ ].join('\n');
+ return code;
+};
+
+Blockly.Lisp['wait'] = function(block) {
+ var number_duration = block.getFieldValue('DURATION');
+ var code = [
+ '( delay ' + (number_duration * 1000) + ' )',
+ ''
+ ].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 = [
+ '( pinmode ' + dropdown_pin + ' :output )',
+ '( digitalwrite ' + dropdown_pin + ' ' + dropdown_value + ' )',
+ ''
+ ].join('\n');
+ return code;
+};
\ No newline at end of file
diff --git a/generators/lisp/lisp_library.xml b/generators/lisp/lisp_library.xml
new file mode 100644
index 00000000000..7584b9c7635
--- /dev/null
+++ b/generators/lisp/lisp_library.xml
@@ -0,0 +1,605 @@
+
+
+ digital_toggle_pin
+ INT
+ BOTH
+
+
+ LEFT
+
+
+ digital toggle pin
+
+
+
+ PIN
+ 11
+ 11
+ 12
+ 12
+ 13
+ 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
+ 11
+ 11
+ 12
+ 12
+ 13
+ 13
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 330
+
+
+
+
+ digital_write_pin
+ AUTO
+ BOTH
+
+
+ LEFT
+
+
+ digital write pin
+
+
+
+ PIN
+ 11
+ 11
+ 12
+ 12
+ 13
+ 13
+
+
+ to
+
+
+
+ VALUE
+ LOW
+ :low
+ HIGH
+ :high
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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/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'];
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;
+};