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)
+ }
+ }
+}