819 lines
24 KiB
Swift
819 lines
24 KiB
Swift
//
|
|
// MTMathList.swift
|
|
// MathRenderSwift
|
|
//
|
|
// Created by Mike Griebling on 2022-12-31.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// type defines spacing and how it is rendered
|
|
public enum MTMathAtomType: String, CustomStringConvertible {
|
|
case ordinary // number or text
|
|
case number // number
|
|
case variable // text in italic
|
|
case largeOperator // sin/cos, integral
|
|
case binaryOperator // \bin
|
|
case unaryOperator //
|
|
case relation // = < >
|
|
case open // open bracket
|
|
case close // close bracket
|
|
case fraction // \frac
|
|
case radical // \sqrt
|
|
case punctuation // ,
|
|
case placeholder // inner atom
|
|
case inner // embedded list
|
|
case underline // underlined atom
|
|
case overline // overlined atom
|
|
case accent // accented atom
|
|
|
|
// these atoms do not support subscripts/superscripts:
|
|
case boundary
|
|
case space
|
|
|
|
// Denotes style changes during randering
|
|
case style
|
|
case color
|
|
case colorBox
|
|
|
|
case table
|
|
|
|
func isNotBinaryOperator() -> Bool {
|
|
switch self {
|
|
case .binaryOperator, .relation, .open, .punctuation, .largeOperator: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
func isScriptAllowed() -> Bool {
|
|
return self != .boundary && self != .space && self != .style && self != .table
|
|
}
|
|
|
|
// we want string representations to be capitalized
|
|
public var description: String {
|
|
self.rawValue.capitalized
|
|
}
|
|
}
|
|
|
|
public enum MTFontStyle:Int {
|
|
/// The default latex rendering style. i.e. variables are italic and numbers are roman.
|
|
case defaultStyle = 0,
|
|
/// Roman font style i.e. \mathrm
|
|
roman,
|
|
/// Bold font style i.e. \mathbf
|
|
bold,
|
|
/// Caligraphic font style i.e. \mathcal
|
|
caligraphic,
|
|
/// Typewriter (monospace) style i.e. \mathtt
|
|
typewriter,
|
|
/// Italic style i.e. \mathit
|
|
italic,
|
|
/// San-serif font i.e. \mathss
|
|
sansSerif,
|
|
/// Fractur font i.e \mathfrak
|
|
fraktur,
|
|
/// Blackboard font i.e. \mathbb
|
|
blackboard,
|
|
/// Bold italic
|
|
boldItalic
|
|
}
|
|
|
|
public class MTMathAtom: NSObject, NSCopying {
|
|
|
|
public var type: MTMathAtomType
|
|
public var subScript: MTMathList?
|
|
public var superScript: MTMathList?
|
|
public var nucleus: String = ""
|
|
public var childAtoms = [MTMathAtom]() // atoms that fused to create this one
|
|
public var indexRange = NSRange(location: 0, length: 0) // indexRange in list that this atom tracks:
|
|
|
|
var fontStyle: MTFontStyle = .defaultStyle
|
|
var fusedAtoms: MTMathList?
|
|
|
|
public func copy(with zone: NSZone? = nil) -> Any {
|
|
let atom = MTMathAtom.atom(withType: type, value: nucleus)
|
|
atom.type = self.type
|
|
atom.nucleus = self.nucleus
|
|
atom.subScript = self.subScript?.copy() as? MTMathList
|
|
atom.superScript = self.subScript?.copy() as? MTMathList
|
|
atom.indexRange = self.indexRange
|
|
atom.fontStyle = self.fontStyle
|
|
return atom
|
|
}
|
|
|
|
public static func atom(withType type:MTMathAtomType, value:String) -> MTMathAtom {
|
|
switch type {
|
|
case .largeOperator:
|
|
return MTLargeOperator(value: value, limits: true)
|
|
case .fraction:
|
|
return MTFraction()
|
|
case .radical:
|
|
return MTRadical()
|
|
case .placeholder:
|
|
return MTMathAtom(type: type, value: UnicodeSymbol.whiteSquare)
|
|
case .inner:
|
|
return MTInner()
|
|
case .underline:
|
|
return MTUnderLine()
|
|
case .overline:
|
|
return MTOverLine()
|
|
case .accent:
|
|
return MTAccent(value: value)
|
|
case .space:
|
|
return MTMathSpace(space: 0)
|
|
case .color:
|
|
return MTMathColor()
|
|
case .colorBox:
|
|
return MTMathColorbox()
|
|
default:
|
|
return MTMathAtom(type: type, value: value)
|
|
}
|
|
}
|
|
|
|
public func setSuperScript(_ list: MTMathList?) {
|
|
if self.isScriptAllowed() {
|
|
self.superScript = list
|
|
} else {
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Superscripts not allowed for atom \(self.type.rawValue)").raise()
|
|
}
|
|
}
|
|
|
|
public func setSubScript(_ list: MTMathList?) {
|
|
if self.isScriptAllowed() {
|
|
self.subScript = list
|
|
} else {
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Subscripts not allowed for atom \(self.type.rawValue)").raise()
|
|
}
|
|
}
|
|
|
|
public override var description: String {
|
|
var string = ""
|
|
|
|
string += self.nucleus
|
|
if self.superScript != nil {
|
|
string += "^{\(self.superScript!.description)}"
|
|
}
|
|
if self.subScript != nil {
|
|
string += "_{\(self.subScript!.description)}"
|
|
}
|
|
|
|
return string
|
|
}
|
|
|
|
public var finalized: MTMathAtom {
|
|
let finalized = self
|
|
if finalized.superScript != nil {
|
|
finalized.superScript = finalized.superScript!.finalized
|
|
}
|
|
if finalized.subScript != nil {
|
|
finalized.subScript = finalized.subScript!.finalized
|
|
}
|
|
return finalized
|
|
}
|
|
|
|
public var string:String {
|
|
var str = self.nucleus
|
|
if let superScript = self.superScript {
|
|
str.append("^{\(superScript.string)}")
|
|
}
|
|
if let subScript = self.subScript {
|
|
str.append("_{\(subScript.string)}")
|
|
}
|
|
return str
|
|
}
|
|
|
|
func fuse(with atom: MTMathAtom) {
|
|
assert(self.subScript == nil, "Cannot fuse into an atom which has a subscript: \(self)");
|
|
assert(self.superScript == nil, "Cannot fuse into an atom which has a superscript: \(self)");
|
|
assert(atom.type == self.type, "Only atoms of the same type can be fused. \(self), \(atom)");
|
|
guard self.subScript == nil,
|
|
self.superScript == nil,
|
|
self.type == atom.type
|
|
else {
|
|
print("Can't fuse these 2 atoms")
|
|
return
|
|
}
|
|
|
|
self.childAtoms.append(self)
|
|
if atom.childAtoms.count > 0 {
|
|
self.childAtoms += atom.childAtoms
|
|
} else {
|
|
self.childAtoms.append(atom)
|
|
}
|
|
|
|
// Update nucleus:
|
|
self.nucleus += atom.nucleus
|
|
|
|
// Update range:
|
|
self.indexRange.length += atom.indexRange.length
|
|
|
|
// Update super/subscript:
|
|
self.superScript = atom.superScript
|
|
self.subScript = atom.subScript
|
|
}
|
|
|
|
func isScriptAllowed() -> Bool { self.type.isScriptAllowed() }
|
|
|
|
public init(type: MTMathAtomType, value: String) {
|
|
self.type = type
|
|
self.nucleus = value
|
|
}
|
|
|
|
func isNotBinaryOperator() -> Bool { self.type.isNotBinaryOperator() }
|
|
|
|
}
|
|
|
|
func isNotBinaryOperator(_ prevNode:MTMathAtom?) -> Bool {
|
|
if prevNode == nil { return true }
|
|
return prevNode!.type.isNotBinaryOperator()
|
|
}
|
|
|
|
public class MTFraction: MTMathAtom {
|
|
public var hasRule: Bool = true
|
|
public var leftDelimiter: String?
|
|
public var rightDelimiter: String?
|
|
public var numerator: MTMathList? = MTMathList()
|
|
public var denominator: MTMathList? = MTMathList()
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let frac = super.copy(with: zone) as! MTFraction
|
|
frac.numerator = self.numerator?.copy() as? MTMathList
|
|
frac.denominator = self.denominator?.copy() as? MTMathList
|
|
frac.hasRule = self.hasRule
|
|
frac.leftDelimiter = self.leftDelimiter
|
|
frac.rightDelimiter = self.rightDelimiter
|
|
return frac
|
|
}
|
|
|
|
override public var description: String {
|
|
var string = ""
|
|
if self.hasRule {
|
|
string += "\\atop"
|
|
} else {
|
|
string += "\\frac"
|
|
}
|
|
if self.leftDelimiter != nil {
|
|
string += "[\(self.leftDelimiter!)]"
|
|
}
|
|
if self.rightDelimiter != nil {
|
|
string += "[\(self.rightDelimiter!)]"
|
|
}
|
|
|
|
string += "{\(self.numerator?.description ?? "placeholder")}{\(self.denominator?.description ?? "placeholder")}"
|
|
|
|
if self.superScript != nil {
|
|
string += "^{\(self.superScript!.description)}"
|
|
}
|
|
if self.subScript != nil {
|
|
string += "_{\(self.subScript!.description)}"
|
|
}
|
|
|
|
return string
|
|
}
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTFraction = super.finalized as! MTFraction
|
|
|
|
finalized.numerator = finalized.numerator?.finalized
|
|
finalized.denominator = finalized.denominator?.finalized
|
|
|
|
return finalized
|
|
}
|
|
|
|
convenience init(hasRule: Bool = true) {
|
|
self.init(type: .fraction, value: "")
|
|
self.hasRule = hasRule
|
|
}
|
|
}
|
|
|
|
public class MTRadical: MTMathAtom {
|
|
// Under the roof
|
|
public var radicand: MTMathList? = MTMathList()
|
|
|
|
// Value on radical sign
|
|
public var degree: MTMathList?
|
|
|
|
convenience init() {
|
|
self.init(type: .radical, value: "")
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let rad = super.copy(with: zone) as! MTRadical
|
|
rad.radicand = self.radicand?.copy() as? MTMathList
|
|
rad.degree = self.degree?.copy() as? MTMathList
|
|
return rad
|
|
}
|
|
|
|
override public var description: String {
|
|
var string = "\\sqrt"
|
|
|
|
if self.degree != nil {
|
|
string += "[\(self.degree!.description)]"
|
|
}
|
|
|
|
if self.radicand != nil {
|
|
string += "{\(self.radicand?.description ?? "placeholder")}"
|
|
}
|
|
|
|
if self.superScript != nil {
|
|
string += "^{\(self.superScript!.description)}"
|
|
}
|
|
if self.subScript != nil {
|
|
string += "_{\(self.subScript!.description)}"
|
|
}
|
|
|
|
return string
|
|
}
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTRadical = super.finalized as! MTRadical
|
|
|
|
finalized.radicand = finalized.radicand?.finalized
|
|
finalized.degree = finalized.degree?.finalized
|
|
|
|
return finalized
|
|
}
|
|
}
|
|
|
|
public class MTLargeOperator: MTMathAtom {
|
|
public var limits: Bool = false
|
|
|
|
init(value: String, limits: Bool) {
|
|
super.init(type: .largeOperator, value: value)
|
|
self.limits = limits
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTLargeOperator
|
|
op.limits = self.limits
|
|
return op
|
|
}
|
|
}
|
|
|
|
// MARK: - MTInner
|
|
|
|
public class MTInner: MTMathAtom {
|
|
public var innerList: MTMathList?
|
|
public var leftBoundary: MTMathAtom? {
|
|
didSet {
|
|
if leftBoundary != nil && leftBoundary!.type != .boundary {
|
|
assertionFailure("Left boundary must be of type .boundary")
|
|
}
|
|
}
|
|
}
|
|
public var rightBoundary: MTMathAtom? {
|
|
didSet {
|
|
if rightBoundary != nil && rightBoundary!.type != .boundary {
|
|
assertionFailure("Right boundary must be of type .boundary")
|
|
}
|
|
}
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let inner = super.copy(with: zone) as! MTInner
|
|
inner.innerList = self.innerList?.copy() as? MTMathList
|
|
inner.leftBoundary = self.leftBoundary?.copy() as? MTMathAtom
|
|
inner.rightBoundary = self.rightBoundary?.copy() as? MTMathAtom
|
|
return inner
|
|
}
|
|
|
|
init() {
|
|
super.init(type: .inner, value: "")
|
|
}
|
|
|
|
public override convenience init(type: MTMathAtomType, value: String) {
|
|
if type == .inner {
|
|
self.init(); return
|
|
}
|
|
assertionFailure("MTInner(type:value:) cannot be called. Use MTInner() instead.")
|
|
self.init()
|
|
}
|
|
|
|
override public var description: String {
|
|
var string = "\\inner"
|
|
|
|
if self.leftBoundary != nil {
|
|
string += "[\(self.leftBoundary!.nucleus)]"
|
|
}
|
|
string += "{\(self.innerList!.description)}"
|
|
|
|
if self.rightBoundary != nil {
|
|
string += "[\(self.rightBoundary!.nucleus)]"
|
|
}
|
|
|
|
if self.superScript != nil {
|
|
string += "^{\(self.superScript!.description)}"
|
|
}
|
|
if self.subScript != nil {
|
|
string += "_{\(self.subScript!.description)}"
|
|
}
|
|
|
|
return string
|
|
}
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTInner = super.finalized as! MTInner
|
|
finalized.innerList = finalized.innerList?.finalized
|
|
return finalized
|
|
}
|
|
}
|
|
|
|
public class MTOverLine: MTMathAtom {
|
|
public var innerList: MTMathList?
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTOverLine = super.finalized as! MTOverLine
|
|
finalized.innerList = finalized.innerList?.finalized
|
|
return finalized
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTOverLine
|
|
op.innerList = self.innerList?.copy() as? MTMathList
|
|
return op
|
|
}
|
|
|
|
convenience init() {
|
|
self.init(type: .overline, value: "")
|
|
}
|
|
}
|
|
|
|
public class MTUnderLine: MTMathAtom {
|
|
public var innerList: MTMathList?
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTUnderLine = super.finalized as! MTUnderLine
|
|
finalized.innerList = finalized.innerList?.finalized
|
|
return finalized
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTUnderLine
|
|
op.innerList = self.innerList?.copy() as? MTMathList
|
|
return op
|
|
}
|
|
|
|
convenience init() {
|
|
self.init(type: .underline, value: "")
|
|
}
|
|
}
|
|
|
|
public class MTAccent: MTMathAtom {
|
|
public var innerList: MTMathList?
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTAccent = super.finalized as! MTAccent
|
|
finalized.innerList = finalized.innerList?.finalized
|
|
return finalized
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTAccent
|
|
op.innerList = self.innerList?.copy() as? MTMathList
|
|
return op
|
|
}
|
|
|
|
convenience init(value: String) {
|
|
self.init(type: .accent, value: value)
|
|
}
|
|
}
|
|
|
|
public class MTMathSpace: MTMathAtom {
|
|
public var space: CGFloat = 0
|
|
|
|
convenience init(space: CGFloat) {
|
|
self.init(type: .space, value: "")
|
|
self.space = space
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTMathSpace
|
|
op.space = self.space
|
|
return op
|
|
}
|
|
|
|
}
|
|
|
|
public enum MTLineStyle {
|
|
case display
|
|
case text
|
|
case script
|
|
case scriptOfScript
|
|
|
|
public func inc() -> MTLineStyle {
|
|
switch self {
|
|
case .display: return .text
|
|
case .text: return .script
|
|
case .script: return .scriptOfScript
|
|
case .scriptOfScript: return .display
|
|
}
|
|
}
|
|
|
|
public var isNotScript:Bool {
|
|
self == .display || self == .text
|
|
}
|
|
}
|
|
|
|
public class MTMathStyle: MTMathAtom {
|
|
public var style: MTLineStyle = .display
|
|
|
|
convenience init(style: MTLineStyle = .display) {
|
|
self.init(type: .style, value: "")
|
|
self.style = style
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTMathStyle
|
|
op.style = self.style
|
|
return op
|
|
}
|
|
}
|
|
|
|
public class MTMathColor: MTMathAtom {
|
|
public var colorString:String=""
|
|
public var innerList:MTMathList?
|
|
|
|
init() {
|
|
super.init(type: .color, value: "")
|
|
}
|
|
|
|
public override convenience init(type: MTMathAtomType, value: String) {
|
|
if type == .color {
|
|
self.init(); return
|
|
}
|
|
NSException(name: NSExceptionName("InvalidMethod"), reason: "MTMathColor(type:value) cannot be called. Use MTMathColor() instead.").raise()
|
|
self.init()
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTMathColor
|
|
op.colorString = self.colorString
|
|
op.innerList = self.innerList?.copy() as? MTMathList
|
|
return op
|
|
}
|
|
|
|
public override var string: String {
|
|
"\\color{\(self.colorString)}{\(self.innerList!.string)}"
|
|
}
|
|
}
|
|
|
|
public class MTMathColorbox: MTMathAtom {
|
|
public var colorString:String=""
|
|
public var innerList:MTMathList?
|
|
|
|
init() {
|
|
super.init(type: .color, value: "")
|
|
}
|
|
|
|
public override convenience init(type: MTMathAtomType, value: String) {
|
|
if type == .color {
|
|
self.init(); return
|
|
}
|
|
NSException(name: NSExceptionName("InvalidMethod"), reason: "MTMathColorbox(type:value) cannot be called. Use MTMathColorbox() instead.").raise()
|
|
self.init()
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTMathColorbox
|
|
op.colorString = self.colorString
|
|
op.innerList = self.innerList?.copy() as? MTMathList
|
|
return op
|
|
}
|
|
|
|
public override var string: String {
|
|
"\\colorbox{\(self.colorString)}{\(self.innerList!.string)}"
|
|
}
|
|
}
|
|
|
|
public enum MTColumnAlignment {
|
|
case left
|
|
case center
|
|
case right
|
|
}
|
|
|
|
public class MTMathTable: MTMathAtom {
|
|
public var alignments = [MTColumnAlignment]()
|
|
public var cells = [[MTMathList]]()
|
|
|
|
public var environment: String?
|
|
public var interColumnSpacing: CGFloat = 0
|
|
public var interRowAdditionalSpacing: CGFloat = 0
|
|
|
|
override public var finalized: MTMathAtom {
|
|
let finalized: MTMathTable = super.finalized as! MTMathTable
|
|
|
|
for var row in finalized.cells {
|
|
for i in 0..<row.count {
|
|
row[i] = row[i].finalized
|
|
}
|
|
}
|
|
|
|
return finalized
|
|
}
|
|
|
|
convenience init(environment: String? = nil) {
|
|
self.init(type: .table, value: "")
|
|
self.environment = environment
|
|
}
|
|
|
|
public override func copy(with zone: NSZone? = nil) -> Any {
|
|
let op = super.copy(with: zone) as! MTMathTable
|
|
op.interRowAdditionalSpacing = self.interRowAdditionalSpacing
|
|
op.interColumnSpacing = self.interColumnSpacing
|
|
op.environment = self.environment
|
|
var cellCopy = [[MTMathList]]()
|
|
cellCopy.reserveCapacity(self.cells.count)
|
|
for row in self.cells {
|
|
let newRow = [MTMathList](row)
|
|
cellCopy.append(newRow)
|
|
}
|
|
op.cells = cellCopy
|
|
return op
|
|
}
|
|
|
|
public func set(cell list: MTMathList, forRow row:Int, column:Int) {
|
|
if self.cells.count <= row {
|
|
for _ in self.cells.count...row {
|
|
self.cells.append([])
|
|
}
|
|
}
|
|
let rows = self.cells[row].count
|
|
if rows <= column {
|
|
for _ in rows...column {
|
|
self.cells[row].append(MTMathList())
|
|
}
|
|
}
|
|
self.cells[row][column] = list
|
|
}
|
|
|
|
public func set(alignment: MTColumnAlignment, forColumn col: Int) {
|
|
if self.alignments.count <= col {
|
|
for _ in self.alignments.count...col {
|
|
self.alignments.append(MTColumnAlignment.center)
|
|
}
|
|
}
|
|
|
|
self.alignments[col] = alignment
|
|
}
|
|
|
|
public func get(alignmentForColumn col: Int) -> MTColumnAlignment {
|
|
if self.alignments.count <= col {
|
|
return MTColumnAlignment.center
|
|
} else {
|
|
return self.alignments[col]
|
|
}
|
|
}
|
|
|
|
public var numColumns: Int {
|
|
var numberOfCols = 0
|
|
|
|
for row in self.cells {
|
|
numberOfCols = max(numberOfCols, row.count)
|
|
}
|
|
|
|
return numberOfCols
|
|
}
|
|
|
|
public var numRows: Int {
|
|
return self.cells.count
|
|
}
|
|
}
|
|
|
|
// represent list of math objects
|
|
extension MTMathList {
|
|
public override var description: String { self.atoms.description }
|
|
public var string: String { self.description }
|
|
}
|
|
|
|
public class MTMathList: NSObject, NSCopying {
|
|
|
|
public func copy(with zone: NSZone? = nil) -> Any {
|
|
let list = MTMathList()
|
|
list.atoms = [MTMathAtom](self.atoms)
|
|
return list
|
|
}
|
|
|
|
public var atoms = [MTMathAtom]()
|
|
|
|
public var finalized: MTMathList {
|
|
let finalizedList = MTMathList()
|
|
let zeroRange = NSMakeRange(0, 0)
|
|
|
|
var prevNode: MTMathAtom? = nil
|
|
for atom in self.atoms {
|
|
let newNode = atom.finalized
|
|
|
|
if NSEqualRanges(zeroRange, atom.indexRange) {
|
|
let index = prevNode == nil ? 0 : prevNode!.indexRange.location + prevNode!.indexRange.length
|
|
newNode.indexRange = NSMakeRange(index, 1)
|
|
}
|
|
|
|
switch newNode.type {
|
|
case .binaryOperator:
|
|
if prevNode == nil || prevNode!.isNotBinaryOperator() {
|
|
newNode.type = .unaryOperator
|
|
}
|
|
break
|
|
case .relation, .punctuation, .close:
|
|
if prevNode != nil &&
|
|
prevNode!.type == .binaryOperator {
|
|
prevNode!.type = .unaryOperator
|
|
}
|
|
break
|
|
case .number:
|
|
if prevNode != nil &&
|
|
prevNode!.type == .number &&
|
|
prevNode!.subScript == nil &&
|
|
prevNode!.superScript == nil {
|
|
prevNode!.fuse(with: newNode)
|
|
continue
|
|
}
|
|
break
|
|
default: break
|
|
}
|
|
|
|
finalizedList.add(newNode)
|
|
prevNode = newNode
|
|
}
|
|
|
|
if prevNode != nil && prevNode!.type == .binaryOperator {
|
|
prevNode!.type = .unaryOperator
|
|
finalizedList.removeLastAtom()
|
|
finalizedList.add(prevNode)
|
|
}
|
|
|
|
return finalizedList
|
|
}
|
|
|
|
public init(atoms: [MTMathAtom]) {
|
|
self.atoms.append(contentsOf: atoms)
|
|
}
|
|
|
|
public init(atom: MTMathAtom) {
|
|
self.atoms.append(atom)
|
|
}
|
|
|
|
public override init() {
|
|
self.atoms = []
|
|
}
|
|
|
|
func NSParamException(_ param:Any?) {
|
|
if param == nil {
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Parameter cannot be nil").raise()
|
|
}
|
|
}
|
|
|
|
func NSIndexException(_ array:[Any], index: Int) {
|
|
guard !array.indices.contains(index) else { return }
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Index \(index) out of bounds").raise()
|
|
}
|
|
|
|
func add(_ atom: MTMathAtom?) {
|
|
guard let atom = atom else { return }
|
|
if self.isAtomAllowed(atom) {
|
|
self.atoms.append(atom)
|
|
} else {
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Cannot add atom of type \(atom.type.rawValue) into mathlist").raise()
|
|
}
|
|
}
|
|
|
|
func insert(_ atom: MTMathAtom?, at index: Int) {
|
|
// NSParamException(atom)
|
|
guard let atom = atom else { return }
|
|
guard self.atoms.indices.contains(index) || index == self.atoms.endIndex else { return }
|
|
// guard self.atoms.endIndex >= index else { NSIndexException(); return }
|
|
if self.isAtomAllowed(atom) {
|
|
// NSIndexException(self.atoms, index: index)
|
|
self.atoms.insert(atom, at: index)
|
|
} else {
|
|
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Cannot add atom of type \(atom.type.rawValue) into mathlist").raise()
|
|
}
|
|
}
|
|
|
|
func append(_ list: MTMathList?) {
|
|
guard let list = list else { return }
|
|
self.atoms += list.atoms
|
|
}
|
|
|
|
func removeLastAtom() {
|
|
if self.atoms.count > 0 {
|
|
self.atoms.removeLast()
|
|
}
|
|
}
|
|
|
|
func removeAtom(at index: Int) {
|
|
NSIndexException(self.atoms, index:index)
|
|
self.atoms.remove(at: index)
|
|
}
|
|
|
|
func removeAtoms(in range: ClosedRange<Int>) {
|
|
NSIndexException(self.atoms, index: range.lowerBound)
|
|
NSIndexException(self.atoms, index: range.upperBound)
|
|
self.atoms.removeSubrange(range)
|
|
}
|
|
|
|
func isAtomAllowed(_ atom: MTMathAtom?) -> Bool {
|
|
return atom?.type != .boundary
|
|
}
|
|
}
|