diff --git a/source/server/prettify.hexa b/source/server/prettify.hexa new file mode 100644 index 0000000..7ef4186 --- /dev/null +++ b/source/server/prettify.hexa @@ -0,0 +1,273 @@ +// The Hexa Compiler +// Copyright (C) 2024-2025 Oleh Petrenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +/// Formatting by taking a syntax tree as input +/// Preserves user personal style to some degree +// TODO ^ +class Prettify { + new () { + // Initializes state for nested blocks indentation + } + + fun stringify(node Node) String { + switch node { + + /// `super` + case Super: return "super" + + /// `123n` + /// `123u32` + // TODO just add meta to Int or drop Int/Float entirely + // TODO rename to Number and Meta to TokenKind + case MetaInt(number, meta): + return number.toString() + Meta.stringifyPostfix(meta) + + /// `declare Alias = Value` + case TypeAlias(alias, value): + + /// `storage op= value` + // TODO rename to AssignWith or CompoundAssign + case AssignOp(storage, op, value): + return this.stringify(storage) + ' ' + Token.stringify(op) + '= ' + this.stringify(value) + + /// { el[0] ... el[n] } + // TODO `, commaSeparated Bool` for `{a,b}` and `{a:b,c} + // TODO remove `:` here! + case Block(el): + // TODO depth! + `{\n` + el.map(e => this.stringify(e)).join('\n') + `\n}` + + /// `if condition[0], ...condition[n] { then } [else { otherwise }]` + case If(condition, then, otherwise, ternary): + if ternary { + return this.stringify(condition[0]) + ' ? ' + this.stringify(then) + ' : ' + this.stringify(otherwise) + } + + return `if ` + condition.map(c => this.stringify(c)).join(`, `) + ` ` + this.stringify(then) + (otherwise ? ` else ` + this.stringify(otherwise) : ``) + + /// `expr expr` without `{}` + case InlineStatements(el): + + /// `return e` + case Return(e): + if let e = e { + return 'return ' + this.stringify(e) + } else { + return 'return' + } + + /// `throw e` + case Throw(e): + return 'throw ' + this.stringify(e) + + /// `break` + case Break: return "break" + + /// `continue` + case Continue: return "continue" + + /// postfix ? `e op` : `op e` + // TODO rename to `Unary` + case Unop(op, postfix, e): + if postfix { + return this.stringify(e) + Token.stringify(op) + } else { + return Token.stringify(op) + this.stringify(e) + } + + /// `while reason { e }` or if pre == true then `do { e } while reason` + // TODO rename pre to isDoWhile + case While(reason, e, pre): + if pre { + return 'do ' + this.stringify(e) + ' while ' + this.stringify(reason) + } else { + return 'while ' + this.stringify(reason) + ' ' + this.stringify(e) + } + + + /// `[declare] fun name(vars) rettype { expr }` + //ParametricFunction(name String?, expr Node, vars [Node], retType NodeType, external Bool, params [NodeType]?) + + /// `(vars) retType => expr` + // TODO parser `: retType` is really needed at all? + case Arrow(expr, vars, retType): + if let retType = retType { + return '(' + vars.map(v => this.stringify(v)).join(', ') + ') => ' + this.stringify(expr) + ': ' + NodeType.stringify(retType) + } else { + return '(' + vars.map(v => this.stringify(v)).join(', ') + ') => ' + this.stringify(expr) + } + +// // var Var[0], ..., Var[n] +// Vars(vars [Node]) +// + /// `external class t extends extend implements implement { fields }` + // TODO IDE: hover over `(className)` in the pattern itself must show its type + case Class(className, extend, implement, fields, external, kind): + return 'class ' + className + ' ' + (extend ? 'extends ' + extend : '') + ' ' + (implement ? 'implements ' + implement : '') + ' {\n' + fields.map(f => this.stringify(f)).join('\n') + '\n}' + + /// var name T { get { return x } set (v) {} } + // TODO .Var, .Function, .Function + case Property(field, getter, setter): + + /// try { expr } catch v[0]: t[0] { catches[0] } ... catch v[n]: t[n] { catches[n] } + case Try(expr, types, values, catches): + return 'try ' + this.stringify(expr) + catches.map((v, i) => 'catch ' + this.stringify(v) + ': ' + NodeType.stringify(types[i]) + ' ' + this.stringify(catches[i])).join(' ') + + /// `new T { } (args)` TODO new syntax + case New(path, ofType, args, fields, el, argNames): + return NodeType.stringify(ofType) + `(` + args.map(a => this.stringify(a)).join(', ') + `)` + + /// [keys[0] : values[0], ... keys[n] : values[n]] + case Map(keys, values): + return '[' + [for i in keys.length this.stringify(keys[i]) + ': ' + this.stringify(values[i])].join(', ') + ']' + + // TODO update names in this doc + /// switch exprs[0], exprs[n] { + /// case cases[0] if guards[0]: conditions[0] + /// case cases[n]: conditions[n] + /// } + case Switch(inputs , expressions , guards , patterns ): + return 'switch ' + [for i in inputs.length this.stringify(inputs[i])].join(', ') + ' {\n' + + [for i in patterns.length 'case ' + this.stringify(patterns[i]) + ': ' + this.stringify(expressions[i])].join('\n') + '\n}' + + /// module path[0].path[1].r..path[n] { el } + case Module(path , el ): + + /// Not present in syntax, used for typing + case ModuleExports(handle ): + + /// import xxx as yyy in "test" + case Import(el , path ): + + // TODO unify into `Node.Class` and rename to `ClassLike` or `Prototype` + // ^ avoid `class` word to not look too much focused on OOP + /// enum t : valuesType extends extend { fields[0] ... fields[n] } + case Enum( + enumType/*TODO uniq name classType etc for easy bind*/ , + fields , + valuesType , + extend + ): + return 'enum ' + enumType + (valuesType ? ': ' + NodeType.stringify(valuesType) : '') + (extend ? ' extends ' + extend : '') + ' {\n' + fields.map(f => this.stringify(f)).join('\n') + '\n}' + + /// let expr(extract[0], ..., extract[n]) = name + case EnumExtract(path , bind , expr ): + + // TODO + // A.B + // A.B(c, d) + // A.B(c: d, d: d) + case EnumConstructor: + // TODO + // case `B()` + // case `B(c, d)` + // case `B(c: d, d: d)` ? + case EnumPattern: + /// `expr is T` + case Is(expr , aType ): + return this.stringify(expr) + ' is ' + NodeType.stringify(aType) + + /// `expr as T` + /// `expr as? T` + /// `expr as! T` + case As(expr , kind , toType ): + return this.stringify(expr) + ' as' + Token.stringify(kind) + ' ' + NodeType.stringify(toType) + + /// `_` + case Underscore: + return "_" + + /// `private` field or type + // TODO drop this + case Private(field ): + return "private " + this.stringify(field) + + /// for name in over { statement } + /// for name in over ... range statement + // for index : name in over statement // but no range + // for key : name in over statement // same, just clarification for Map<> + // ^ index is a Var for simple typer implementation + case For(iterator , over , statement , range ): + if let range = range { + return 'for ' + iterator + ' in ' + this.stringify(over) + ' ... ' + this.stringify(range) + ' ' + this.stringify(statement) + } + return 'for ' + iterator + ' in ' + this.stringify(over) + ' ' + this.stringify(statement) + // TODO `range Node?` + //For(name String, over Node, by Node, range Node, index Node?) + + /// `nullable ?? alternative` + case Elvis(nullable , alternative ): + return this.stringify(nullable) + ' ?? ' + this.stringify(alternative) + + + + // Smaller ones + case String(s): + // TODO quote types + mirror special symbols + let s = JSON.stringify(s) + return "'\(s)'" + case Ident(name): return name + case Bool(b): return b ? "true": "false" + case Int(s): return s.toString() + case Float(s, meta): + // TODO meta + return s.toString() + case Null: return "null" + case This: return "this" + case Parenthesis(expr): return "(" + this.stringify(expr) + ")" + case Index(expr, index): return this.stringify(expr) + '[' + this.stringify(index) + ']' + case Dot(expr, name): return this.stringify(expr) + '.' + name + case DotUpper(expr, name): return this.stringify(expr) + '.' + name + case Call(e, args, argNames): + let arg = [] + for i in args.length { + if let name = argNames[i] { + arg.push(name + ': ' + this.stringify(args [i])) + } else { + arg.push(this.stringify(args [i])) + } + } + return this.stringify(e) + '(' + arg.join(', ') + ')' + case Array(elements): return '[' + [for el in elements this.stringify(el)].join(', ') + ']' + case Binop(a, op, b): return this.stringify(a) + ' ' + Token.stringify(op) + ' ' + this.stringify(b) + case Object(names, el): + return '{' + [for i in el.length names[i] + ': ' + this.stringify(el [i])].join(', ') + '}' + case NodeTypeValue(t): + // TODO full type + return DataHelper.extractTypeName(t) + case Static(f): return 'static ' + this.stringify(f) + case Var(name, t, expr, const, external): { + var body = '' + if let expr = expr { + body = ' = ' + this.stringify(expr) + } + return + (external ? 'declare ': '') + + (const ? 'let ': 'var ') + name + ' ' + NodeType.stringify(t) + body + } + case Function(name, body, vars, returnType, external, variadic): + if name == null { + // TODO `// Anonymous` + return `fun` + } + + return 'fun ' + name + case _: + // TODO exhaustive + // console.error('stringify', node) + return "..." + JSON.stringify(node) + } + } +}