-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Intentionally de-formatted
- Loading branch information
Showing
1 changed file
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <https://www.gnu.org/licenses/>. | ||
|
||
/// 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<T>(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<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) | ||
} | ||
} | ||
} |