diff --git a/compiler/driver.c b/compiler/driver.c index fd9826dc..1e165838 100644 --- a/compiler/driver.c +++ b/compiler/driver.c @@ -28,6 +28,10 @@ static int eq(const char * s1, const char * s2) { return strcmp(s1, s2) == 0; } +static int startswith(const char * s1, const char * s2) { + return strncmp(s1, s2, strlen(s2)) == 0; +} + static void print_arglist(int exit_code) { FILE * f = exit_code ? stderr : stdout; fprintf(f, "Usage: snowball SOURCE_FILE... [OPTIONS]\n\n" @@ -49,7 +53,8 @@ static void print_arglist(int exit_code) { " -py, -python\n" #endif #ifndef DISABLE_JS - " -js\n" + " -js[=TYPE] generate Javascript (TYPE values:\n" + " esm global, default: global)\n" #endif #ifndef DISABLE_RUST " -rust\n" @@ -115,6 +120,7 @@ static int read_options(struct options * o, int argc, char * argv[]) { o->output_file = 0; o->syntax_tree = false; o->comments = false; + o->js_esm = false; o->externals_prefix = NULL; o->variables_prefix = 0; o->runtime_path = 0; @@ -162,6 +168,18 @@ static int read_options(struct options * o, int argc, char * argv[]) { #ifndef DISABLE_JS if (eq(s, "-js")) { o->make_lang = LANG_JAVASCRIPT; + o->js_esm = false; + continue; + } + if (startswith(s, "-js=")) { + o->make_lang = LANG_JAVASCRIPT; + if (eq(s + 4, "global")) { + o->js_esm = false; + } else if (eq(s + 4, "esm")) { + o->js_esm = true; + } else { + fprintf(stderr, "Unknown Javascript type '%s'\n", s + 4); + } continue; } #endif @@ -543,7 +561,11 @@ extern int main(int argc, char * argv[]) { #ifndef DISABLE_JS if (o->make_lang == LANG_JAVASCRIPT) { byte * s = add_sz_to_s(0, output_base); - s = add_literal_to_s(s, ".js"); + if (o->js_esm) { + s = add_literal_to_s(s, ".mjs"); + } else { + s = add_literal_to_s(s, ".js"); + } o->output_src = get_output(s); lose_s(s); generate_program_js(g); diff --git a/compiler/generator_js.c b/compiler/generator_js.c index bb4b20d3..fbbd156a 100644 --- a/compiler/generator_js.c +++ b/compiler/generator_js.c @@ -1221,11 +1221,21 @@ static void generate(struct generator * g, struct node * p) { } static void generate_class_begin(struct generator * g) { - - w(g, "/**@constructor*/~N" - "var ~n = function() {~+~N" - "~Mconst ~P = require('./base-stemmer.js');~N" - "~Mvar base = new ~P();~N"); + if (g->options->js_esm) { + w(g, "// deno-lint-ignore-file~N" + "import ~P from './base-stemmer.mjs'~N" + "~N" + "/** @typedef {{ stemWord(word: string): string }} Stemmer */~N" + "~N" + "/** @type {{ new(): Stemmer }} */~N" + "const ~n = function() {~+~N" + "~Mvar base = new ~P();~N"); + } else { + w(g, "/**@constructor*/~N" + "var ~n = function() {~+~N" + "~Mconst ~P = require('./base-stemmer.js');~N" + "~Mvar base = new ~P();~N"); + } } static void generate_class_end(struct generator * g) { @@ -1237,8 +1247,13 @@ static void generate_class_end(struct generator * g) { w(g, "~Mreturn base.getCurrent();~N"); w(g, "~-~M};~N"); w(g, "~-};~N"); - w(g, "~N"); - w(g, "if (typeof module === 'object' && module.exports) module.exports = ~n;~N"); + if (g->options->js_esm) { + w(g, "~N" + "export default ~n~N"); + } else { + w(g, "~N" + "if (typeof module === 'object' && module.exports) module.exports = ~n;~N"); + } } static void generate_among_table(struct generator * g, struct among * x) { diff --git a/compiler/header.h b/compiler/header.h index ff6f8078..6fd1b58c 100644 --- a/compiler/header.h +++ b/compiler/header.h @@ -385,6 +385,7 @@ struct options { FILE * output_h; byte syntax_tree; byte comments; + byte js_esm; enc encoding; enum { LANG_JAVA, LANG_C, LANG_CPLUSPLUS, LANG_CSHARP, LANG_PASCAL, LANG_PYTHON, LANG_JAVASCRIPT, LANG_RUST, LANG_GO, LANG_ADA } make_lang; const char * externals_prefix; diff --git a/javascript/base-stemmer.js b/javascript/base-stemmer.js index 63308cd7..d8003bc5 100644 --- a/javascript/base-stemmer.js +++ b/javascript/base-stemmer.js @@ -1,5 +1,18 @@ +// @ts-check + /**@constructor*/ const BaseStemmer = function() { + /** @protected */ + this.current = ''; + this.cursor = 0; + this.limit = 0; + this.limit_backward = 0; + this.bra = 0; + this.ket = 0; + + /** + * @param {string} value + */ this.setCurrent = function(value) { this.current = value; this.cursor = 0; @@ -9,11 +22,18 @@ const BaseStemmer = function() { this.ket = this.limit; }; + /** + * @return {string} + */ this.getCurrent = function() { return this.current; }; + /** + * @param {BaseStemmer} other + */ this.copy_from = function(other) { + /** @protected */ this.current = other.current; this.cursor = other.cursor; this.limit = other.limit; @@ -22,7 +42,14 @@ const BaseStemmer = function() { this.ket = other.ket; }; + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ this.in_grouping = function(s, min, max) { + /** @protected */ if (this.cursor >= this.limit) return false; var ch = this.current.charCodeAt(this.cursor); if (ch > max || ch < min) return false; @@ -32,7 +59,14 @@ const BaseStemmer = function() { return true; }; + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ this.in_grouping_b = function(s, min, max) { + /** @protected */ if (this.cursor <= this.limit_backward) return false; var ch = this.current.charCodeAt(this.cursor - 1); if (ch > max || ch < min) return false; @@ -42,7 +76,14 @@ const BaseStemmer = function() { return true; }; + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ this.out_grouping = function(s, min, max) { + /** @protected */ if (this.cursor >= this.limit) return false; var ch = this.current.charCodeAt(this.cursor); if (ch > max || ch < min) { @@ -57,7 +98,14 @@ const BaseStemmer = function() { return false; }; + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ this.out_grouping_b = function(s, min, max) { + /** @protected */ if (this.cursor <= this.limit_backward) return false; var ch = this.current.charCodeAt(this.cursor - 1); if (ch > max || ch < min) { @@ -72,8 +120,13 @@ const BaseStemmer = function() { return false; }; + /** + * @param {string} s + * @return {boolean} + */ this.eq_s = function(s) { + /** @protected */ if (this.limit - this.cursor < s.length) return false; if (this.current.slice(this.cursor, this.cursor + s.length) != s) { @@ -83,8 +136,13 @@ const BaseStemmer = function() { return true; }; + /** + * @param {string} s + * @return {boolean} + */ this.eq_s_b = function(s) { + /** @protected */ if (this.cursor - this.limit_backward < s.length) return false; if (this.current.slice(this.cursor - s.length, this.cursor) != s) { @@ -94,8 +152,13 @@ const BaseStemmer = function() { return true; }; - /** @return {number} */ this.find_among = function(v) + /** + * @param {Among[]} v + * @return {number} + */ + this.find_among = function(v) { + /** @protected */ var i = 0; var j = v.length; @@ -165,8 +228,13 @@ const BaseStemmer = function() { }; // find_among_b is for backwards processing. Same comments apply + /** + * @param {Among[]} v + * @return {number} + */ this.find_among_b = function(v) { + /** @protected */ var i = 0; var j = v.length @@ -232,8 +300,15 @@ const BaseStemmer = function() { /* to replace chars between c_bra and c_ket in this.current by the * chars in s. */ + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + * @return {number} + */ this.replace_s = function(c_bra, c_ket, s) { + /** @protected */ var adjustment = s.length - (c_ket - c_bra); this.current = this.current.slice(0, c_bra) + s + this.current.slice(c_ket); this.limit += adjustment; @@ -242,8 +317,12 @@ const BaseStemmer = function() { return adjustment; }; + /** + * @return {boolean} + */ this.slice_check = function() { + /** @protected */ if (this.bra < 0 || this.bra > this.ket || this.ket > this.limit || @@ -254,8 +333,13 @@ const BaseStemmer = function() { return true; }; + /** + * @param {number} c_bra + * @return {boolean} + */ this.slice_from = function(s) { + /** @protected */ var result = false; if (this.slice_check()) { @@ -265,20 +349,34 @@ const BaseStemmer = function() { return result; }; + /** + * @return {boolean} + */ this.slice_del = function() { + /** @protected */ return this.slice_from(""); }; + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + */ this.insert = function(c_bra, c_ket, s) { + /** @protected */ var adjustment = this.replace_s(c_bra, c_ket, s); if (c_bra <= this.bra) this.bra += adjustment; if (c_bra <= this.ket) this.ket += adjustment; }; + /** + * @return {string} + */ this.slice_to = function() { + /** @protected */ var result = ''; if (this.slice_check()) { @@ -287,8 +385,12 @@ const BaseStemmer = function() { return result; }; + /** + * @return {string} + */ this.assign_to = function() { + /** @protected */ return this.current.slice(0, this.limit); }; };