UNPKG

pretty-gd-js

Version:

A library that makes GDScript code look pretty.

478 lines (427 loc) 14.7 kB
export default class Prettifier { indent_str = "\t" tab_size = 4 input = "" pos = 0 first_words = [] last_token = "" prettify(_input) { this.reset(_input) let output = "" let min_indent = 0 let max_indent = 80 while (!this.is_eof()) { let line = this.read_line(min_indent, max_indent) if (line._gdscript_strip_edges()) { for (let first_token of this.first_words) { if (this.doubleblank._gdscript_has(first_token)) { let i = output._gdscript_rfind("\n\n") if (i > 0) output = output._gdscript_substr(0, i) + "\n" + output._gdscript_substr(i) } } min_indent = 0 max_indent = ceil(this.space_size(line) / this.tab_size) + 2 output += line + "\n" if (this.last_token === ":") { max_indent += - 1 min_indent = max_indent } } else if (!output._gdscript_ends_with("\n\n")) { min_indent = 0 output += "\n" } } return output._gdscript_strip_edges(false, true) } reset(_input = this.input, _indent_str = this.indent_str, _tab_size = this.tab_size, _pos = 0) { this.input = "" + _input this.indent_str = _indent_str this.tab_size = _tab_size this.pos = _pos if (!this.indent_str) this.indent_str = "\t" this.tab_size = this.space_size(this.indent_str) this.first_words = [] this.last_token = "" } read_line(min_indent = 0, max_indent = 10) { let line = this.read_whitespace() this.first_words = [] this.last_token = "" if (this.is_eol()) return this.read()._gdscript_strip_edges() let indent = clamp(ceil(this.space_size(line) / float(this.tab_size)), min_indent, max_indent) line = "" for (let i of range(indent)) { line += this.indent_str } let tokens = ["", "", ""] while (!this.is_eol()) { tokens._gdscript_push_back(this.read_token()) while (tokens._gdscript_size() > 3) tokens._gdscript_pop_front() line += this.between(tokens[0], tokens[1], tokens[2]) + tokens._gdscript_back() } this.first_words = this.get_first_words(line) line += this.read() return line._gdscript_strip_edges(false, true) } read_token() { let token = "" this.read_whitespace() if (this.peek() === "\n") { token += this.read() + this.read_whitespace() } else if (this.longoperators._gdscript_has(this.peek(4))) { token += this.read(4) } else if (this.longoperators._gdscript_has(this.peek(3))) { token += this.read(3) } else if (this.longoperators._gdscript_has(this.peek(2))) { token += this.read(2) } else if (this.peek() === "#") { token += this.read_until("\n") } else if (this.peek() === "@" && this.identifier._gdscript_containsn(this.peek(1, 1))) { token += this.read() + this.read_while(this.identifier) } else if (this.peek() === "." && this.number._gdscript_containsn(this.peek(1, 1))) { token += this.read_number() } else if (this.number._gdscript_containsn(this.peek())) { token += this.read_number() } else if (this.string._gdscript_containsn(this.peek()) && this.quote._gdscript_containsn(this.peek(1, 1))) { token += this.read_string() } else if (this.quote._gdscript_containsn(this.peek())) { token += this.read_string() } else if (this.identifier._gdscript_containsn(this.peek())) { token += this.read_while(this.identifier) } else if (this.node._gdscript_containsn(this.peek())) { token += this.read_node() } else { token += this.read() } this.read_whitespace() if (!token._gdscript_begins_with("#")) { this.last_token = token } return token } read_node() { let token = this.read()._gdscript_to_lower() if (this.quote._gdscript_containsn(this.peek())) { token += this.read_string() } else { token += this.read_while(this.nodepath) } return token } read_string() { let token = "" let quot = this.read()._gdscript_to_lower() if (this.string._gdscript_containsn(quot)) { token += quot quot = this.read() } if (this.peek(2) === quot + quot) { quot += this.read(2) } token += quot while (!this.is_eof() && this.peek(quot._gdscript_length()) != quot) { if (this.peek() === "\\") token += this.read(2) else token += this.read(1) } token += this.read(quot._gdscript_length()) return token } read_number() { let token = "" let reg = this.peek() while (reg._gdscript_containsn(this.peek())) { token += this.read()._gdscript_to_lower() if (token === ".") token = "0." if (token === "0") { reg = ".0123456789_bex" if ("0123456789"._gdscript_containsn(this.peek())) { token = "" } } else if (token._gdscript_begins_with("0x")) reg = "0123456789_abcdef" else if (token._gdscript_begins_with("0b")) reg = "01_" else if (token._gdscript_ends_with("e")) reg = "+-0123456789_" else if (token._gdscript_containsn("e")) reg = "0123456789_" else if (token._gdscript_containsn(".")) reg = "0123456789_e" else reg = ".0123456789_e" } if (token === "0x") token += "0" else if (token === "0b") token += "0" else if (token._gdscript_ends_with(".")) token += "0" else if (token._gdscript_ends_with("e")) token += "0" else if (token._gdscript_ends_with("-")) token += "0" else if (token._gdscript_ends_with("+")) token += "0" return token } read_word() { let token = "" while (this.peek()._gdscript_strip_edges()) { token += this.read() } return token } read_whitespace() { let token = "" while (!this.is_eol() && !this.peek()._gdscript_strip_edges()) { token += this.read() } return token } read_while(charset) { let output = "" while (charset._gdscript_containsn(this.peek()) || (charset._gdscript_containsn("ø") && this.peek()._gdscript_unicode_at(0) > 127)) { output += this.read() } return output } read_until(delimiter) { let output = "" while (this.peek(delimiter._gdscript_length()) != delimiter && !this.is_eof()) { output += this.read() } return output } read(len = 1) { if (this.is_eof()) return "" this.pos += len return this.input._gdscript_substr(this.pos - len, len) } peek(len = 1, skip = 0) { if (this.is_eof()) return "" return this.input._gdscript_substr(this.pos + skip, len) } is_eol() { return this.is_eof() || this.peek() === "\n" } is_eof() { return this.pos >= this.input._gdscript_length() } is_keyword(token) { return this.keywords._gdscript_has(token) } is_identifier(token) { if (!token._gdscript_strip_edges()) return false if (this.is_keyword(token)) return false if (this.number._gdscript_containsn(token._gdscript_substr(0, 1))) return false for (let char of token) { if (!this.identifier._gdscript_containsn(char)) return false } return true } between(token0, token1, token2) { if (!token1) return "" if (!token2) return "" if (token2._gdscript_begins_with("#")) return " " if (this.signage._gdscript_containsn(token1)) { if (this.keywords._gdscript_has(token0)) return "" if (this.parens_end._gdscript_containsn(token0)) return " " if (this.quote._gdscript_containsn(token0._gdscript_right(1))) return " " if (this.identifier._gdscript_containsn(token0._gdscript_right(1))) return " " return "" } if (token1 === "{") return " " if (token2 === "}") return " " if (this.parens_start._gdscript_containsn(token1)) return "" if (this.longoperators._gdscript_has(token1)) return " " if (this.longoperators._gdscript_has(token2)) return " " if (this.operator._gdscript_containsn(token1)) return " " if (this.operator._gdscript_containsn(token2)) return " " if (this.comma._gdscript_containsn(token1)) return " " if (this.comma._gdscript_containsn(token2)) return "" if (this.keywords._gdscript_has(token1)) return " " if (this.keywords._gdscript_has(token2)) return " " if (this.parens._gdscript_containsn(token1)) return "" if (this.parens._gdscript_containsn(token2)) return "" if (this.identifier._gdscript_containsn(token1._gdscript_right(1))) return " " return "" } get_first_words(line) { return (line._gdscript_strip_edges()._gdscript_get_slice("#", 0)._gdscript_get_slice("'", 0)._gdscript_get_slice('"', 0)._gdscript_split(" ", false)) } space_size(whitespace) { if (!whitespace) return 0 if (!this.tab_size) this.tab_size = 4 let sum = 0 for (let char of whitespace) { if (char === "\n") { sum = 0 } else if (char === "\t") { sum += 1 while (sum % this.tab_size) sum += 1 } else if (char === " ") { sum += 1 } else { return sum } } return sum } keywords = ["if", "else", "elif", "for", "while", "break", "continue", "pass", "return", "class", "class_name", "extends", "is", "as", "signal", "static", "const", "enum", "var", "breakpoint", "yield", "in", "and", "or" ] doubleblank = ["class", "func"] longoperators = ["**", "<<", ">>", "==", "!=", ">=", "<=", "&&", "||", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "^=", "|=", "<<=", ">>=", ":=", "->" ] operator = "%&*+-/<=>?\\^|" string = "r&^" quote = "\"\'" node = "$%" comma = ",;:" parens_start = "([" parens = "([.])" parens_end = "]})" signage = "!+-" number = "0123456789" identifier = "0123456789_abcdefghijklmnopqrstuvwxyzø" nodepath = "%/" + this.identifier } // GDscript API function print(...params) { console.log(...params) } function assert(condition, message = "") { if (!condition) throw new Error("Assertion failed" + message ? (": " + message) : (".")) } function bool(from) { return !!from } function int(from) { return parseInt(from) } function float(from) { return parseFloat(from) } function str(...args) { return args.join("") } function clamp(value, min, max) { return Math.min(Math.max(value, min), max) } function ceil(x) { return Math.ceil(x) } function range(b, n, s = 1) { var arr = [] if (n === undefined) { n = b b = 0 } b = parseInt(b) n = parseInt(n) s = parseInt(s) if (!s) return arr for (let i = b; i < n; i += s) { arr.push(i) } return arr } function func(type, methodName, implementation) { if (type.prototype) type = type.prototype Object.defineProperty(type, `_gdscript_${methodName}`, { value: implementation, enumerable: false, writable: true, configurable: true }) } func(String, 'begins_with', function (text) { return this.slice(0, text.length) === text }) func(String, 'containsn', function (what) { if (!what) return false return this.toLowerCase().includes(what.toLowerCase()) }) func(String, 'ends_with', function (text) { if (text == "") return true return this.slice(0 - text.length) === text }) func(String, 'get_slice', function (delimiter, slice) { if (!delimiter) return "" return (this.includes(delimiter) ? this.split(delimiter)[slice] : this) || "" }) func(String, 'length', function () { return this.length }) func(String, 'rfind', function (what, from = -1) { if (!what) return -1 return this.lastIndexOf(what, from < 0 ? this.length + from : from) }) func(String, 'right', function (length) { if (!length) return "" return this.slice(length * -1) }) func(String, 'split', function (delimiter = "", allow_empty = true, maxsplit = 0) { let input = this let parts = [] if (!delimiter) allow_empty = false if (maxsplit < 1) maxsplit = Infinity while (maxsplit > 0 && input && input.includes(delimiter)) { let part = input.slice(0, input.indexOf(delimiter) || (delimiter ? 0 : 1)) if (part || allow_empty) { parts.push(part) maxsplit-- } input = input.slice((input.indexOf(delimiter) + delimiter.length) || 1) } if (input || allow_empty) parts.push(input) return parts }) func(String, 'strip_edges', function (left = true, right = true) { let v = this if (left) v = v.trimStart() if (right) v = v.trimEnd() return v }) func(String, 'substr', function (from, len = -1) { if (from < 0) return "" if (len == -1) len = Infinity return this.substr(from, len) }) func(String, 'to_lower', function () { return this.toLowerCase() }) func(String, 'unicode_at', function (at) { if (at < 0) return 0 return this.charCodeAt(at) }) func(Object, 'has', function (value) { return this[value] !== undefined }) func(Array, 'has', function (value) { return this.includes(value) }) func(Array, 'back', function () { return this.length ? this[this.length - 1] : null }) func(Array, 'pop_front', function () { return this.shift() }) func(Array, 'push_back', function (value) { this.push(value) }) func(Array, 'size', function () { return this.length }) func(JSON, 'stringify', function (data, indent = "", sort_keys = true, full_precision = false) { return JSON.stringify(data, null, indent) })