Browse Source

Merge pull request #325 from mkroehnert/grunt

First steps towards a full Grunt.js based build system
Manfred Kröhnert 11 years ago
parent
commit
665398793e
11 changed files with 608 additions and 172 deletions
  1. 8 0
      .gitignore
  2. 174 2
      bin/amberc
  3. 29 168
      bin/amberc.js
  4. 191 0
      grunt.js
  5. 144 0
      grunt/tasks/amberc-grunt.js
  6. BIN
      images/sprite.amber.png
  7. 16 0
      js/Compiler-Exceptions.deploy.js
  8. 22 0
      js/Compiler-Exceptions.js
  9. 3 2
      js/init.js
  10. 6 0
      package.json
  11. 15 0
      st/Compiler-Exceptions.st

+ 8 - 0
.gitignore

@@ -14,3 +14,11 @@ test/run.js
 
 # In case amber is also saved in a local Subversion
 .svn
+
+# Ignoring local NPM modules
+node_modules
+
+# Ignoring folders - compiled versions of Amber
+# local NPM modules, tmp
+dist
+tmp

+ 174 - 2
bin/amberc

@@ -3,11 +3,183 @@
 var path = require('path');
 var amberc = require('./amberc.js');
 
+// get parameters passed to the command line script
+// discard the first two parameters which are the node binary and the script name
+var parameters = process.argv.slice(2);
+
+// check if at least one parameter was passed to the script
+if (1 > parameters.length) {
+	print_usage();
+	process.exit();
+}
+
+
 // Get Amber root directory from the location of this script so that
 // we can find the st and js directories etc.
 var amber_dir = path.normalize(path.join(path.dirname(process.argv[1]), '..'));
 // Get default location of compiler.jar
 var closure_jar = path.resolve(path.join(process.env['HOME'], 'compiler.jar'));
 
-var compiler = new amberc.Compiler(amber_dir, closure_jar);
-compiler.main();
+var compiler = new amberc.Compiler(amber_dir);
+
+var configuration = handle_options(parameters, amber_dir);
+
+compiler.main(configuration);
+
+
+/**
+ * Process given program options and update defaults values.
+ * Followed by check_for_closure_compiler() and then collect_files().
+ */
+function handle_options(optionsArray, amber_dir) {
+	var programName = [];
+	var currentItem = optionsArray.shift();
+	var defaults = amberc.createDefaults(amber_dir);
+
+	while(undefined !== currentItem) {
+		switch(currentItem) {
+			case '-l':
+				defaults.load.push.apply(defaults.load, optionsArray.shift().split(','));
+				break;
+			case '-i':
+				defaults.init = optionsArray.shift();
+				break;
+			case '-m':
+				defaults.main = optionsArray.shift();
+				break;
+			case '-M':
+				defaults.mainfile = optionsArray.shift();
+				break;
+			case '-o':
+				defaults.closure = true;
+				defaults.closure_parts = true;
+				break;
+			case '-O':
+				defaults.closure = true;
+				defaults.closure_full = true;
+				break;
+			case '-A':
+				defaults.closure = true;
+				defaults.closure_options = defaults.closure_options + ' --compilation_level ADVANCED_OPTIMIZATIONS';
+				defaults.closure_full = true;
+				break;
+			case '-d':
+				defaults.deploy = true;
+				break;
+			case '-s':
+				defaults.suffix = optionsArray.shift();
+				defaults.suffix_used = defaults.suffix;
+				break;
+			case '-S':
+				defaults.loadsuffix = optionsArray.shift();
+				defaults.suffix_used = defaults.suffix;
+				break;
+			case '-h':
+			case '--help':
+			case '?':
+				this.usage();
+				break;
+			default:
+				var fileSuffix = path.extname(currentItem);
+				switch (fileSuffix) {
+					case '.st':
+						defaults.stFiles.push(currentItem);
+						break;
+					case '.js':
+						defaults.jsFiles.push(currentItem);
+						break;
+					default:
+						// Will end up being the last non js/st argument
+						programName.push(currentItem);
+						break;
+				};
+		};
+		currentItem = optionsArray.shift();
+	}
+
+	if(1 < programName.length) {
+		throw new Error('More than one name for ProgramName given: ' + programName);
+	} else {
+		defaults.program = programName[0];
+	}
+	return defaults;
+};
+
+
+// print available flags
+function print_usage() {
+	console.log('Usage: amberc [-l lib1,lib2...] [-i init_file] [-m main_class] [-M main_file]');
+	console.log('          [-o] [-O|-A] [-d] [-s suffix] [-S suffix] [file1 [file2 ...]] [Program]');
+	console.log('');
+	console.log('   amberc compiles Amber files - either separately or into a complete runnable');
+	console.log('   program. If no .st files are listed only a linking stage is performed.');
+	console.log('   Files listed will be handled using the following rules:');
+	console.log('');
+	console.log('   *.js');
+	console.log('     Files are linked (concatenated) in listed order.');
+	console.log('     If not found we look in $AMBER/js/');
+	console.log('');
+	console.log('   *.st');
+	console.log('     Files are compiled into .js files before concatenation.');
+	console.log('     If not found we look in $AMBER/st/.');
+	console.log('');
+	console.log('     NOTE: Each .st file is currently considered to be a fileout of a single class');
+	console.log('     category of the same name as the file!');
+	console.log('');
+	console.log('   If no <Program> is specified each given .st file will be compiled into');
+	console.log('   a matching .js file. Otherwise a <Program>.js file is linked together based on');
+	console.log('   the given options:');
+	console.log('  -l library1,library2');
+	console.log('     Add listed JavaScript libraries in listed order.');
+	console.log('     Libraries are not separated by spaces or end with .js.');
+	console.log('');
+	console.log('  -i init_file');
+	console.log('     Add library initializer <init_file> instead of default $AMBER/js/init.js ');
+	console.log('');
+	console.log('  -m main_class');
+	console.log('     Add a call to the class method main_class>>main at the end of <Program>.');
+	console.log('');
+	console.log('  -M main_file');
+	console.log('     Add <main_file> at the end of <Program.js> acting as #main.');
+	console.log('');
+	console.log('  -o');
+	console.log('     Optimize each .js file using the Google closure compiler.');
+	console.log('     Using Closure compiler found at ~/compiler.jar');
+	console.log('');
+	console.log('  -O');
+	console.log('     Optimize final <Program>.js using the Google closure compiler.');
+	console.log('     Using Closure compiler found at ~/compiler.jar');
+	console.log('');
+	console.log('  -A Same as -O but use --compilation_level ADVANCED_OPTIMIZATIONS');
+	console.log('');
+	console.log('  -d');
+	console.log('     Additionally export code for deploy - stripped from source etc.');
+	console.log('     Uses suffix ".deploy.js" in addition to any explicit suffic set by -s.');
+	console.log('');
+	console.log('  -s suffix');
+	console.log('     Add <suffix> to compiled .js files. File.st is then compiled into');
+	console.log('     File.<suffix>.js.');
+	console.log('');
+	console.log('  -S suffix');
+	console.log('     Use <suffix> for all libraries accessed using -l. This makes it possible');
+	console.log('     to have multiple flavors of Amber and libraries in the same place.');
+	console.log('');
+	console.log('');
+	console.log('     Example invocations:');
+	console.log('');
+	console.log('     Just compile Kernel-Objects.st to Kernel-Objects.js:');
+	console.log('');
+	console.log('        amberc Kernel-Objects.st');
+	console.log('');
+	console.log('     Compile Hello.st to Hello.js and create complete program called Program.js.');
+	console.log('     Additionally add a call to the class method Hello>>main:');
+	console.log('');
+	console.log('        amberc -m Hello Hello.st Program');
+	console.log('');
+	console.log('     Compile Cat1.st and Cat2.st files into corresponding .js files.');
+	console.log('     Link them with myboot.js and myKernel.js and add myinit.js as custom');
+	console.log('     initializer file. Add main.js last which contains the startup code');
+	console.log('      and merge everything into a complete program named Program.js:');
+	console.log('');
+	console.log('        amberc -M main.js -i myinit.js myboot.js myKernel.js Cat1.st Cat2.st Program');
+};

+ 29 - 168
bin/amberc.js

@@ -95,12 +95,17 @@ function AmberC(amber_dir, closure_jar) {
  * Default values.
  */
 var createDefaults = function(amber_dir, finished_callback){
+	if (undefined === amber_dir) {
+		throw new Error('createDefaults() function needs a valid amber_dir parameter');
+	}
+
 	return {
-		'smalltalk': {}, // the evaluated compiler will be stored in this variable (see create_compiler)
 		'load': [],
 		'init': path.join(amber_dir, 'js', 'init.js'),
 		'main': undefined,
 		'mainfile': undefined,
+		'stFiles': [],
+		'jsFiles': [],
 		'closure': false,
 		'closure_parts': false,
 		'closure_full': false,
@@ -122,184 +127,38 @@ var createDefaults = function(amber_dir, finished_callback){
 /**
  * Main function for executing the compiler.
  */
-AmberC.prototype.main = function(parameters, finished_callback) {
+AmberC.prototype.main = function(configuration, finished_callback) {
 	console.time('Compile Time');
-	var options = parameters || process.argv.slice(2);
+	if (undefined !== finished_callback) {
+		configuration.finished_callback = finished_callback;
+	}
 
-	if (1 > options.length) {
-		this.usage();
-	} else {
-		this.defaults = createDefaults(this.amber_dir, finished_callback);
-		this.handle_options(options);
+	if (this.check_configuration_ok(configuration)) {
+		this.defaults = configuration;
+		this.defaults.smalltalk = {}; // the evaluated compiler will be stored in this variable (see create_compiler)
+		var self = this;
+		this.check_for_closure_compiler(function(){
+			self.collect_files(self.defaults.stFiles, self.defaults.jsFiles)
+		});
 	}
 };
 
 
 /**
- * Process given program options and update defaults values.
- * Followed by check_for_closure_compiler() and then collect_files().
+ * Check if the passed in configuration object has sufficient/nonconflicting values
  */
-AmberC.prototype.handle_options = function(optionsArray) {
-	var stFiles = [];
-	var jsFiles = [];
-	var programName = [];
-	var currentItem = optionsArray.shift();
-	var defaults = this.defaults;
-
-	while(undefined !== currentItem) {
-		switch(currentItem) {
-			case '-l':
-				defaults.load.push.apply(defaults.load, optionsArray.shift().split(','));
-				break;
-			case '-i':
-				defaults.init = optionsArray.shift();
-				break;
-			case '-m':
-				defaults.main = optionsArray.shift();
-				break;
-			case '-M':
-				defaults.mainfile = optionsArray.shift();
-				break;
-			case '-o':
-				defaults.closure = true;
-				defaults.closure_parts = true;
-				break;
-			case '-O':
-				defaults.closure = true;
-				defaults.closure_full = true;
-				break;
-			case '-A':
-				defaults.closure = true;
-				defaults.closure_options = defaults.closure_options + ' --compilation_level ADVANCED_OPTIMIZATIONS';
-				defaults.closure_full = true;
-				break;
-			case '-d':
-				defaults.deploy = true;
-				break;
-			case '-s':
-				defaults.suffix = optionsArray.shift();
-				defaults.suffix_used = defaults.suffix;
-				break;
-			case '-S':
-				defaults.loadsuffix = optionsArray.shift();
-				defaults.suffix_used = defaults.suffix;
-				break;
-			case '-h':
-			case '--help':
-			case '?':
-				this.usage();
-				break;
-			default:
-				var fileSuffix = path.extname(currentItem);
-				switch (fileSuffix) {
-					case '.st':
-						stFiles.push(currentItem);
-						break;
-					case '.js':
-						jsFiles.push(currentItem);
-						break;
-					default:
-						// Will end up being the last non js/st argument
-						programName.push(currentItem);
-						break;
-				};
-		};
-		currentItem = optionsArray.shift();
+AmberC.prototype.check_configuration_ok = function(configuration) {
+	if (undefined === configuration) {
+		throw new Error('AmberC.check_configuration_ok(): missing configuration object');
 	}
-
-	if(1 < programName.length) {
-		throw new Error('More than one name for ProgramName given: ' + programName);
-	} else {
-		defaults.program = programName[0];
+	if (undefined === configuration.init) {
+		throw new Error('AmberC.check_configuration_ok(): init value missing in configuration object');
 	}
 
-	var self = this;
-	this.check_for_closure_compiler(function(){
-		self.collect_files(stFiles, jsFiles)
-	});
-};
-
-
-/**
- * Print usage options and exit.
- */
-AmberC.prototype.usage = function() {
-	console.log('Usage: amberc [-l lib1,lib2...] [-i init_file] [-m main_class] [-M main_file]');
-	console.log('          [-o] [-O|-A] [-d] [-s suffix] [-S suffix] [file1 [file2 ...]] [Program]');
-	console.log('');
-	console.log('   amberc compiles Amber files - either separately or into a complete runnable');
-	console.log('   program. If no .st files are listed only a linking stage is performed.');
-	console.log('   Files listed will be handled using the following rules:');
-	console.log('');
-	console.log('   *.js');
-	console.log('     Files are linked (concatenated) in listed order.');
-	console.log('     If not found we look in $AMBER/js/');
-	console.log('');
-	console.log('   *.st');
-	console.log('     Files are compiled into .js files before concatenation.');
-	console.log('     If not found we look in $AMBER/st/.');
-	console.log('');
-	console.log('     NOTE: Each .st file is currently considered to be a fileout of a single class');
-	console.log('     category of the same name as the file!');
-	console.log('');
-	console.log('   If no <Program> is specified each given .st file will be compiled into');
-	console.log('   a matching .js file. Otherwise a <Program>.js file is linked together based on');
-	console.log('   the given options:');
-	console.log('  -l library1,library2');
-	console.log('     Add listed JavaScript libraries in listed order.');
-	console.log('     Libraries are not separated by spaces or end with .js.');
-	console.log('');
-	console.log('  -i init_file');
-	console.log('     Add library initializer <init_file> instead of default $AMBER/js/init.js ');
-	console.log('');
-	console.log('  -m main_class');
-	console.log('     Add a call to the class method main_class>>main at the end of <Program>.');
-	console.log('');
-	console.log('  -M main_file');
-	console.log('     Add <main_file> at the end of <Program.js> acting as #main.');
-	console.log('');
-	console.log('  -o');
-	console.log('     Optimize each .js file using the Google closure compiler.');
-	console.log('     Using Closure compiler found at ~/compiler.jar');
-	console.log('');
-	console.log('  -O');
-	console.log('     Optimize final <Program>.js using the Google closure compiler.');
-	console.log('     Using Closure compiler found at ~/compiler.jar');
-	console.log('');
-	console.log('  -A Same as -O but use --compilation_level ADVANCED_OPTIMIZATIONS');
-	console.log('');
-	console.log('  -d');
-	console.log('     Additionally export code for deploy - stripped from source etc.');
-	console.log('     Uses suffix ".deploy.js" in addition to any explicit suffic set by -s.');
-	console.log('');
-	console.log('  -s suffix');
-	console.log('     Add <suffix> to compiled .js files. File.st is then compiled into');
-	console.log('     File.<suffix>.js.');
-	console.log('');
-	console.log('  -S suffix');
-	console.log('     Use <suffix> for all libraries accessed using -l. This makes it possible');
-	console.log('     to have multiple flavors of Amber and libraries in the same place.');
-	console.log('');
-	console.log('');
-	console.log('     Example invocations:');
-	console.log('');
-	console.log('     Just compile Kernel-Objects.st to Kernel-Objects.js:');
-	console.log('');
-	console.log('        amberc Kernel-Objects.st');
-	console.log('');
-	console.log('     Compile Hello.st to Hello.js and create complete program called Program.js.');
-	console.log('     Additionally add a call to the class method Hello>>main:');
-	console.log('');
-	console.log('        amberc -m Hello Hello.st Program');
-	console.log('');
-	console.log('     Compile Cat1.st and Cat2.st files into corresponding .js files.');
-	console.log('     Link them with myboot.js and myKernel.js and add myinit.js as custom');
-	console.log('     initializer file. Add main.js last which contains the startup code');
-	console.log('      and merge everything into a complete program named Program.js:');
-	console.log('');
-	console.log('        amberc -M main.js -i myinit.js myboot.js myKernel.js Cat1.st Cat2.st Program');
-
-	process.exit();
+	if (0 === configuration.jsFiles.length && 0 === configuration.stFiles.lenght) {
+		throw new Error('AmberC.check_configuration_ok(): no files to compile/link specified in configuration object');
+	}
+	return true;
 };
 
 
@@ -547,6 +406,7 @@ AmberC.prototype.create_compiler = function(compilerFilesArray) {
 		content = content + 'return smalltalk;})();';
 		self.defaults.smalltalk = eval(content);
 		console.log('Compiler loaded');
+		self.defaults.smalltalk.ErrorHandler._setCurrent_(self.defaults.smalltalk.RethrowErrorHandler._new());
 
 		self.compile();
 	});
@@ -759,5 +619,6 @@ AmberC.prototype.closure_compile = function(sourceFile, minifiedFile, callback)
 };
 
 module.exports.Compiler = AmberC;
+module.exports.createDefaults = createDefaults;
 module.exports.Combo = Combo;
 module.exports.map = async_map;

+ 191 - 0
grunt.js

@@ -0,0 +1,191 @@
+module.exports = function(grunt) {
+
+  grunt.loadTasks('./grunt/tasks');
+
+  grunt.loadNpmTasks('grunt-image-embed');
+  grunt.loadNpmTasks('grunt-contrib-mincss');
+
+  grunt.registerTask('build:deploy', 'shell:compileDeploy concat:deploy min');
+  grunt.registerTask('build:dev', 'shell:compileDev concat:css imageEmbed mincss css2js concat:dev');
+//  grunt.registerTask('default', 'build:deploy build:dev');
+  grunt.registerTask('default', 'amberc');
+
+  grunt.initConfig({
+    pkg: '<json:package.json>',
+
+    meta: {
+      banner: '/*!\n <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n License: <%= pkg.license.type %> \n*/\n'
+    },
+
+    pegjs: {
+      amber_parser: {
+        src: 'js/parser.pegjs',
+        dest: 'js/parser.js',
+        trackLineAndColumn: true,
+        cache: false,
+        export_var: 'smalltalk.parser'
+      }
+    },
+
+    amberc: {
+      _config: {
+        amber_dir: process.cwd(),
+        closure_jar: ''
+      },
+      amber_kernel: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['Kernel-Objects.st', 'Kernel-Classes.st', 'Kernel-Methods.st', 'Kernel-Collections.st',
+              'Kernel-Exceptions.st', 'Kernel-Transcript.st', 'Kernel-Announcements.st'],
+        deploy: true
+      },
+      amber_compiler: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['Importer-Exporter.st', 'Compiler.st', 'Compiler-Exceptions.st', 'Compiler-Core.st', 'Compiler-AST.st',
+              'Compiler-IR.st', 'Compiler-Inlining.st', 'Compiler-Semantic.st'],
+        output_name: 'Compiler',
+        deploy: true
+      },
+      amber_canvas: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['Canvas.st', 'SUnit.st'],
+        deploy: true
+      },
+      amber_IDE: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['IDE.st', 'Documentation.st'],
+        libraries: ['Canvas'],
+        deploy: true
+      },
+      amber_tests: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['Kernel-Tests.st', 'Compiler-Tests.st'],
+        libraries: ['SUnit']
+      },
+      amber_examples: {
+        working_dir: 'st',
+        target_dir : 'js',
+        src: ['Examples.st'],
+        libraries: ['Canvas', 'IDE']
+      },
+      amber_dev: {
+        working_dir: 'js',
+        src: ['Kernel-Objects.js', 'Kernel-Classes.js', 'Kernel-Methods.js', 'Kernel-Collections.js',
+              'Kernel-Exceptions.js', 'Kernel-Transcript.js', 'Kernel-Announcements.js',
+              'Compiler.js', 'Compiler-Exceptions.js', 'Compiler-Core.js', 'Compiler-AST.js',
+              'Compiler-IR.js', 'Compiler-Inlining.js', 'Compiler-Semantic.js',
+              'Kernel-Tests.js', 'Compiler-Tests.js',
+              'Canvas.js', 'IDE.js', 'SUnit.js', 'Documentation.js', 'Examples.js'],
+        output_name: 'amber'
+      },
+      server: {
+        working_dir: 'server',
+        src: ['FileServer.st'],
+        main_class: 'FileServer',
+        output_name: 'server'
+      },
+      repl: {
+        working_dir: 'repl',
+        src: ['REPL.st'],
+        main_class: 'Repl',
+        output_name: 'amber'
+      }
+    },
+
+    lint: {
+      amber: ['js/*.js'],
+      server: ['server/*.js'],
+      repl: ['repl/*.js'],
+      tests: ['test/*.js'],
+      grunt: ['grunt.js', 'grunt/**/*.js']
+    },
+
+    concat: {
+      deploy: {
+        src: ['tmp/amber-compiled.deploy.js'],
+        dest: 'dist/amber-<%= pkg.version %>.deploy.js'
+      },
+
+      css: {
+        src: [
+          'css/amber.css',
+          'js/lib/CodeMirror/codemirror.css',
+          'js/lib/CodeMirror/amber.css'
+        ],
+        dest: 'tmp/amber.css'
+      },
+
+      dev: {
+        src: [
+          'js/lib/jQuery/jquery-ui-1.8.16.custom.min.js',
+          'js/lib/jQuery/jquery.textarea.js',
+          'js/lib/CodeMirror/codemirror.js',
+          'js/lib/CodeMirror/smalltalk.js',
+          'tmp/amber-compiled.js',
+          'tmp/css.js'
+        ],
+        dest: 'dist/amber-<%= pkg.version %>.js'
+      }
+    },
+
+    imageEmbed: {
+      dev: {
+        src: ['tmp/amber.css'],
+        dest: 'tmp/amber-images.css',
+        options: {baseDir: 'public'}
+      }
+    },
+
+    mincss: {
+      dev: {
+        src: ['tmp/amber-images.css'],
+        dest: 'tmp/amber.min.css'
+      }
+    },
+
+    css2js: {
+      dev: {
+        src: 'tmp/amber.min.css',
+        dest: 'tmp/css.js'
+      }
+    },
+
+    min: {
+      deploy: {
+        src: 'dist/amber-<%= pkg.version %>.deploy.js',
+        dest: 'dist/amber-<%= pkg.version %>.deploy.min.js'
+      }
+    }
+  });
+
+  grunt.registerMultiTask('css2js', 'Embed CSS into JS', function() {
+    var cssContent = grunt.task.directive(grunt.file.expandFiles(this.data.src)[0], grunt.file.read);
+    var content =
+      'var css="' + cssContent + '";' +
+      'var cssTag = document.createElement("link");' +
+      'document.head = document.head || document.getElementsByTagName("head")[0];' +
+      'cssTag.href = "data:text/css,"+css;' +
+      'cssTag.rel = "stylesheet";' +
+      'document.head.appendChild(cssTag);';
+
+    grunt.file.write(this.data.dest, content);
+
+    grunt.log.writeln('File "' + this.data.dest + '" created.');
+  });
+
+  grunt.registerMultiTask('pegjs', 'Generate JavaScript parser from PEG.js description', function() {
+    var PEG = require('pegjs');
+    var pegOptions = {
+      cache: this.data.cache || false,
+      trackLineAndColumn: this.data.trackLineAndColumn || false
+    };
+    var export_var = this.data.export_var || 'module.exports';
+    var parser = PEG.buildParser(grunt.file.read(this.data.src), pegOptions);
+    var content = export_var + ' = ' + parser.toSource() + ';\n';
+    grunt.file.write(this.data.dest, content);
+  });
+};

+ 144 - 0
grunt/tasks/amberc-grunt.js

@@ -0,0 +1,144 @@
+module.exports = function(grunt) {
+
+  var path = require('path');
+  var fs = require('fs');
+  var amberc = require('../../bin/amberc.js');
+
+  /**
+     Full config looks like this:
+     amberc: {
+       _config: {
+         amber_dir: process.cwd(),     // REQUIRED
+         closure_jar: ''               // optional
+       },
+       helloWorld: {
+         src: ['HelloWorld.st'],                // REQUIRED
+         working_dir: 'projects/HelloWorld/st', // optional
+         target_dir: 'projects/HelloWorld/js',  // optional
+         main_class: 'HelloWorld',              // optional
+         output_name: 'helloWorld',            // optional
+         libraries: 'Canvas',                  // optional
+         init: 'myInit',                       // optional
+         main_file: 'myMain.js',               // optional
+         deploy: true,                         // optional
+         output_suffix: 'mySuffix',            // optional
+         library_suffix: '-0.9',               // optional
+       },
+     },
+
+   */
+  grunt.registerMultiTask('amberc', 'Compile Smalltalk files with the amberc compiler', function() {
+    // mark required properties
+    this.requiresConfig('amberc._config.amber_dir');
+    this.requiresConfig(['amberc', this.target, 'src']);
+
+    // change working directory if the working_dir property is set on the target
+    var current_dir = process.cwd();
+    var working_dir = this.data.working_dir;
+    if (undefined !== working_dir) {
+      grunt.file.setBase(working_dir);
+    }
+
+    // mark task as async task
+    var done = this.async();
+
+    // create and initialize amberc
+    var compiler = new amberc.Compiler(grunt.config('amberc._config.amber_dir'), grunt.config('amberc._config.closure_jar'));
+
+    // generate the amberc configuration out of the given target properties
+    var configuration = generateCompilerConfiguration(this.data, grunt.config('amberc._config.amber_dir'));
+
+    // run the compiler
+    // change back to the old working directory and call the async callback once finished
+    var self = this;
+    compiler.main(configuration, function(){
+      if (undefined !== self.data.target_dir) {
+        var absolute_target_dir = path.join(current_dir, self.data.target_dir);
+        replaceFileSuffix_moveToTargetDir(self.data.src, absolute_target_dir);
+        // if deploy is set also copy the deploy files
+        if (self.data.deploy) {
+          var suffix = self.data.output_suffix || 'deploy';
+          suffix = '.' + suffix + '.js';
+          replaceFileSuffix_moveToTargetDir(self.data.src, absolute_target_dir, suffix);
+        }
+      }
+      // reset working directory
+      grunt.file.setBase(current_dir);
+      // signal that task has finished
+      done();
+    });
+  });
+
+
+  function generateCompilerConfiguration(data, amber_dir) {
+    var configuration = amberc.createDefaults(amber_dir);
+    var parameters = [];
+
+    var libraries = data.libraries;
+    if (undefined !== libraries) {
+      configuration.load = libraries;
+    }
+    var initFile = data.init;
+    if (undefined !== initFile) {
+      configuration.init = initFile;
+    }
+    var mainClass = data.main_class;
+    if (undefined !== mainClass) {
+      configuration.main = mainClass;
+    }
+    var mainFile = data.main_file;
+    if (undefined !== initFile) {
+      configuration.mainfile = mainFile;
+    }
+    if (true === data.deploy) {
+      configuration.deploy = true;
+    }
+    var outputSuffix = data.output_suffix;
+    if (undefined !== outputSuffix) {
+      configuration.suffix = outputSuffix;
+      configuration.suffix_used = outputSuffix;
+    }
+    var librarySuffix = data.library_suffix;
+    if (undefined !== librarySuffix) {
+      configuration.loadsuffix = librarySuffix;
+      configuration.suffix_used = librarySuffix;
+    }
+    var sourceFiles = data.src;
+    if (undefined !== sourceFiles) {
+      sourceFiles.forEach(function(currentItem){
+        var fileSuffix = path.extname(currentItem);
+        switch (fileSuffix) {
+          case '.st':
+            configuration.stFiles.push(currentItem);
+          break;
+          case '.js':
+            configuration.jsFiles.push(currentItem);
+          break;
+        }
+      });
+    }
+    var outputName = data.output_name;
+    if (undefined !== outputName) {
+      configuration.program = outputName;
+    }
+    return configuration;
+  }
+
+
+  /**
+   * Replace '.st' suffix of \p files with '.js' or with \p replace_suffix if this parameter is given.
+   * Afterwards move the files with replaced suffix to \p target_dir if the files exist.
+   */
+  function replaceFileSuffix_moveToTargetDir(files, target_dir, replace_suffix) {
+    var suffix = replace_suffix || '.js';
+    var compiledFiles = files.map(function(item) {
+      return item.replace(/.st$/g, suffix);
+    });
+
+    compiledFiles.forEach(function(file) {
+      if (path.existsSync(file)) {
+        fs.renameSync(file, path.join(target_dir, file));
+      }
+    });
+  }
+};

BIN
images/sprite.amber.png


+ 16 - 0
js/Compiler-Exceptions.deploy.js

@@ -112,3 +112,19 @@ smalltalk.UnknownVariableError);
 
 
 
+smalltalk.addClass('RethrowErrorHandler', smalltalk.ErrorHandler, [], 'Compiler-Exceptions');
+smalltalk.addMethod(
+"_handleError_",
+smalltalk.method({
+selector: "handleError:",
+fn: function (anError){
+var self=this;
+smalltalk.send(self,"_handleError_",[anError],smalltalk.ErrorHandler);
+throw anError;
+;
+return self}
+}),
+smalltalk.RethrowErrorHandler);
+
+
+

+ 22 - 0
js/Compiler-Exceptions.js

@@ -159,3 +159,25 @@ smalltalk.UnknownVariableError);
 
 
 
+smalltalk.addClass('RethrowErrorHandler', smalltalk.ErrorHandler, [], 'Compiler-Exceptions');
+smalltalk.RethrowErrorHandler.comment="This class is used in the commandline version of the compiler.\x0aIt uses the handleError: message of ErrorHandler for printing the stacktrace and throws the error again as JS exception.\x0aAs a result Smalltalk errors are not swallowd by the Amber runtime and compilation can be aborted."
+smalltalk.addMethod(
+"_handleError_",
+smalltalk.method({
+selector: "handleError:",
+category: 'error handling',
+fn: function (anError){
+var self=this;
+smalltalk.send(self,"_handleError_",[anError],smalltalk.ErrorHandler);
+throw anError;
+;
+return self},
+args: ["anError"],
+source: "handleError: anError\x0a\x09super handleError: anError.\x0a    <throw anError>",
+messageSends: ["handleError:"],
+referencedClasses: []
+}),
+smalltalk.RethrowErrorHandler);
+
+
+

+ 3 - 2
js/init.js

@@ -1,9 +1,10 @@
 smalltalk.init(smalltalk.Object); //metaclasses are in through Class
 smalltalk.classes()._do_(function(each) {
-	each._initialize()});
+	each._initialize();
+});
 
 /* Similar to jQuery(document).ready() */
 
 if(this.smalltalkReady) {
-    this.smalltalkReady();
+	this.smalltalkReady();
 }

+ 6 - 0
package.json

@@ -24,5 +24,11 @@
     },
     "scripts": {
         "test": "./test/run_build.sh"
+    },
+    "devDependencies": {
+      "pegjs": "~0.7.0",
+      "grunt": "~0.3.17",
+      "grunt-image-embed": "~0.1.3",
+      "grunt-contrib-mincss": "~0.3.2"
     }
 }

+ 15 - 0
st/Compiler-Exceptions.st

@@ -85,3 +85,18 @@ variableName: aString
 	variableName := aString
 ! !
 
+ErrorHandler subclass: #RethrowErrorHandler
+	instanceVariableNames: ''
+	package: 'Compiler-Exceptions'!
+!RethrowErrorHandler commentStamp!
+This class is used in the commandline version of the compiler.
+It uses the handleError: message of ErrorHandler for printing the stacktrace and throws the error again as JS exception.
+As a result Smalltalk errors are not swallowd by the Amber runtime and compilation can be aborted.!
+
+!RethrowErrorHandler methodsFor: 'error handling'!
+
+handleError: anError
+	super handleError: anError.
+    <throw anError>
+! !
+