/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
package stdx.syntax
import std.collection.{ArrayList, ArrayStack}
import std.sync.AtomicUInt64
private let SyntaxNodeImplID = AtomicUInt64(0)
// The base of NonTerminal (Non-Leaf node) and Terminal (Leaf node)
abstract class SyntaxNodeImpl {
let kind: SyntaxNodeKind
let preWhiteSpace: Array<SyntaxNodeImpl>
let virtualParent: Option<SyntaxNodeImpl> = None
let id: UInt64
init(kind: SyntaxNodeKind, preWhiteSpace: Array<SyntaxNodeImpl>) {
this.kind = kind
this.preWhiteSpace = preWhiteSpace
this.id = SyntaxNodeImplID.load()
SyntaxNodeImplID.fetchAdd(1)
}
prop offset: Offset {
get() {
return getOffset()
}
}
protected func toString(): String
protected func getOffset(): Offset
protected func preWhiteSpaceString(): String {
var res = ""
for (node in preWhiteSpace) {
res += node.toString()
}
res
}
protected func flatter(): Array<SyntaxNodeImpl>
}
// NonTerminal: Non-Leaf node
class NonTerminal <: SyntaxNodeImpl {
let children: Array<SyntaxNodeImpl>
init(kind: SyntaxNodeKind, children: Array<SyntaxNodeImpl>, preWhiteSpace!: Array<SyntaxNodeImpl> = []) {
super(kind, preWhiteSpace)
this.children = children
}
protected func toString(): String {
let stack = ArrayStack<SyntaxNodeImpl>()
stack.add(this)
let sb = StringBuilder()
while (let Some(node) <- stack.remove()) {
match (node) {
case nt: NonTerminal =>
let size = nt.children.size
for (i in (size - 1)..=0 : -1) {
stack.add(nt.children[i])
}
case t: Terminal => sb.append(t.toString())
case _ => "Unknown"
}
}
sb.toString()
}
protected func getOffset(): Offset {
let offset = Offset()
for (child in children) {
offset.moveNode(child)
}
return offset
}
protected func flatter(): Array<SyntaxNodeImpl> {
match (kind) {
// Handle NonTerminal node that converts to a single Token
case SyntaxNodeKind.RuneLiteral | SyntaxNodeKind.LineStringLiteral | SyntaxNodeKind.MultiLineStringLiteral
| SyntaxNodeKind.MultiLineRawStringLiteral => return [this]
case _ => ()
}
let res = ArrayList<SyntaxNodeImpl>()
for (child in children) {
res.add(all: child.flatter())
}
return res.toArray()
}
}
// Terminal: Leaf node
open class Terminal <: SyntaxNodeImpl {
init(kind: SyntaxNodeKind, preWhiteSpace!: Array<SyntaxNodeImpl> = []) {
super(kind, preWhiteSpace)
}
protected open func toString(): String {
kind.toString()
}
protected open func getOffset(): Offset {
return Offset(0, Int32(toString().size))
}
protected open func flatter(): Array<SyntaxNodeImpl> {
[this]
}
}
@When[os != "Windows"]
const NEWLINE = "\n"
@When[os == "Windows"]
const NEWLINE = "\r\n"
@When[os != "Windows"]
const SEPARATOR = "/"
@When[os == "Windows"]
const SEPARATOR = "\\"
// Including space, newline, tab, # token with repeat times
class RepeatedTerminal <: Terminal {
let repeat: Int64
init(kind: SyntaxNodeKind, repeat: Int64) {
super(kind)
this.repeat = repeat
}
protected func toString(): String {
match (this.kind) {
case SyntaxNodeKind.SpaceToken => " " * repeat
case SyntaxNodeKind.NewlineToken => NEWLINE * repeat
case SyntaxNodeKind.HashToken => "#" * repeat
case _ => ""
}
}
protected func getOffset(): Offset {
match (this.kind) {
case SyntaxNodeKind.NewlineToken => return Offset(Int32(repeat), 1)
case _ => return Offset(0, Int32(repeat))
}
}
}
class ValuedTerminal <: Terminal {
let value: String
init(kind: SyntaxNodeKind, value: String, preWhiteSpace!: Array<SyntaxNodeImpl> = []) {
super(kind, preWhiteSpace: preWhiteSpace)
this.value = value
}
protected func toString(): String {
value
}
protected func getOffset(): Offset {
var offset = Offset()
var strSplit = value.replace("\r\n", "\n").split("\n")
if (strSplit.size > 1) {
offset.line += Int32(strSplit.size) - 1
offset.column = Int32(strSplit[strSplit.size - 1].toRuneArray().size + 1)
} else {
offset.column += Int32(value.toRuneArray().size)
}
return offset
}
}
class TokenTerminal <: Terminal {
let tokenKind: TokenKind
let value: String
let hasEscape: Bool
init(tokenKind: TokenKind, value: String, hasEscape!: Bool = false) {
super(SyntaxNodeKind.Token)
this.tokenKind = tokenKind
this.value = value
this.hasEscape = hasEscape
}
protected func toString(): String {
if (hasEscape) {
"\\" + value
} else {
value
}
}
}
// Optimization Points only create one Terminal when create same class
// The builder of SyntaxNodeImpl node
/**
* The builder of SyntaxNodeImpl node.
*
* Optimization Points:
* 1. Cache some terminals
*/
class SyntaxNodeBuilder {
private static let invalid = Terminal(SyntaxNodeKind.Invalid)
func buildBasicTerminal(kind: SyntaxNodeKind): Terminal {
Terminal(kind)
}
func buildInvalidTerminal(): Terminal {
SyntaxNodeBuilder.invalid
}
func buildNewline(repeat: Int64): RepeatedTerminal {
RepeatedTerminal(SyntaxNodeKind.NewlineToken, repeat)
}
func buildSpace(repeat: Int64): RepeatedTerminal {
RepeatedTerminal(SyntaxNodeKind.SpaceToken, repeat)
}
func buildHash(repeat: Int64): RepeatedTerminal {
RepeatedTerminal(SyntaxNodeKind.HashToken, repeat)
}
func buildValuedTerminal(kind: SyntaxNodeKind, value: String, preChildren!: Array<SyntaxNodeImpl> = []): ValuedTerminal {
ValuedTerminal(kind, value, preWhiteSpace: preChildren)
}
func buildTokenTerminal(kind: TokenKind, value: String, hasEscape!: Bool = false): TokenTerminal {
TokenTerminal(kind, value, hasEscape: hasEscape)
}
func buildNonTerminal(kind: SyntaxNodeKind, children: Array<SyntaxNodeImpl>,
preChildren!: Array<SyntaxNodeImpl> = []): NonTerminal {
NonTerminal(kind, children, preWhiteSpace: preChildren)
}
func buildQuote(isSingle: Bool): Terminal {
if (isSingle) {
buildBasicTerminal(SyntaxNodeKind.SingleQuoteToken)
} else {
buildBasicTerminal(SyntaxNodeKind.DoubleQuoteToken)
}
}
func buildTripleQuote(isSingle: Bool): Terminal {
if (isSingle) {
buildBasicTerminal(SyntaxNodeKind.TripleSingleQuoteToken)
} else {
buildBasicTerminal(SyntaxNodeKind.TripleDoubleQuoteToken)
}
}
}