Browse Source

GLSL/JSON live editing via Codemirror.

tschw 10 years ago
parent
commit
a405949681
5 changed files with 1000 additions and 187 deletions
  1. 4 0
      editor/index.html
  2. 139 29
      editor/js/Script.js
  3. 156 158
      editor/js/Sidebar.Material.js
  4. 245 0
      editor/js/libs/codemirror/mode/glsl.js
  5. 456 0
      editor/js/libs/jsonlint.js

+ 4 - 0
editor/index.html

@@ -40,8 +40,12 @@
 		<link rel="stylesheet" href="js/libs/codemirror/codemirror.css">
 		<link rel="stylesheet" href="js/libs/codemirror/theme/monokai.css">
 		<script src="js/libs/codemirror/codemirror.js"></script>
+
 		<script src="js/libs/codemirror/mode/javascript.js"></script>
 		<script src="js/libs/esprima.js"></script>
+		<script src="js/libs/jsonlint.js"></script>
+
+		<script src="js/libs/codemirror/mode/glsl.js"></script>
 
 		<script src="js/libs/jszip.min.js"></script>
 		<script src="js/libs/sortable.min.js"></script>

+ 139 - 29
editor/js/Script.js

@@ -43,7 +43,9 @@ var Script = function ( editor ) {
 	header.add( close );
 
 	var delay;
+	var currentMode;
 	var currentScript;
+	var currentObject;
 
 	var codemirror = CodeMirror( container.dom, {
 		value: '',
@@ -61,14 +63,40 @@ var Script = function ( editor ) {
 
 			var value = codemirror.getValue();
 
-			if ( validate( value ) ) {
+			if ( ! validate( value ) ) return;
+
+			if ( typeof( currentScript ) === 'object' ) {
 
 				currentScript.source = value;
 				signals.scriptChanged.dispatch( currentScript );
+				return;
+			}
+
+			switch ( currentScript ) {
+
+				case 'vertexShader':
+
+					currentObject.vertexShader = value;
+					break;
+
+				case 'fragmentShader':
+
+					currentObject.fragmentShader = value;
+					break;
+
+				case 'programInfo':
+
+					var json = JSON.parse( value );
+					currentObject.defines = json.defines;
+					currentObject.uniforms = json.uniforms;
+					currentObject.attributes = json.attributes;
 
 			}
 
-		}, 300 );
+			currentObject.needsUpdate = true;
+			signals.materialChanged.dispatch( currentObject );
+
+		}, 200 );
 
 	});
 
@@ -79,7 +107,7 @@ var Script = function ( editor ) {
 
 	var validate = function ( string ) {
 
-		var syntax, errors;
+		var errors;
 
 		return codemirror.operation( function () {
 
@@ -97,48 +125,84 @@ var Script = function ( editor ) {
 
 			//
 
-			try {
+			switch ( currentMode ) {
+
+				case 'javascript':
+
+					try {
+
+						var syntax = esprima.parse( string, { tolerant: true } );
+						errors = syntax.errors;
+
+					} catch ( error ) {
+
+						errors = [
+
+							{ lineNumber: error.lineNumber,message: error.message }
+						];
+
+					}
+
+					for ( var i = 0; i < errors.length; i ++ ) {
+
+						var error = errors[ i ];
+						error.message = error.message.replace(/Line [0-9]+: /, '');
+
+					}
+
+					break;
+
+				case 'json':
+
+					errors = [];
+
+					jsonlint.parseError = function ( message, info ) {
 
-				syntax = esprima.parse( string, { tolerant: true } );
-				errors = syntax.errors;
+						message = message.split('\n')[3];
 
-				for ( var i = 0; i < errors.length; i ++ ) {
+						errors.push({
+							lineNumber: info.loc.first_line,
+							message: message 
+						});
 
-					var error = errors[ i ];
+					};
 
-					var message = document.createElement( 'div' );
-					message.className = 'esprima-error';
-					message.textContent = error.message.replace(/Line [0-9]+: /, '');
+					try {
 
-					var lineNumber = error.lineNumber - 1;
-					errorLines.push( lineNumber );
+						jsonlint.parse( string );
 
-					codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
+					} catch ( error ) {
 
-					var widget = codemirror.addLineWidget(
-						lineNumber,
-						message
-					);
+						// ignore failed error recovery
 
-					widgets.push( widget );
+					}
 
-				}
+					break;
 
-			} catch ( error ) {
+				case 'glsl':
+
+					// TODO validate GLSL (compiling shader?)
+
+				default:
+
+					errors = [];
+
+			}
+
+			for ( var i = 0; i < errors.length; i ++ ) {
+
+				var error = errors[ i ];
 
 				var message = document.createElement( 'div' );
 				message.className = 'esprima-error';
-				message.textContent = error.message.replace(/Line [0-9]+: /, '');
+				message.textContent = error.message;
 
 				var lineNumber = error.lineNumber - 1;
 				errorLines.push( lineNumber );
 
 				codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
 
-				var widget = codemirror.addLineWidget(
-					lineNumber,
-					message
-				);
+				var widget = codemirror.addLineWidget( lineNumber, message );
 
 				widgets.push( widget );
 
@@ -160,12 +224,58 @@ var Script = function ( editor ) {
 
 	signals.editScript.add( function ( object, script ) {
 
-		container.setDisplay( '' );
+		var mode, name, source;
+
+		if ( typeof( script ) === 'object' ) {
+
+			mode = 'javascript';
+			name = script.name;
+			source = script.source;
+
+		} else {
+
+			switch ( script ) {
 
+				case 'vertexShader':
+
+					mode = 'glsl';
+					name = 'Vertex Shader';
+					source = object.vertexShader || "";
+
+					break;
+
+				case 'fragmentShader':
+
+					mode = 'glsl';
+					name = 'Fragment Shader';
+					source = object.fragmentShader || "";
+
+					break;
+
+				case 'programInfo':
+
+					mode = 'json';
+					name = 'Program Properties';
+					var json = {
+						defines: object.defines,
+						uniforms: object.uniforms,
+						attributes: object.attributes
+					};
+					source = JSON.stringify( json, null, '\t' );
+
+			}
+
+		}
+
+		currentMode = mode;
 		currentScript = script;
+		currentObject = object;
 
-		title.setValue( object.name + ' / ' + script.name );
-		codemirror.setValue( script.source );
+		title.setValue( object.name + ' / ' + name );
+		container.setDisplay( '' );
+		codemirror.setValue( source );
+		if (mode === 'json' ) mode = { name: 'javascript', json: true };
+		codemirror.setOption( 'mode', mode );
 
 	} );
 

+ 156 - 158
editor/js/Sidebar.Material.js

@@ -5,6 +5,7 @@
 Sidebar.Material = function ( editor ) {
 
 	var signals = editor.signals;
+	var currentObject;
 
 	var container = new UI.CollapsiblePanel();
 	container.setCollapsed( editor.config.getKey( 'ui/sidebar/material/collapsed' ) );
@@ -112,39 +113,6 @@ Sidebar.Material = function ( editor ) {
 
 	container.add( materialShininessRow );
 
-	// uniforms
-
-	var materialUniformsRow = new UI.Panel();
-	var materialUniforms = new UI.TextArea().setWidth( '150px' ).setHeight( '80px' );
-	materialUniforms.setValue( '{\n  "uColor": {\n    "type": "3f",\n    "value": [1, 0, 0]\n  }\n}' ).onChange( update );
-
-	materialUniformsRow.add( new UI.Text( 'Uniforms' ).setWidth( '90px' ) );
-	materialUniformsRow.add( materialUniforms );
-
-	container.add( materialUniformsRow );
-
-	// vertex shader
-
-	var materialVertexShaderRow = new UI.Panel();
-	var materialVertexShader = new UI.TextArea().setWidth( '150px' ).setHeight( '80px' );
-	materialVertexShader.setValue( 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}' ).onChange( update );
-
-	materialVertexShaderRow.add( new UI.Text( 'Vertex Shader' ).setWidth( '90px' ) );
-	materialVertexShaderRow.add( materialVertexShader );
-
-	container.add( materialVertexShaderRow );
-
-	// fragment shader
-
-	var materialFragmentShaderRow = new UI.Panel();
-	var materialFragmentShader = new UI.TextArea().setWidth( '150px' ).setHeight( '80px' );
-	materialFragmentShader.setValue( 'uniform vec3 uColor;\n\nvoid main() {\n\tgl_FragColor = vec4( uColor, 1.0 );\n}' ).onChange( update );
-
-	materialFragmentShaderRow.add( new UI.Text( 'Fragment Shader' ).setWidth( '90px' ) );
-	materialFragmentShaderRow.add( materialFragmentShader );
-
-	container.add( materialFragmentShaderRow );
-
 	// vertex colors
 
 	var materialVertexColorsRow = new UI.Panel();
@@ -356,11 +324,60 @@ Sidebar.Material = function ( editor ) {
 
 	container.add( materialWireframeRow );
 
+
+	// program info (defines, uniforms, attributes)
+
+	var materialProgramInfoRow = new UI.Panel();
+	var edit = new UI.Button( 'Edit' );
+	edit.setMarginLeft( '4px' );
+	edit.onClick( function () {
+
+		signals.editScript.dispatch( currentObject.material, 'programInfo' );
+
+	} );
+
+	materialProgramInfoRow.add( new UI.Text( 'Program Info' ).setWidth( '130px' ) );
+	materialProgramInfoRow.add( edit );
+
+	container.add( materialProgramInfoRow );
+
+	// vertex shader
+
+	var materialVertexShaderRow = new UI.Panel();
+	var edit = new UI.Button( 'Edit' );
+	edit.setMarginLeft( '4px' );
+	edit.onClick( function () {
+
+		signals.editScript.dispatch( currentObject.material, 'vertexShader' );
+
+	} );
+
+	materialVertexShaderRow.add( new UI.Text( 'Vertex Shader' ).setWidth( '130px' ) );
+	materialVertexShaderRow.add( edit );
+
+	container.add( materialVertexShaderRow );
+
+	// fragment shader
+
+	var materialFragmentShaderRow = new UI.Panel();
+	var edit = new UI.Button( 'Edit' );
+	edit.setMarginLeft( '4px' );
+	edit.onClick( function () {
+
+		signals.editScript.dispatch( currentObject.material, 'fragmentShader' );
+
+	} );
+
+	materialFragmentShaderRow.add( new UI.Text( 'Fragment Shader' ).setWidth( '130px' ) );
+	materialFragmentShaderRow.add( edit );
+
+	container.add( materialFragmentShaderRow );
+
 	//
 
 	function update() {
 
-		var object = editor.selected;
+		var object = currentObject;
 
 		var geometry = object.geometry;
 		var material = object.material;
@@ -383,7 +400,13 @@ Sidebar.Material = function ( editor ) {
 			if ( material instanceof THREE[ materialClass.getValue() ] === false ) {
 
 				material = new THREE[ materialClass.getValue() ]();
+
 				object.material = material;
+				// TODO Copy other references in the scene graph
+				// keeping name and UUID then.
+				// Also there should be means to create a unique
+				// copy for the current object explicitly and to
+				// attach the current material to other objects.
 
 			}
 
@@ -411,24 +434,6 @@ Sidebar.Material = function ( editor ) {
 
 			}
 
-			if ( material.uniforms !== undefined ) {
-
-				material.uniforms = JSON.parse( materialUniforms.getValue() );
-
-			}
-
-			if ( material.vertexShader !== undefined ) {
-
-				material.vertexShader = materialVertexShader.getValue();
-
-			}
-
-			if ( material.fragmentShader !== undefined ) {
-
-				material.fragmentShader = materialFragmentShader.getValue();
-
-			}
-
 			if ( material.vertexColors !== undefined ) {
 
 				var vertexColors = parseInt( materialVertexColors.getValue() );
@@ -585,6 +590,7 @@ Sidebar.Material = function ( editor ) {
 				}
 
 			}
+
 			if ( material.side !== undefined ) {
 
 				material.side = parseInt( materialSide.getValue() );
@@ -627,7 +633,7 @@ Sidebar.Material = function ( editor ) {
 
 			}
 
-			updateRows();
+			refreshUi();
 
 			signals.materialChanged.dispatch( material );
 
@@ -641,7 +647,9 @@ Sidebar.Material = function ( editor ) {
 
 	};
 
-	function updateRows() {
+	//
+
+	function setRowVisibility() {
 
 		var properties = {
 			'name': materialNameRow,
@@ -649,7 +657,7 @@ Sidebar.Material = function ( editor ) {
 			'emissive': materialEmissiveRow,
 			'specular': materialSpecularRow,
 			'shininess': materialShininessRow,
-			'uniforms': materialUniformsRow,
+			'uniforms': materialProgramInfoRow,
 			'vertexShader': materialVertexShaderRow,
 			'fragmentShader': materialFragmentShaderRow,
 			'vertexColors': materialVertexColorsRow,
@@ -670,7 +678,7 @@ Sidebar.Material = function ( editor ) {
 			'wireframe': materialWireframeRow
 		};
 
-		var material = editor.selected.material;
+		var material = currentObject.material;
 
 		for ( var property in properties ) {
 
@@ -680,189 +688,179 @@ Sidebar.Material = function ( editor ) {
 
 	};
 
-	// events
-
-	signals.objectSelected.add( function ( object ) {
-
-		if ( object && object.material ) {
 
-			container.setDisplay( '' );
+	function refreshUi() {
 
-			var material = object.material;
+		var material = currentObject.material;
 
-			if ( material.uuid !== undefined ) {
+		if ( material.uuid !== undefined ) {
 
-				materialUUID.setValue( material.uuid );
+			materialUUID.setValue( material.uuid );
 
-			}
-
-			if ( material.name !== undefined ) {
-
-				materialName.setValue( material.name );
-
-			}
-
-			materialClass.setValue( material.type );
+		}
 
-			if ( material.color !== undefined ) {
+		if ( material.name !== undefined ) {
 
-				materialColor.setHexValue( material.color.getHexString() );
+			materialName.setValue( material.name );
 
-			}
+		}
 
-			if ( material.emissive !== undefined ) {
+		materialClass.setValue( material.type );
 
-				materialEmissive.setHexValue( material.emissive.getHexString() );
+		if ( material.color !== undefined ) {
 
-			}
+			materialColor.setHexValue( material.color.getHexString() );
 
-			if ( material.specular !== undefined ) {
+		}
 
-				materialSpecular.setHexValue( material.specular.getHexString() );
+		if ( material.emissive !== undefined ) {
 
-			}
+			materialEmissive.setHexValue( material.emissive.getHexString() );
 
-			if ( material.shininess !== undefined ) {
+		}
 
-				materialShininess.setValue( material.shininess );
+		if ( material.specular !== undefined ) {
 
-			}
+			materialSpecular.setHexValue( material.specular.getHexString() );
 
-			if ( material.uniforms !== undefined ) {
+		}
 
-				materialUniforms.setValue( JSON.stringify( material.uniforms, null, '  ' ) );
+		if ( material.shininess !== undefined ) {
 
-			}
+			materialShininess.setValue( material.shininess );
 
-			if ( material.vertexShader !== undefined ) {
+		}
 
-				materialVertexShader.setValue( material.vertexShader );
+		if ( material.vertexColors !== undefined ) {
 
-			}
+			materialVertexColors.setValue( material.vertexColors );
 
-			if ( material.fragmentShader !== undefined ) {
+		}
 
-				materialFragmentShader.setValue( material.fragmentShader );
+		if ( material.skinning !== undefined ) {
 
-			}
+			materialSkinning.setValue( material.skinning );
 
-			if ( material.vertexColors !== undefined ) {
+		}
 
-				materialVertexColors.setValue( material.vertexColors );
+		if ( material.map !== undefined ) {
 
-			}
+			materialMapEnabled.setValue( material.map !== null );
+			materialMap.setValue( material.map );
 
-			if ( material.skinning !== undefined ) {
+		}
 
-				materialSkinning.setValue( material.skinning );
+		if ( material.alphaMap !== undefined ) {
 
-			}
+			materialAlphaMapEnabled.setValue( material.alphaMap !== null );
+			materialAlphaMap.setValue( material.alphaMap );
 
-			if ( material.map !== undefined ) {
+		}
 
-				materialMapEnabled.setValue( material.map !== null );
-				materialMap.setValue( material.map );
+		if ( material.bumpMap !== undefined ) {
 
-			}
+			materialBumpMapEnabled.setValue( material.bumpMap !== null );
+			materialBumpMap.setValue( material.bumpMap );
+			materialBumpScale.setValue( material.bumpScale );
 
-			if ( material.alphaMap !== undefined ) {
+		}
 
-				materialAlphaMapEnabled.setValue( material.alphaMap !== null );
-				materialAlphaMap.setValue( material.alphaMap );
+		if ( material.normalMap !== undefined ) {
 
-			}
+			materialNormalMapEnabled.setValue( material.normalMap !== null );
+			materialNormalMap.setValue( material.normalMap );
 
-			if ( material.bumpMap !== undefined ) {
+		}
 
-				materialBumpMapEnabled.setValue( material.bumpMap !== null );
-				materialBumpMap.setValue( material.bumpMap );
-				materialBumpScale.setValue( material.bumpScale );
+		if ( material.specularMap !== undefined ) {
 
-			}
+			materialSpecularMapEnabled.setValue( material.specularMap !== null );
+			materialSpecularMap.setValue( material.specularMap );
 
-			if ( material.normalMap !== undefined ) {
+		}
 
-				materialNormalMapEnabled.setValue( material.normalMap !== null );
-				materialNormalMap.setValue( material.normalMap );
+		if ( material.envMap !== undefined ) {
 
-			}
+			materialEnvMapEnabled.setValue( material.envMap !== null );
+			materialEnvMap.setValue( material.envMap );
+			materialReflectivity.setValue( material.reflectivity );
 
-			if ( material.specularMap !== undefined ) {
+		}
 
-				materialSpecularMapEnabled.setValue( material.specularMap !== null );
-				materialSpecularMap.setValue( material.specularMap );
+		if ( material.lightMap !== undefined ) {
 
-			}
+			materialLightMapEnabled.setValue( material.lightMap !== null );
+			materialLightMap.setValue( material.lightMap );
 
-			if ( material.envMap !== undefined ) {
+		}
 
-				materialEnvMapEnabled.setValue( material.envMap !== null );
-				materialEnvMap.setValue( material.envMap );
-				materialReflectivity.setValue( material.reflectivity );
+		if ( material.aoMap !== undefined ) {
 
-			}
+			materialAOMapEnabled.setValue( material.aoMap !== null );
+			materialAOMap.setValue( material.aoMap );
+			materialAOScale.setValue( material.aoMapIntensity );
 
-			if ( material.lightMap !== undefined ) {
+		}
 
-				materialLightMapEnabled.setValue( material.lightMap !== null );
-				materialLightMap.setValue( material.lightMap );
+		if ( material.side !== undefined ) {
 
-			}
+			materialSide.setValue( material.side );
 
-			if ( material.aoMap !== undefined ) {
+		}
 
-				materialAOMapEnabled.setValue( material.aoMap !== null );
-				materialAOMap.setValue( material.aoMap );
-				materialAOScale.setValue( material.aoMapIntensity );
+		if ( material.shading !== undefined ) {
 
-			}
+			materialShading.setValue( material.shading );
 
-			if ( material.side !== undefined ) {
+		}
 
-				materialSide.setValue( material.side );
+		if ( material.blending !== undefined ) {
 
-			}
+			materialBlending.setValue( material.blending );
 
-			if ( material.shading !== undefined ) {
+		}
 
-				materialShading.setValue( material.shading );
+		if ( material.opacity !== undefined ) {
 
-			}
+			materialOpacity.setValue( material.opacity );
 
-			if ( material.blending !== undefined ) {
+		}
 
-				materialBlending.setValue( material.blending );
+		if ( material.transparent !== undefined ) {
 
-			}
+			materialTransparent.setValue( material.transparent );
 
-			if ( material.opacity !== undefined ) {
+		}
 
-				materialOpacity.setValue( material.opacity );
+		if ( material.wireframe !== undefined ) {
 
-			}
+			materialWireframe.setValue( material.wireframe );
 
-			if ( material.transparent !== undefined ) {
+		}
 
-				materialTransparent.setValue( material.transparent );
+		if ( material.wireframeLinewidth !== undefined ) {
 
-			}
+			materialWireframeLinewidth.setValue( material.wireframeLinewidth );
 
-			if ( material.wireframe !== undefined ) {
+		}
 
-				materialWireframe.setValue( material.wireframe );
+		setRowVisibility();
 
-			}
+	}
 
-			if ( material.wireframeLinewidth !== undefined ) {
+	// events
 
-				materialWireframeLinewidth.setValue( material.wireframeLinewidth );
+	signals.objectSelected.add( function ( object ) {
 
-			}
+		if ( object && object.material ) {
 
-			updateRows();
+			currentObject = object;
+			refreshUi();
+			container.setDisplay( '' );
 
 		} else {
 
+			currentObject = null;
 			container.setDisplay( 'none' );
 
 		}

+ 245 - 0
editor/js/libs/codemirror/mode/glsl.js

@@ -0,0 +1,245 @@
+// Full source: 
+//
+// 		https://github.com/hughsk/glsl-editor 
+//
+// (C) Copyright Hugh Kennedy
+//
+// This software is released under the MIT license:
+//
+// Permission is hereby granted, free of charge, to any person obtaining a 
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL-
+// INGS IN THE SOFTWARE.
+
+// The original source code has been slightly modified for the purpose of
+// integration (tschw).
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+  CodeMirror.defineMode("glsl", function(config, parserConfig) {
+    var indentUnit = config.indentUnit,
+        keywords = parserConfig.keywords || words(glslKeywords),
+        builtins = parserConfig.builtins || words(glslBuiltins),
+        blockKeywords = parserConfig.blockKeywords || words("case do else for if switch while struct"),
+        atoms = parserConfig.atoms || words("null"),
+        hooks = parserConfig.hooks || {},
+        multiLineStrings = parserConfig.multiLineStrings;
+    var isOperatorChar = /[+\-*&%=<>!?|\/]/;
+
+    var curPunc;
+
+    function tokenBase(stream, state) {
+      var ch = stream.next();
+      if (hooks[ch]) {
+        var result = hooks[ch](stream, state);
+        if (result !== false) return result;
+      }
+      if (ch == '"' || ch == "'") {
+        state.tokenize = tokenString(ch);
+        return state.tokenize(stream, state);
+      }
+      if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+        curPunc = ch;
+        return "bracket";
+      }
+      if (/\d/.test(ch)) {
+        stream.eatWhile(/[\w\.]/);
+        return "number";
+      }
+      if (ch == "/") {
+        if (stream.eat("*")) {
+          state.tokenize = tokenComment;
+          return tokenComment(stream, state);
+        }
+        if (stream.eat("/")) {
+          stream.skipToEnd();
+          return "comment";
+        }
+      }
+      if (ch == "#") {
+        stream.eatWhile(/[\S]+/);
+        stream.eatWhile(/[\s]+/);
+        stream.eatWhile(/[\S]+/);
+        stream.eatWhile(/[\s]+/);
+        return "comment";
+      }
+      if (isOperatorChar.test(ch)) {
+        stream.eatWhile(isOperatorChar);
+        return "operator";
+      }
+      stream.eatWhile(/[\w\$_]/);
+      var cur = stream.current();
+      if (keywords.propertyIsEnumerable(cur)) {
+        if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+        return "keyword";
+      }
+      if (builtins.propertyIsEnumerable(cur)) {
+        return "builtin";
+      }
+      if (atoms.propertyIsEnumerable(cur)) return "atom";
+      return "word";
+    }
+
+    function tokenString(quote) {
+      return function(stream, state) {
+        var escaped = false, next, end = false;
+        while ((next = stream.next()) != null) {
+          if (next == quote && !escaped) {end = true; break;}
+          escaped = !escaped && next == "\\";
+        }
+        if (end || !(escaped || multiLineStrings))
+          state.tokenize = tokenBase;
+        return "string";
+      };
+    }
+
+    function tokenComment(stream, state) {
+      var maybeEnd = false, ch;
+      while (ch = stream.next()) {
+        if (ch == "/" && maybeEnd) {
+          state.tokenize = tokenBase;
+          break;
+        }
+        maybeEnd = (ch == "*");
+      }
+      return "comment";
+    }
+
+    function Context(indented, column, type, align, prev) {
+      this.indented = indented;
+      this.column = column;
+      this.type = type;
+      this.align = align;
+      this.prev = prev;
+    }
+    function pushContext(state, col, type) {
+      return state.context = new Context(state.indented, col, type, null, state.context);
+    }
+    function popContext(state) {
+      var t = state.context.type;
+      if (t == ")" || t == "]" || t == "}")
+        state.indented = state.context.indented;
+      return state.context = state.context.prev;
+    }
+
+    // Interface
+
+    return {
+      startState: function(basecolumn) {
+        return {
+          tokenize: null,
+          context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
+          indented: 0,
+          startOfLine: true
+        };
+      },
+
+      token: function(stream, state) {
+        var ctx = state.context;
+        if (stream.sol()) {
+          if (ctx.align == null) ctx.align = false;
+          state.indented = stream.indentation();
+          state.startOfLine = true;
+        }
+        if (stream.eatSpace()) return null;
+        curPunc = null;
+        var style = (state.tokenize || tokenBase)(stream, state);
+        if (style == "comment" || style == "meta") return style;
+        if (ctx.align == null) ctx.align = true;
+
+        if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
+        else if (curPunc == "{") pushContext(state, stream.column(), "}");
+        else if (curPunc == "[") pushContext(state, stream.column(), "]");
+        else if (curPunc == "(") pushContext(state, stream.column(), ")");
+        else if (curPunc == "}") {
+          while (ctx.type == "statement") ctx = popContext(state);
+          if (ctx.type == "}") ctx = popContext(state);
+          while (ctx.type == "statement") ctx = popContext(state);
+        }
+        else if (curPunc == ctx.type) popContext(state);
+        else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
+          pushContext(state, stream.column(), "statement");
+        state.startOfLine = false;
+        return style;
+      },
+
+      indent: function(state, textAfter) {
+        if (state.tokenize != tokenBase && state.tokenize != null) return 0;
+        var firstChar = textAfter && textAfter.charAt(0), ctx = state.context, closing = firstChar == ctx.type;
+        if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
+        else if (ctx.align) return ctx.column + (closing ? 0 : 1);
+        else return ctx.indented + (closing ? 0 : indentUnit);
+      },
+
+      electricChars: "{}"
+    };
+  });
+
+  function words(str) {
+    var obj = {}, words = str.split(" ");
+    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+    return obj;
+  }
+  var glslKeywords = "attribute const uniform varying break continue " +
+    "do for while if else in out inout float int void bool true false " +
+    "lowp mediump highp precision invariant discard return mat2 mat3 " +
+    "mat4 vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 sampler2D " +
+    "samplerCube struct gl_FragCoord gl_FragColor";
+  var glslBuiltins = "radians degrees sin cos tan asin acos atan pow " +
+    "exp log exp2 log2 sqrt inversesqrt abs sign floor ceil fract mod " +
+    "min max clamp mix step smoothstep length distance dot cross " +
+    "normalize faceforward reflect refract matrixCompMult lessThan " +
+    "lessThanEqual greaterThan greaterThanEqual equal notEqual any all " +
+    "not dFdx dFdy fwidth texture2D texture2DProj texture2DLod " +
+    "texture2DProjLod textureCube textureCubeLod require export";
+
+  function cppHook(stream, state) {
+    if (!state.startOfLine) return false;
+    stream.skipToEnd();
+    return "meta";
+  }
+
+  ;(function() {
+    // C#-style strings where "" escapes a quote.
+    function tokenAtString(stream, state) {
+      var next;
+      while ((next = stream.next()) != null) {
+        if (next == '"' && !stream.eat('"')) {
+          state.tokenize = null;
+          break;
+        }
+      }
+      return "string";
+    }
+
+    CodeMirror.defineMIME("text/x-glsl", {
+      name: "glsl",
+      keywords: words(glslKeywords),
+      builtins: words(glslBuiltins),
+      blockKeywords: words("case do else for if switch while struct"),
+      atoms: words("null"),
+      hooks: {"#": cppHook}
+    });
+  }());
+});

+ 456 - 0
editor/js/libs/jsonlint.js

@@ -0,0 +1,456 @@
+// Full source:
+//
+//		https://github.com/zaach/jsonlint
+//
+// Copyright (C) 2012 Zachary Carter
+//
+// Permission is hereby granted, free of charge, to any person obtaining a 
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL-
+// INGS IN THE SOFTWARE.
+
+/* Jison generated parser */
+var jsonlint = (function(){
+var parser = {trace: function trace() { },
+yy: {},
+symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
+terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
+productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
+performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1: // replace escaped characters with actual character
+          this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
+                     .replace(/\\n/g,'\n')
+                     .replace(/\\r/g,'\r')
+                     .replace(/\\t/g,'\t')
+                     .replace(/\\v/g,'\v')
+                     .replace(/\\f/g,'\f')
+                     .replace(/\\b/g,'\b');
+        
+break;
+case 2:this.$ = Number(yytext);
+break;
+case 3:this.$ = null;
+break;
+case 4:this.$ = true;
+break;
+case 5:this.$ = false;
+break;
+case 6:return this.$ = $$[$0-1];
+break;
+case 13:this.$ = {};
+break;
+case 14:this.$ = $$[$0-1];
+break;
+case 15:this.$ = [$$[$0-2], $$[$0]];
+break;
+case 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
+break;
+case 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
+break;
+case 18:this.$ = [];
+break;
+case 19:this.$ = $$[$0-1];
+break;
+case 20:this.$ = [$$[$0]];
+break;
+case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
+break;
+}
+},
+table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],
+defaultActions: {16:[2,6]},
+parseError: function parseError(str, hash) {
+    throw new Error(str);
+},
+parse: function parse(input) {
+    var self = this,
+        stack = [0],
+        vstack = [null], // semantic value stack
+        lstack = [], // location stack
+        table = this.table,
+        yytext = '',
+        yylineno = 0,
+        yyleng = 0,
+        recovering = 0,
+        TERROR = 2,
+        EOF = 1;
+
+    //this.reductionCount = this.shiftCount = 0;
+
+    this.lexer.setInput(input);
+    this.lexer.yy = this.yy;
+    this.yy.lexer = this.lexer;
+    if (typeof this.lexer.yylloc == 'undefined')
+        this.lexer.yylloc = {};
+    var yyloc = this.lexer.yylloc;
+    lstack.push(yyloc);
+
+    if (typeof this.yy.parseError === 'function')
+        this.parseError = this.yy.parseError;
+
+    function popStack (n) {
+        stack.length = stack.length - 2*n;
+        vstack.length = vstack.length - n;
+        lstack.length = lstack.length - n;
+    }
+
+    function lex() {
+        var token;
+        token = self.lexer.lex() || 1; // $end = 1
+        // if token isn't its numeric value, convert
+        if (typeof token !== 'number') {
+            token = self.symbols_[token] || token;
+        }
+        return token;
+    }
+
+    var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
+    while (true) {
+        // retreive state number from top of stack
+        state = stack[stack.length-1];
+
+        // use default actions if available
+        if (this.defaultActions[state]) {
+            action = this.defaultActions[state];
+        } else {
+            if (symbol == null)
+                symbol = lex();
+            // read action for current state and first input
+            action = table[state] && table[state][symbol];
+        }
+
+        // handle parse error
+        _handle_error:
+        if (typeof action === 'undefined' || !action.length || !action[0]) {
+
+            if (!recovering) {
+                // Report error
+                expected = [];
+                for (p in table[state]) if (this.terminals_[p] && p > 2) {
+                    expected.push("'"+this.terminals_[p]+"'");
+                }
+                var errStr = '';
+                if (this.lexer.showPosition) {
+                    errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
+                } else {
+                    errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
+                                  (symbol == 1 /*EOF*/ ? "end of input" :
+                                              ("'"+(this.terminals_[symbol] || symbol)+"'"));
+                }
+                this.parseError(errStr,
+                    {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
+            }
+
+            // just recovered from another error
+            if (recovering == 3) {
+                if (symbol == EOF) {
+                    throw new Error(errStr || 'Parsing halted.');
+                }
+
+                // discard current lookahead and grab another
+                yyleng = this.lexer.yyleng;
+                yytext = this.lexer.yytext;
+                yylineno = this.lexer.yylineno;
+                yyloc = this.lexer.yylloc;
+                symbol = lex();
+            }
+
+            // try to recover from error
+            while (1) {
+                // check for error recovery rule in this state
+                if ((TERROR.toString()) in table[state]) {
+                    break;
+                }
+                if (state == 0) {
+                    throw new Error(errStr || 'Parsing halted.');
+                }
+                popStack(1);
+                state = stack[stack.length-1];
+            }
+
+            preErrorSymbol = symbol; // save the lookahead token
+            symbol = TERROR;         // insert generic error symbol as new lookahead
+            state = stack[stack.length-1];
+            action = table[state] && table[state][TERROR];
+            recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
+        }
+
+        // this shouldn't happen, unless resolve defaults are off
+        if (action[0] instanceof Array && action.length > 1) {
+            throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
+        }
+
+        switch (action[0]) {
+
+            case 1: // shift
+                //this.shiftCount++;
+
+                stack.push(symbol);
+                vstack.push(this.lexer.yytext);
+                lstack.push(this.lexer.yylloc);
+                stack.push(action[1]); // push state
+                symbol = null;
+                if (!preErrorSymbol) { // normal execution/no error
+                    yyleng = this.lexer.yyleng;
+                    yytext = this.lexer.yytext;
+                    yylineno = this.lexer.yylineno;
+                    yyloc = this.lexer.yylloc;
+                    if (recovering > 0)
+                        recovering--;
+                } else { // error just occurred, resume old lookahead f/ before error
+                    symbol = preErrorSymbol;
+                    preErrorSymbol = null;
+                }
+                break;
+
+            case 2: // reduce
+                //this.reductionCount++;
+
+                len = this.productions_[action[1]][1];
+
+                // perform semantic action
+                yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
+                // default location, uses first token for firsts, last for lasts
+                yyval._$ = {
+                    first_line: lstack[lstack.length-(len||1)].first_line,
+                    last_line: lstack[lstack.length-1].last_line,
+                    first_column: lstack[lstack.length-(len||1)].first_column,
+                    last_column: lstack[lstack.length-1].last_column
+                };
+                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+
+                if (typeof r !== 'undefined') {
+                    return r;
+                }
+
+                // pop off stack
+                if (len) {
+                    stack = stack.slice(0,-1*len*2);
+                    vstack = vstack.slice(0, -1*len);
+                    lstack = lstack.slice(0, -1*len);
+                }
+
+                stack.push(this.productions_[action[1]][0]);    // push nonterminal (reduce)
+                vstack.push(yyval.$);
+                lstack.push(yyval._$);
+                // goto new state = table[STATE][NONTERMINAL]
+                newState = table[stack[stack.length-2]][stack[stack.length-1]];
+                stack.push(newState);
+                break;
+
+            case 3: // accept
+                return true;
+        }
+
+    }
+
+    return true;
+}};
+/* Jison generated lexer */
+var lexer = (function(){
+var lexer = ({EOF:1,
+parseError:function parseError(str, hash) {
+        if (this.yy.parseError) {
+            this.yy.parseError(str, hash);
+        } else {
+            throw new Error(str);
+        }
+    },
+setInput:function (input) {
+        this._input = input;
+        this._more = this._less = this.done = false;
+        this.yylineno = this.yyleng = 0;
+        this.yytext = this.matched = this.match = '';
+        this.conditionStack = ['INITIAL'];
+        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
+        return this;
+    },
+input:function () {
+        var ch = this._input[0];
+        this.yytext+=ch;
+        this.yyleng++;
+        this.match+=ch;
+        this.matched+=ch;
+        var lines = ch.match(/\n/);
+        if (lines) this.yylineno++;
+        this._input = this._input.slice(1);
+        return ch;
+    },
+unput:function (ch) {
+        this._input = ch + this._input;
+        return this;
+    },
+more:function () {
+        this._more = true;
+        return this;
+    },
+less:function (n) {
+        this._input = this.match.slice(n) + this._input;
+    },
+pastInput:function () {
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+    },
+upcomingInput:function () {
+        var next = this.match;
+        if (next.length < 20) {
+            next += this._input.substr(0, 20-next.length);
+        }
+        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
+    },
+showPosition:function () {
+        var pre = this.pastInput();
+        var c = new Array(pre.length + 1).join("-");
+        return pre + this.upcomingInput() + "\n" + c+"^";
+    },
+next:function () {
+        if (this.done) {
+            return this.EOF;
+        }
+        if (!this._input) this.done = true;
+
+        var token,
+            match,
+            tempMatch,
+            index,
+            col,
+            lines;
+        if (!this._more) {
+            this.yytext = '';
+            this.match = '';
+        }
+        var rules = this._currentRules();
+        for (var i=0;i < rules.length; i++) {
+            tempMatch = this._input.match(this.rules[rules[i]]);
+            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+                match = tempMatch;
+                index = i;
+                if (!this.options.flex) break;
+            }
+        }
+        if (match) {
+            lines = match[0].match(/\n.*/g);
+            if (lines) this.yylineno += lines.length;
+            this.yylloc = {first_line: this.yylloc.last_line,
+                           last_line: this.yylineno+1,
+                           first_column: this.yylloc.last_column,
+                           last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
+            this.yytext += match[0];
+            this.match += match[0];
+            this.yyleng = this.yytext.length;
+            this._more = false;
+            this._input = this._input.slice(match[0].length);
+            this.matched += match[0];
+            token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
+            if (this.done && this._input) this.done = false;
+            if (token) return token;
+            else return;
+        }
+        if (this._input === "") {
+            return this.EOF;
+        } else {
+            this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 
+                    {text: "", token: null, line: this.yylineno});
+        }
+    },
+lex:function lex() {
+        var r = this.next();
+        if (typeof r !== 'undefined') {
+            return r;
+        } else {
+            return this.lex();
+        }
+    },
+begin:function begin(condition) {
+        this.conditionStack.push(condition);
+    },
+popState:function popState() {
+        return this.conditionStack.pop();
+    },
+_currentRules:function _currentRules() {
+        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
+    },
+topState:function () {
+        return this.conditionStack[this.conditionStack.length-2];
+    },
+pushState:function begin(condition) {
+        this.begin(condition);
+    }});
+lexer.options = {};
+lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+
+var YYSTATE=YY_START
+switch($avoiding_name_collisions) {
+case 0:/* skip whitespace */
+break;
+case 1:return 6
+break;
+case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
+break;
+case 3:return 17
+break;
+case 4:return 18
+break;
+case 5:return 23
+break;
+case 6:return 24
+break;
+case 7:return 22
+break;
+case 8:return 21
+break;
+case 9:return 10
+break;
+case 10:return 11
+break;
+case 11:return 8
+break;
+case 12:return 14
+break;
+case 13:return 'INVALID'
+break;
+}
+};
+lexer.rules = [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/];
+lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}};
+
+
+;
+return lexer;})()
+parser.lexer = lexer;
+return parser;
+})();
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = jsonlint;
+exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); }
+exports.main = function commonjsMain(args) {
+    if (!args[1])
+        throw new Error('Usage: '+args[0]+' FILE');
+    if (typeof process !== 'undefined') {
+        var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
+    } else {
+        var cwd = require("file").path(require("file").cwd());
+        var source = cwd.join(args[1]).read({charset: "utf-8"});
+    }
+    return exports.parser.parse(source);
+}
+if (typeof module !== 'undefined' && require.main === module) {
+  exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
+}
+}