From 4fff99e320539a1472ce0512cd1fd3e5fc7f2ca6 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 16 Oct 2023 07:29:53 +0000 Subject: [PATCH] feat: add nasal language (#5342) * feat: add nasal language * fix: update nasal mode * fix: fix linter errors * feat: add nasal tokens --- demo/kitchen-sink/docs/nasal.nas | 7 + src/ext/modelist.js | 1 + src/mode/_test/tokens_nasal.json | 56 +++++ src/mode/nasal.js | 56 +++++ src/mode/nasal_highlight_rules.js | 359 ++++++++++++++++++++++++++++++ 5 files changed, 479 insertions(+) create mode 100644 demo/kitchen-sink/docs/nasal.nas create mode 100644 src/mode/_test/tokens_nasal.json create mode 100644 src/mode/nasal.js create mode 100644 src/mode/nasal_highlight_rules.js diff --git a/demo/kitchen-sink/docs/nasal.nas b/demo/kitchen-sink/docs/nasal.nas new file mode 100644 index 00000000000..daccc6b0bf1 --- /dev/null +++ b/demo/kitchen-sink/docs/nasal.nas @@ -0,0 +1,7 @@ +var sayHello = func(names, favorite) { + foreach (var name; names) { + printf("Hello %s, %s is the best!", name, favorite); + } +} + +sayHello(["World", "FlightGear"], "Nasal"); diff --git a/src/ext/modelist.js b/src/ext/modelist.js index b114c1e7975..be6151372bb 100644 --- a/src/ext/modelist.js +++ b/src/ext/modelist.js @@ -150,6 +150,7 @@ var supportedModes = { MIXAL: ["mixal"], MUSHCode: ["mc|mush"], MySQL: ["mysql"], + Nasal: ["nas"], Nginx: ["nginx|conf"], Nim: ["nim"], Nix: ["nix"], diff --git a/src/mode/_test/tokens_nasal.json b/src/mode/_test/tokens_nasal.json new file mode 100644 index 00000000000..e7ea0b5b6ec --- /dev/null +++ b/src/mode/_test/tokens_nasal.json @@ -0,0 +1,56 @@ +[[ + "start", + ["storage.type.nasal","var"], + ["text"," sayHello "], + ["keyword.operator.nasal","="], + ["text"," func(names, favorite) {"] +],[ + "start", + ["text"," "], + ["keyword.control.nasal","foreach"], + ["text"," ("], + ["storage.type.nasal","var"], + ["text"," name"], + ["punctuation.terminator.statement.nasal",";"], + ["text"," names) {"] +],[ + "start", + ["text"," "], + ["variable.language.nasal","printf"], + ["text","("], + ["punctuation.definition.string.begin.nasal","\""], + ["string.quoted.double.nasal","Hello "], + ["constant.character.escape.nasal","%s"], + ["string.quoted.double.nasal",", "], + ["constant.character.escape.nasal","%s"], + ["string.quoted.double.nasal"," is the best!"], + ["punctuation.definition.string.end.nasal","\""], + ["text",", name, favorite)"], + ["punctuation.terminator.statement.nasal",";"] +],[ + "start", + ["text"," }"] +],[ + "start", + ["text","}"] +],[ + "start" +],[ + "start", + ["text","sayHello(["], + ["punctuation.definition.string.begin.nasal","\""], + ["string.quoted.double.nasal","World"], + ["punctuation.definition.string.end.nasal","\""], + ["text",", "], + ["punctuation.definition.string.begin.nasal","\""], + ["string.quoted.double.nasal","FlightGear"], + ["punctuation.definition.string.end.nasal","\""], + ["text","], "], + ["punctuation.definition.string.begin.nasal","\""], + ["string.quoted.double.nasal","Nasal"], + ["punctuation.definition.string.end.nasal","\""], + ["text",")"], + ["punctuation.terminator.statement.nasal",";"] +],[ + "start" +]] \ No newline at end of file diff --git a/src/mode/nasal.js b/src/mode/nasal.js new file mode 100644 index 00000000000..fd838999f11 --- /dev/null +++ b/src/mode/nasal.js @@ -0,0 +1,56 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +/* + THIS FILE WAS AUTOGENERATED BY mode.tmpl.js +*/ + +"use strict"; + +var oop = require("../lib/oop"); +var TextMode = require("./text").Mode; +var NasalHighlightRules = require("./nasal_highlight_rules").NasalHighlightRules; +// TODO: pick appropriate fold mode +var FoldMode = require("./folding/cstyle").FoldMode; + +var Mode = function() { + this.HighlightRules = NasalHighlightRules; + this.foldingRules = new FoldMode(); +}; +oop.inherits(Mode, TextMode); + +(function() { + // this.lineCommentStart = ""//""; + // this.blockComment = {start: ""/*"", end: ""*/""}; + // Extra logic goes here. + this.$id = "ace/mode/nasal"; +}).call(Mode.prototype); + +exports.Mode = Mode; \ No newline at end of file diff --git a/src/mode/nasal_highlight_rules.js b/src/mode/nasal_highlight_rules.js new file mode 100644 index 00000000000..f94f8aea23a --- /dev/null +++ b/src/mode/nasal_highlight_rules.js @@ -0,0 +1,359 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2012, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +/* This file was autogenerated from https://github.com/BobDotCom/Nasal.tmbundle/blob/95113f60db7cb7ac7b6c3d854683773879407a48/Syntaxes/Nasal.tmLanguage (uuid: ) */ +/**************************************************************************************** + * IT MIGHT NOT BE PERFECT ...But it's a good start from an existing *.tmlanguage file. * + * fileTypes * + ****************************************************************************************/ + +"use strict"; + +var oop = require("../lib/oop"); +var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; + +var NasalHighlightRules = function() { + // regexp must not have capturing parentheses. Use (?:) instead. + // regexps are ordered -> the first match is used + + this.$rules = { + start: [{ + token: "constant.other.allcaps.nasal", + regex: /\b[[:upper:]_][[:upper:][:digit:]_]*\b(?![\.\(\'\"])/, + comment: "Match identifiers in ALL_CAPS as constants, except when followed by `.`, `(`, `'`, or `\"`." + }, { + todo: { + token: [ + "support.class.nasal", + "meta.function.nasal", + "entity.name.function.nasal", + "meta.function.nasal", + "keyword.operator.nasal", + "meta.function.nasal", + "storage.type.function.nasal", + "meta.function.nasal", + "punctuation.definition.parameters.begin.nasal" + ], + regex: /([a-zA-Z_?.$][\w?.$]*)(\.)([a-zA-Z_?.$][\w?.$]*)(\s*)(=)(\s*)(func)(\s*)(\()/, + push: [{ + token: "punctuation.definition.parameters.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + token: "variable.parameter.nasal", + regex: /\w/ + }, { + defaultToken: "meta.function.nasal" + }] + }, + comment: "match stuff like: Sound.play = func() { … }" + }, { + todo: { + token: [ + "entity.name.function.nasal", + "meta.function.nasal", + "keyword.operator.nasal", + "meta.function.nasal", + "storage.type.function.nasal", + "meta.function.nasal", + "punctuation.definition.parameters.begin.nasal" + ], + regex: /([a-zA-Z_?$][\w?$]*)(\s*)(=)(\s*)(func)(\s*)(\()/, + push: [{ + token: "punctuation.definition.parameters.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + token: "variable.parameter.nasal", + regex: /\w/ + }, { + defaultToken: "meta.function.nasal" + }] + }, + comment: "match stuff like: play = func() { … }" + }, { + todo: { + token: [ + "entity.name.function.nasal", + "meta.function.nasal", + "keyword.operator.nasal", + "meta.function.nasal", + "storage.type.function.nasal", + "meta.function.nasal", + "punctuation.definition.parameters.begin.nasal" + ], + regex: /([a-zA-Z_?$][\w?$]*)(\s*)(=)(\s*\(\s*)(func)(\s*)(\()/, + push: [{ + token: "punctuation.definition.parameters.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + token: "variable.parameter.nasal", + regex: /\w/ + }, { + defaultToken: "meta.function.nasal" + }] + }, + comment: "match stuff like: play = (func() { … }" + }, { + todo: { + token: [ + "entity.name.function.nasal", + "meta.function.hash.nasal", + "storage.type.function.nasal", + "meta.function.hash.nasal", + "punctuation.definition.parameters.begin.nasal" + ], + regex: /\b([a-zA-Z_?.$][\w?.$]*)(\s*:\s*\b)(func)(\s*)(\()/, + push: [{ + token: "punctuation.definition.parameters.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + token: "variable.parameter.nasal", + regex: /\w/ + }, { + defaultToken: "meta.function.hash.nasal" + }] + }, + comment: "match stuff like: foobar: func() { … }" + }, { + todo: { + token: [ + "storage.type.function.nasal", + "meta.function.nasal", + "punctuation.definition.parameters.begin.nasal" + ], + regex: /\b(func)(\s*)(\()/, + push: [{ + token: "punctuation.definition.parameters.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + token: "variable.parameter.nasal", + regex: /\w/ + }, { + defaultToken: "meta.function.nasal" + }] + }, + comment: "match stuff like: func() { … }" + }, { + token: [ + "keyword.operator.new.nasal", + "meta.class.instance.constructor", + "entity.name.type.instance.nasal" + ], + regex: /(new)(\s+)(\w+(?:\.\w*)?)/ + }, { + token: "keyword.control.nasal", + regex: /\b(?:if|else|elsif|while|for|foreach|forindex)\b/ + }, { + token: "keyword.control.nasal", + regex: /\b(?:break(?:\s+[A-Z]{2,16})?(?=\s*(?:;|\}))|continue(?:\s+[A-Z]{2,16})?(?=\s*(?:;|\}))|[A-Z]{2,16}(?=\s*;(?:[^\)#;]*?;){0,2}[^\)#;]*?\)))\b/ + }, { + token: "keyword.operator.nasal", + regex: /!|\*|\-|\+|~|\/|==|=|!=|<=|>=|<|>|!|\?|\:|\*=|\/=|\+=|\-=|~=|\.\.\.|\b(?:and|or)\b/ + }, { + token: "variable.language.nasal", + regex: /\b(?:me|arg|parents|obj)\b/ + }, { + token: "storage.type.nasal", + regex: /\b(?:return|var)\b/ + }, { + token: "constant.language.nil.nasal", + regex: /\bnil\b/ + }, { + token: "punctuation.definition.string.begin.nasal", + regex: /'/, + push: [{ + token: "punctuation.definition.string.end.nasal", + regex: /'/, + next: "pop" + }, { + token: "constant.character.escape.nasal", + regex: /\\'/ + }, { + defaultToken: "string.quoted.single.nasal" + }], + comment: "Single quoted strings" + }, { + token: "punctuation.definition.string.begin.nasal", + regex: /"/, + push: [{ + token: "punctuation.definition.string.end.nasal", + regex: /"/, + next: "pop" + }, { + token: "constant.character.escape.nasal", + regex: /\\(?:x[\da-fA-F]{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|r|n|t|\\|")/ + }, { + token: "constant.character.escape.nasal", + regex: /%(?:%|(?:\d+\$)?[+-]?(?:[ 0]|'.{1})?-?\d*(?:\.\d+)?[bcdeEufFgGosxX])/ + }, { + defaultToken: "string.quoted.double.nasal" + }], + comment: "Double quoted strings" + }, { + token: [ + "punctuation.definition.string.begin.nasal", + "string.other", + "punctuation.definition.string.end.nasal" + ], + regex: /(`)(.)(`)/, + comment: "Single-byte ASCII character constants" + }, { + token: [ + "punctuation.definition.comment.nasal", + "comment.line.hash.nasal" + ], + regex: /(#)(.*$)/, + comment: "Comments" + }, { + token: "constant.numeric.nasal", + regex: /(?:(?:\b[0-9]+)?\.)?\b[0-9]+(?:[eE][-+]?[0-9]+)?\b/, + comment: "Integers, floats, and scientific format" + }, { + token: "constant.numeric.nasal", + regex: /0[x|X][0-9a-fA-F]+/, + comment: "Hex codes" + }, { + token: "punctuation.terminator.statement.nasal", + regex: /\;/ + }, { + token: [ + "punctuation.section.scope.begin.nasal", + "punctuation.section.scope.end.nasal" + ], + regex: /(\[)(\])/ + }, { + todo: { + token: "punctuation.section.scope.begin.nasal", + regex: /\{/, + push: [{ + token: "punctuation.section.scope.end.nasal", + regex: /\}/, + next: "pop" + }, { + include: "$self" + }] + } + }, { + todo: { + token: "punctuation.section.scope.begin.nasal", + regex: /\(/, + push: [{ + token: "punctuation.section.scope.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }] + } + }, { + token: "invalid.illegal", + regex: /%|\$|@|&|\^|\||\\|`/, + comment: "Illegal characters" + }, { + todo: { + comment: "TODO: Symbols in hash keys" + }, + comment: "TODO: Symbols in hash keys" + }, { + token: "variable.language.nasal", + regex: /\b(?:append|bind|call|caller|chr|closure|cmp|compile|contains|delete|die|find|ghosttype|id|int|keys|left|num|pop|right|setsize|size|sort|split|sprintf|streq|substr|subvec|typeof|readline)\b/, + comment: "Core functions" + }, { + token: "variable.language.nasal", + regex: /\b(?:abort|abs|aircraftToCart|addcommand|airportinfo|airwaysRoute|assert|carttogeod|cmdarg|courseAndDistance|createDiscontinuity|createViaTo|createWP|createWPFrom|defined|directory|fgcommand|findAirportsByICAO|findAirportsWithinRange|findFixesByID|findNavaidByFrequency|findNavaidsByFrequency|findNavaidsByID|findNavaidsWithinRange|finddata|flightplan|geodinfo|geodtocart|get_cart_ground_intersection|getprop|greatCircleMove|interpolate|isa|logprint|magvar|maketimer|start|stop|restart|maketimestamp|md5|navinfo|parse_markdown|parsexml|print|printf|printlog|rand|registerFlightPlanDelegate|removecommand|removelistener|resolvepath|setlistener|_setlistener|setprop|srand|systime|thisfunc|tileIndex|tilePath|values)\b/, + comment: "FG ext core functions" + }, { + token: "variable.language.nasal", + regex: /\b(?:singleShot|isRunning|simulatedTime)\b/, + comment: "FG ext core functions" + }, { + token: "constant.language.nasal", + regex: /\b(?:D2R|FPS2KT|FT2M|GAL2L|IN2M|KG2LB|KT2FPS|KT2MPS|LG2GAL|LB2KG|M2FT|M2IN|M2NM|MPS2KT|NM2M|R2D)\b/, + comment: "FG ext core constants" + }, { + token: "support.function.nasal", + regex: /\b(?:addChild|addChildren|alias|clearValue|equals|getAliasTarget|getAttribute|getBoolValue|getChild|getChildren|getIndex|getName|getNode|getParent|getPath|getType|getValue|getValues|initNode|remove|removeAllChildren|removeChild|removeChildren|setAttribute|setBoolValue|setDoubleValue|setIntValue|setValue|setValues|unalias|compileCondition|condition|copy|dump|getNode|nodeList|runBinding|setAll|wrap|wrapNode)\b/, + comment: "FG func props" + }, { + token: "support.class.nasal", + regex: /\bNode\b/, + comment: "FG node class" + }, { + token: "variable.language.nasal", + regex: /\b(?:props|globals)\b/, + comment: "FG func props variables" + }, { + todo: { + token: [ + "support.function.nasal", + "punctuation.definition.arguments.begin.nasal" + ], + regex: /\b([a-zA-Z_?$][\w?$]*)(\()/, + push: [{ + token: "punctuation.definition.arguments.end.nasal", + regex: /\)/, + next: "pop" + }, { + include: "$self" + }, { + defaultToken: "meta.function-call.nasal" + }] + }, + comment: "function call" + }] + }; + + this.normalizeRules(); +}; + +NasalHighlightRules.metaData = { + fileTypes: ["nas"], + name: "Nasal", + scopeName: "source.nasal" +}; + + +oop.inherits(NasalHighlightRules, TextHighlightRules); + +exports.NasalHighlightRules = NasalHighlightRules; \ No newline at end of file