|
| 1 | +// The Hexa Compiler |
| 2 | +// Copyright (C) 2024-2025 Oleh Petrenko |
| 3 | +// |
| 4 | +// This program is free software: you can redistribute it and/or modify |
| 5 | +// it under the terms of the GNU Lesser General Public License as published by |
| 6 | +// the Free Software Foundation, version 3 of the License. |
| 7 | +// |
| 8 | +// This program is distributed in the hope that it will be useful, |
| 9 | +// but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | +// GNU Lesser General Public License for more details. |
| 12 | +// |
| 13 | +// You should have received a copy of the GNU Lesser General Public License |
| 14 | +// along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 15 | + |
| 16 | +/// Formatting by taking a syntax tree as input |
| 17 | +/// Preserves user personal style to some degree |
| 18 | +// TODO ^ |
| 19 | +class Prettify { |
| 20 | + new () { |
| 21 | + // Initializes state for nested blocks indentation |
| 22 | + } |
| 23 | + |
| 24 | + fun stringify(node Node) String { |
| 25 | + switch node { |
| 26 | + |
| 27 | + /// `super` |
| 28 | + case Super: return "super" |
| 29 | + |
| 30 | + /// `123n` |
| 31 | + /// `123u32` |
| 32 | + // TODO just add meta to Int or drop Int/Float entirely |
| 33 | + // TODO rename to Number and Meta to TokenKind |
| 34 | + case MetaInt(number, meta): |
| 35 | + return number.toString() + Meta.stringifyPostfix(meta) |
| 36 | + |
| 37 | + /// `declare Alias = Value` |
| 38 | + case TypeAlias(alias, value): |
| 39 | + |
| 40 | + /// `storage op= value` |
| 41 | + // TODO rename to AssignWith or CompoundAssign |
| 42 | + case AssignOp(storage, op, value): |
| 43 | + return this.stringify(storage) + ' ' + Token.stringify(op) + '= ' + this.stringify(value) |
| 44 | + |
| 45 | + /// { el[0] ... el[n] } |
| 46 | + // TODO `, commaSeparated Bool` for `{a,b}` and `{a:b,c} |
| 47 | + // TODO remove `:` here! |
| 48 | + case Block(el): |
| 49 | + // TODO depth! |
| 50 | + `{\n` + el.map(e => this.stringify(e)).join('\n') + `\n}` |
| 51 | + |
| 52 | + /// `if condition[0], ...condition[n] { then } [else { otherwise }]` |
| 53 | + case If(condition, then, otherwise, ternary): |
| 54 | + if ternary { |
| 55 | + return this.stringify(condition[0]) + ' ? ' + this.stringify(then) + ' : ' + this.stringify(otherwise) |
| 56 | + } |
| 57 | + |
| 58 | + return `if ` + condition.map(c => this.stringify(c)).join(`, `) + ` ` + this.stringify(then) + (otherwise ? ` else ` + this.stringify(otherwise) : ``) |
| 59 | + |
| 60 | + /// `expr expr` without `{}` |
| 61 | + case InlineStatements(el): |
| 62 | + |
| 63 | + /// `return e` |
| 64 | + case Return(e): |
| 65 | + if let e = e { |
| 66 | + return 'return ' + this.stringify(e) |
| 67 | + } else { |
| 68 | + return 'return' |
| 69 | + } |
| 70 | + |
| 71 | + /// `throw e` |
| 72 | + case Throw(e): |
| 73 | + return 'throw ' + this.stringify(e) |
| 74 | + |
| 75 | + /// `break` |
| 76 | + case Break: return "break" |
| 77 | + |
| 78 | + /// `continue` |
| 79 | + case Continue: return "continue" |
| 80 | + |
| 81 | + /// postfix ? `e op` : `op e` |
| 82 | + // TODO rename to `Unary` |
| 83 | + case Unop(op, postfix, e): |
| 84 | + if postfix { |
| 85 | + return this.stringify(e) + Token.stringify(op) |
| 86 | + } else { |
| 87 | + return Token.stringify(op) + this.stringify(e) |
| 88 | + } |
| 89 | + |
| 90 | + /// `while reason { e }` or if pre == true then `do { e } while reason` |
| 91 | + // TODO rename pre to isDoWhile |
| 92 | + case While(reason, e, pre): |
| 93 | + if pre { |
| 94 | + return 'do ' + this.stringify(e) + ' while ' + this.stringify(reason) |
| 95 | + } else { |
| 96 | + return 'while ' + this.stringify(reason) + ' ' + this.stringify(e) |
| 97 | + } |
| 98 | + |
| 99 | + |
| 100 | + /// `[declare] fun name<T>(vars) rettype { expr }` |
| 101 | + //ParametricFunction(name String?, expr Node, vars [Node], retType NodeType, external Bool, params [NodeType]?) |
| 102 | + |
| 103 | + /// `(vars) retType => expr` |
| 104 | + // TODO parser `: retType` is really needed at all? |
| 105 | + case Arrow(expr, vars, retType): |
| 106 | + if let retType = retType { |
| 107 | + return '(' + vars.map(v => this.stringify(v)).join(', ') + ') => ' + this.stringify(expr) + ': ' + NodeType.stringify(retType) |
| 108 | + } else { |
| 109 | + return '(' + vars.map(v => this.stringify(v)).join(', ') + ') => ' + this.stringify(expr) |
| 110 | + } |
| 111 | + |
| 112 | +// // var Var[0], ..., Var[n] |
| 113 | +// Vars(vars [Node]) |
| 114 | +// |
| 115 | + /// `external class t extends extend implements implement { fields }` |
| 116 | + // TODO IDE: hover over `(className)` in the pattern itself must show its type |
| 117 | + case Class(className, extend, implement, fields, external, kind): |
| 118 | + return 'class ' + className + ' ' + (extend ? 'extends ' + extend : '') + ' ' + (implement ? 'implements ' + implement : '') + ' {\n' + fields.map(f => this.stringify(f)).join('\n') + '\n}' |
| 119 | + |
| 120 | + /// var name T { get { return x } set (v) {} } |
| 121 | + // TODO .Var, .Function, .Function |
| 122 | + case Property(field, getter, setter): |
| 123 | + |
| 124 | + /// try { expr } catch v[0]: t[0] { catches[0] } ... catch v[n]: t[n] { catches[n] } |
| 125 | + case Try(expr, types, values, catches): |
| 126 | + return 'try ' + this.stringify(expr) + catches.map((v, i) => 'catch ' + this.stringify(v) + ': ' + NodeType.stringify(types[i]) + ' ' + this.stringify(catches[i])).join(' ') |
| 127 | + |
| 128 | + /// `new T<T> { } (args)` TODO new syntax |
| 129 | + case New(path, ofType, args, fields, el, argNames): |
| 130 | + return NodeType.stringify(ofType) + `(` + args.map(a => this.stringify(a)).join(', ') + `)` |
| 131 | + |
| 132 | + /// [keys[0] : values[0], ... keys[n] : values[n]] |
| 133 | + case Map(keys, values): |
| 134 | + return '[' + [for i in keys.length this.stringify(keys[i]) + ': ' + this.stringify(values[i])].join(', ') + ']' |
| 135 | + |
| 136 | + // TODO update names in this doc |
| 137 | + /// switch exprs[0], exprs[n] { |
| 138 | + /// case cases[0] if guards[0]: conditions[0] |
| 139 | + /// case cases[n]: conditions[n] |
| 140 | + /// } |
| 141 | + case Switch(inputs , expressions , guards , patterns ): |
| 142 | + return 'switch ' + [for i in inputs.length this.stringify(inputs[i])].join(', ') + ' {\n' + |
| 143 | + [for i in patterns.length 'case ' + this.stringify(patterns[i]) + ': ' + this.stringify(expressions[i])].join('\n') + '\n}' |
| 144 | + |
| 145 | + /// module path[0].path[1].r..path[n] { el } |
| 146 | + case Module(path , el ): |
| 147 | + |
| 148 | + /// Not present in syntax, used for typing |
| 149 | + case ModuleExports(handle ): |
| 150 | + |
| 151 | + /// import xxx as yyy in "test" |
| 152 | + case Import(el , path ): |
| 153 | + |
| 154 | + // TODO unify into `Node.Class` and rename to `ClassLike` or `Prototype` |
| 155 | + // ^ avoid `class` word to not look too much focused on OOP |
| 156 | + /// enum t : valuesType extends extend { fields[0] ... fields[n] } |
| 157 | + case Enum( |
| 158 | + enumType/*TODO uniq name classType etc for easy bind*/ , |
| 159 | + fields , |
| 160 | + valuesType , |
| 161 | + extend |
| 162 | + ): |
| 163 | + return 'enum ' + enumType + (valuesType ? ': ' + NodeType.stringify(valuesType) : '') + (extend ? ' extends ' + extend : '') + ' {\n' + fields.map(f => this.stringify(f)).join('\n') + '\n}' |
| 164 | + |
| 165 | + /// let expr(extract[0], ..., extract[n]) = name |
| 166 | + case EnumExtract(path , bind , expr ): |
| 167 | + |
| 168 | + // TODO |
| 169 | + // A.B |
| 170 | + // A.B(c, d) |
| 171 | + // A.B(c: d, d: d) |
| 172 | + case EnumConstructor: |
| 173 | + // TODO |
| 174 | + // case `B()` |
| 175 | + // case `B(c, d)` |
| 176 | + // case `B(c: d, d: d)` ? |
| 177 | + case EnumPattern: |
| 178 | + /// `expr is T` |
| 179 | + case Is(expr , aType ): |
| 180 | + return this.stringify(expr) + ' is ' + NodeType.stringify(aType) |
| 181 | + |
| 182 | + /// `expr as T` |
| 183 | + /// `expr as? T` |
| 184 | + /// `expr as! T` |
| 185 | + case As(expr , kind , toType ): |
| 186 | + return this.stringify(expr) + ' as' + Token.stringify(kind) + ' ' + NodeType.stringify(toType) |
| 187 | + |
| 188 | + /// `_` |
| 189 | + case Underscore: |
| 190 | + return "_" |
| 191 | + |
| 192 | + /// `private` field or type |
| 193 | + // TODO drop this |
| 194 | + case Private(field ): |
| 195 | + return "private " + this.stringify(field) |
| 196 | + |
| 197 | + /// for name in over { statement } |
| 198 | + /// for name in over ... range statement |
| 199 | + // for index : name in over statement // but no range |
| 200 | + // for key : name in over statement // same, just clarification for Map<> |
| 201 | + // ^ index is a Var for simple typer implementation |
| 202 | + case For(iterator , over , statement , range ): |
| 203 | + if let range = range { |
| 204 | + return 'for ' + iterator + ' in ' + this.stringify(over) + ' ... ' + this.stringify(range) + ' ' + this.stringify(statement) |
| 205 | + } |
| 206 | + return 'for ' + iterator + ' in ' + this.stringify(over) + ' ' + this.stringify(statement) |
| 207 | + // TODO `range Node?` |
| 208 | + //For(name String, over Node, by Node, range Node, index Node?) |
| 209 | + |
| 210 | + /// `nullable ?? alternative` |
| 211 | + case Elvis(nullable , alternative ): |
| 212 | + return this.stringify(nullable) + ' ?? ' + this.stringify(alternative) |
| 213 | + |
| 214 | + |
| 215 | + |
| 216 | + // Smaller ones |
| 217 | + case String(s): |
| 218 | + // TODO quote types + mirror special symbols |
| 219 | + let s = JSON.stringify(s) |
| 220 | + return "'\(s)'" |
| 221 | + case Ident(name): return name |
| 222 | + case Bool(b): return b ? "true": "false" |
| 223 | + case Int(s): return s.toString() |
| 224 | + case Float(s, meta): |
| 225 | + // TODO meta |
| 226 | + return s.toString() |
| 227 | + case Null: return "null" |
| 228 | + case This: return "this" |
| 229 | + case Parenthesis(expr): return "(" + this.stringify(expr) + ")" |
| 230 | + case Index(expr, index): return this.stringify(expr) + '[' + this.stringify(index) + ']' |
| 231 | + case Dot(expr, name): return this.stringify(expr) + '.' + name |
| 232 | + case DotUpper(expr, name): return this.stringify(expr) + '.' + name |
| 233 | + case Call(e, args, argNames): |
| 234 | + let arg = [] |
| 235 | + for i in args.length { |
| 236 | + if let name = argNames[i] { |
| 237 | + arg.push(name + ': ' + this.stringify(args [i])) |
| 238 | + } else { |
| 239 | + arg.push(this.stringify(args [i])) |
| 240 | + } |
| 241 | + } |
| 242 | + return this.stringify(e) + '(' + arg.join(', ') + ')' |
| 243 | + case Array(elements): return '[' + [for el in elements this.stringify(el)].join(', ') + ']' |
| 244 | + case Binop(a, op, b): return this.stringify(a) + ' ' + Token.stringify(op) + ' ' + this.stringify(b) |
| 245 | + case Object(names, el): |
| 246 | + return '{' + [for i in el.length names[i] + ': ' + this.stringify(el [i])].join(', ') + '}' |
| 247 | + case NodeTypeValue(t): |
| 248 | + // TODO full type |
| 249 | + return DataHelper.extractTypeName(t) |
| 250 | + case Static(f): return 'static ' + this.stringify(f) |
| 251 | + case Var(name, t, expr, const, external): { |
| 252 | + var body = '' |
| 253 | + if let expr = expr { |
| 254 | + body = ' = ' + this.stringify(expr) |
| 255 | + } |
| 256 | + return |
| 257 | + (external ? 'declare ': '') + |
| 258 | + (const ? 'let ': 'var ') + name + ' ' + NodeType.stringify(t) + body |
| 259 | + } |
| 260 | + case Function(name, body, vars, returnType, external, variadic): |
| 261 | + if name == null { |
| 262 | + // TODO `// Anonymous` |
| 263 | + return `fun` |
| 264 | + } |
| 265 | + |
| 266 | + return 'fun ' + name |
| 267 | + case _: |
| 268 | + // TODO exhaustive |
| 269 | + // console.error('stringify', node) |
| 270 | + return "..." + JSON.stringify(node) |
| 271 | + } |
| 272 | + } |
| 273 | +} |
0 commit comments