Skip to content

Commit

Permalink
[Prettify] Prototype
Browse files Browse the repository at this point in the history
Intentionally de-formatted
  • Loading branch information
PeyTy committed Jan 5, 2025
1 parent 27f5760 commit 8bbfe7a
Showing 1 changed file with 273 additions and 0 deletions.
273 changes: 273 additions & 0 deletions source/server/prettify.hexa
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)
}
}
}

0 comments on commit 8bbfe7a

Please sign in to comment.