Passed first set of builder tests.

This commit is contained in:
Michael Griebling
2023-01-07 07:53:46 -05:00
parent a5e6d37d0c
commit 9afc6970d4
6 changed files with 1354 additions and 475 deletions

View File

@@ -273,56 +273,56 @@ public class MTMathAtomFactory {
"amalg" : MTMathAtom(type: .binaryOperator, value: "\u{2A3F}"), "amalg" : MTMathAtom(type: .binaryOperator, value: "\u{2A3F}"),
// No limit operators // No limit operators
"log" : MTMathAtomFactory.getOperator(withName: "log", limits: false), "log" : MTMathAtomFactory.operatorWithName( "log", limits: false),
"lg" : MTMathAtomFactory.getOperator(withName: "lg", limits: false), "lg" : MTMathAtomFactory.operatorWithName( "lg", limits: false),
"ln" : MTMathAtomFactory.getOperator(withName: "ln", limits: false), "ln" : MTMathAtomFactory.operatorWithName( "ln", limits: false),
"sin" : MTMathAtomFactory.getOperator(withName: "sin", limits: false), "sin" : MTMathAtomFactory.operatorWithName( "sin", limits: false),
"arcsin" : MTMathAtomFactory.getOperator(withName: "arcsin", limits: false), "arcsin" : MTMathAtomFactory.operatorWithName( "arcsin", limits: false),
"sinh" : MTMathAtomFactory.getOperator(withName: "sinh", limits: false), "sinh" : MTMathAtomFactory.operatorWithName( "sinh", limits: false),
"cos" : MTMathAtomFactory.getOperator(withName: "cos", limits: false), "cos" : MTMathAtomFactory.operatorWithName( "cos", limits: false),
"arccos" : MTMathAtomFactory.getOperator(withName: "arccos", limits: false), "arccos" : MTMathAtomFactory.operatorWithName( "arccos", limits: false),
"cosh" : MTMathAtomFactory.getOperator(withName: "cosh", limits: false), "cosh" : MTMathAtomFactory.operatorWithName( "cosh", limits: false),
"tan" : MTMathAtomFactory.getOperator(withName: "tan", limits: false), "tan" : MTMathAtomFactory.operatorWithName( "tan", limits: false),
"arctan" : MTMathAtomFactory.getOperator(withName: "arctan", limits: false), "arctan" : MTMathAtomFactory.operatorWithName( "arctan", limits: false),
"tanh" : MTMathAtomFactory.getOperator(withName: "tanh", limits: false), "tanh" : MTMathAtomFactory.operatorWithName( "tanh", limits: false),
"cot" : MTMathAtomFactory.getOperator(withName: "cot", limits: false), "cot" : MTMathAtomFactory.operatorWithName( "cot", limits: false),
"coth" : MTMathAtomFactory.getOperator(withName: "coth", limits: false), "coth" : MTMathAtomFactory.operatorWithName( "coth", limits: false),
"sec" : MTMathAtomFactory.getOperator(withName: "sec", limits: false), "sec" : MTMathAtomFactory.operatorWithName( "sec", limits: false),
"csc" : MTMathAtomFactory.getOperator(withName: "csc", limits: false), "csc" : MTMathAtomFactory.operatorWithName( "csc", limits: false),
"arg" : MTMathAtomFactory.getOperator(withName: "arg", limits: false), "arg" : MTMathAtomFactory.operatorWithName( "arg", limits: false),
"ker" : MTMathAtomFactory.getOperator(withName: "ker", limits: false), "ker" : MTMathAtomFactory.operatorWithName( "ker", limits: false),
"dim" : MTMathAtomFactory.getOperator(withName: "dim", limits: false), "dim" : MTMathAtomFactory.operatorWithName( "dim", limits: false),
"hom" : MTMathAtomFactory.getOperator(withName: "hom", limits: false), "hom" : MTMathAtomFactory.operatorWithName( "hom", limits: false),
"exp" : MTMathAtomFactory.getOperator(withName: "exp", limits: false), "exp" : MTMathAtomFactory.operatorWithName( "exp", limits: false),
"deg" : MTMathAtomFactory.getOperator(withName: "deg", limits: false), "deg" : MTMathAtomFactory.operatorWithName( "deg", limits: false),
// Limit operators // Limit operators
"lim" : MTMathAtomFactory.getOperator(withName: "lim", limits: true), "lim" : MTMathAtomFactory.operatorWithName( "lim", limits: true),
"limsup" : MTMathAtomFactory.getOperator(withName: "lim sup", limits: true), "limsup" : MTMathAtomFactory.operatorWithName( "lim sup", limits: true),
"liminf" : MTMathAtomFactory.getOperator(withName: "lim inf", limits: true), "liminf" : MTMathAtomFactory.operatorWithName( "lim inf", limits: true),
"max" : MTMathAtomFactory.getOperator(withName: "max", limits: true), "max" : MTMathAtomFactory.operatorWithName( "max", limits: true),
"min" : MTMathAtomFactory.getOperator(withName: "min", limits: true), "min" : MTMathAtomFactory.operatorWithName( "min", limits: true),
"sup" : MTMathAtomFactory.getOperator(withName: "sup", limits: true), "sup" : MTMathAtomFactory.operatorWithName( "sup", limits: true),
"inf" : MTMathAtomFactory.getOperator(withName: "inf", limits: true), "inf" : MTMathAtomFactory.operatorWithName( "inf", limits: true),
"det" : MTMathAtomFactory.getOperator(withName: "det", limits: true), "det" : MTMathAtomFactory.operatorWithName( "det", limits: true),
"Pr" : MTMathAtomFactory.getOperator(withName: "Pr", limits: true), "Pr" : MTMathAtomFactory.operatorWithName( "Pr", limits: true),
"gcd" : MTMathAtomFactory.getOperator(withName: "gcd", limits: true), "gcd" : MTMathAtomFactory.operatorWithName( "gcd", limits: true),
// Large operators // Large operators
"prod" : MTMathAtomFactory.getOperator(withName: "\u{220F}", limits: true), "prod" : MTMathAtomFactory.operatorWithName( "\u{220F}", limits: true),
"coprod" : MTMathAtomFactory.getOperator(withName: "\u{2210}", limits: true), "coprod" : MTMathAtomFactory.operatorWithName( "\u{2210}", limits: true),
"sum" : MTMathAtomFactory.getOperator(withName: "\u{2211}", limits: true), "sum" : MTMathAtomFactory.operatorWithName( "\u{2211}", limits: true),
"int" : MTMathAtomFactory.getOperator(withName: "\u{222B}", limits: false), "int" : MTMathAtomFactory.operatorWithName( "\u{222B}", limits: false),
"oint" : MTMathAtomFactory.getOperator(withName: "\u{222E}", limits: false), "oint" : MTMathAtomFactory.operatorWithName( "\u{222E}", limits: false),
"bigwedge" : MTMathAtomFactory.getOperator(withName: "\u{22C0}", limits: true), "bigwedge" : MTMathAtomFactory.operatorWithName( "\u{22C0}", limits: true),
"bigvee" : MTMathAtomFactory.getOperator(withName: "\u{22C1}", limits: true), "bigvee" : MTMathAtomFactory.operatorWithName( "\u{22C1}", limits: true),
"bigcap" : MTMathAtomFactory.getOperator(withName: "\u{22C2}", limits: true), "bigcap" : MTMathAtomFactory.operatorWithName( "\u{22C2}", limits: true),
"bigcup" : MTMathAtomFactory.getOperator(withName: "\u{22C3}", limits: true), "bigcup" : MTMathAtomFactory.operatorWithName( "\u{22C3}", limits: true),
"bigodot" : MTMathAtomFactory.getOperator(withName: "\u{2A00}", limits: true), "bigodot" : MTMathAtomFactory.operatorWithName( "\u{2A00}", limits: true),
"bigoplus" : MTMathAtomFactory.getOperator(withName: "\u{2A01}", limits: true), "bigoplus" : MTMathAtomFactory.operatorWithName( "\u{2A01}", limits: true),
"bigotimes" : MTMathAtomFactory.getOperator(withName: "\u{2A02}", limits: true), "bigotimes" : MTMathAtomFactory.operatorWithName( "\u{2A02}", limits: true),
"biguplus" : MTMathAtomFactory.getOperator(withName: "\u{2A04}", limits: true), "biguplus" : MTMathAtomFactory.operatorWithName( "\u{2A04}", limits: true),
"bigsqcup" : MTMathAtomFactory.getOperator(withName: "\u{2A06}", limits: true), "bigsqcup" : MTMathAtomFactory.operatorWithName( "\u{2A06}", limits: true),
// Latex command characters // Latex command characters
"{" : MTMathAtom(type: .open, value: "{"), "{" : MTMathAtom(type: .open, value: "{"),
@@ -454,6 +454,40 @@ public class MTMathAtomFactory {
return nil return nil
} }
public static func fontNameForStyle(_ fontStyle:MTFontStyle) -> String {
switch fontStyle {
case .defaultStyle:
return "mathnormal";
case .roman:
return "mathrm";
case .bold:
return "mathbf";
case .fraktur:
return "mathfrak";
case .caligraphic:
return "mathcal";
case .italic:
return "mathit";
case .sansSerif:
return "mathsf";
case .blackboard:
return "mathbb";
case .typewriter:
return "mathtt";
case .boldItalic:
return "bm";
}
}
// Return an atom for times sign \times or * // Return an atom for times sign \times or *
public static func times() -> MTMathAtom { public static func times() -> MTMathAtom {
return MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication) return MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication)
@@ -511,43 +545,41 @@ public class MTMathAtomFactory {
- Chars with special meaning in latex: ^ _ { } \ - Chars with special meaning in latex: ^ _ { } \
All other characters will have a non-nil atom returned. All other characters will have a non-nil atom returned.
*/ */
public static func atom(for char: String) -> MTMathAtom? { public static func atom(forCharacter ch: Character) -> MTMathAtom? {
let atomCharacterSet = CharacterSet(charactersIn: UnicodeScalar(0x21)!...UnicodeScalar(0x7E)!) let chStr = String(ch)
if char.rangeOfCharacter(from: atomCharacterSet) != nil { switch chStr {
switch char { case "\\u{0410}"..."\\u{044F}":
return MTMathAtom(type: .ordinary, value: chStr)
case _ where ch.utf32Char < 0x0021 || ch.utf32Char > 0x007E:
return nil
case "$", "%", "#", "&", "~", "\'", "^", "_", "{", "}", "\\": case "$", "%", "#", "&", "~", "\'", "^", "_", "{", "}", "\\":
return nil return nil
case "(", "[": case "(", "[":
return MTMathAtom(type: .open, value: char) return MTMathAtom(type: .open, value: chStr)
case ")", "]", "!", "?": case ")", "]", "!", "?":
return MTMathAtom(type: .close, value: char) return MTMathAtom(type: .close, value: chStr)
case ",", ";": case ",", ";":
return MTMathAtom(type: .punctuation, value: char) return MTMathAtom(type: .punctuation, value: chStr)
case "=", ">", "<": case "=", ">", "<":
return MTMathAtom(type: .relation, value: char) return MTMathAtom(type: .relation, value: chStr)
case ":": case ":":
// Math colon is ratio. Regular colon is \colon // Math colon is ratio. Regular colon is \colon
return MTMathAtom(type: .relation, value: "\u{2236}") return MTMathAtom(type: .relation, value: "\u{2236}")
case "-": case "-":
return MTMathAtom(type: .binaryOperator, value: "\u{2212}") return MTMathAtom(type: .binaryOperator, value: "\u{2212}")
case "+", "*": case "+", "*":
return MTMathAtom(type: .binaryOperator, value: char) return MTMathAtom(type: .binaryOperator, value: chStr)
case ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": case ".", "0"..."9":
return MTMathAtom(type: .number, value: char) return MTMathAtom(type: .number, value: chStr)
case _ where case "a"..."z", "A"..."Z":
(char.rangeOfCharacter(from: CharacterSet.lowercaseLetters) != nil) || return MTMathAtom(type: .variable, value: chStr)
(char.rangeOfCharacter(from: CharacterSet.lowercaseLetters) != nil) :
return MTMathAtom(type: .variable, value: char)
case "\"", "/", "@", "`", "|": case "\"", "/", "@", "`", "|":
return MTMathAtom(type: .ordinary, value: char) return MTMathAtom(type: .ordinary, value: chStr)
default: default:
assert(false, "Unknown Character: \(char)") assertionFailure("Unknown ASCII character '\(ch)'. Should have been handled earlier.")
print("Unknown characters: \(char)")
return nil return nil
} }
} }
return nil
}
/** Returns a `MTMathList` with one atom per character in the given string. This function /** Returns a `MTMathList` with one atom per character in the given string. This function
does not do any LaTeX conversion or interpretation. It simply uses `atomForCharacter` to does not do any LaTeX conversion or interpretation. It simply uses `atomForCharacter` to
@@ -556,7 +588,7 @@ public class MTMathAtomFactory {
let list = MTMathList() let list = MTMathList()
for character in string { for character in string {
if let newAtom = atom(for: "\(character)") { if let newAtom = atom(forCharacter: character) {
list.add(newAtom) list.add(newAtom)
} }
} }
@@ -602,14 +634,14 @@ public class MTMathAtomFactory {
e.g. to define a symbol for "lcm" one can call: e.g. to define a symbol for "lcm" one can call:
`[MTMathAtomFactory addLatexSymbol:@"lcm" value:[MTMathAtomFactory operatorWithName:@"lcm" limits: false)]` */ `[MTMathAtomFactory addLatexSymbol:@"lcm" value:[MTMathAtomFactory operatorWithName:@"lcm" limits: false)]` */
public static func define(latexSymbol name: String, value: MTMathAtom) { public static func add(latexSymbol name: String, value: MTMathAtom) {
Self.sharedInstance.supportedLatexSymbols[name] = value Self.sharedInstance.supportedLatexSymbols[name] = value
Self.sharedInstance.textToLatexSymbolName[value.nucleus] = name Self.sharedInstance.textToLatexSymbolName[value.nucleus] = name
} }
/** Returns a large opertor for the given name. If limits is true, limits are set up on /** Returns a large opertor for the given name. If limits is true, limits are set up on
the operator and displyed differently. */ the operator and displyed differently. */
public static func getOperator(withName name: String, limits: Bool) -> MTLargeOperator { public static func operatorWithName(_ name: String, limits: Bool) -> MTLargeOperator {
return MTLargeOperator(value: name, limits: limits) return MTLargeOperator(value: name, limits: limits)
} }
@@ -617,7 +649,7 @@ public class MTMathAtomFactory {
such as `grave`, `hat` etc. If the name is not a recognized accent name, this such as `grave`, `hat` etc. If the name is not a recognized accent name, this
returns nil. The `innerList` of the returned `MTAccent` is nil. returns nil. The `innerList` of the returned `MTAccent` is nil.
*/ */
public static func getAccent(withName name: String) -> MTAccent? { public static func accent(withName name: String) -> MTAccent? {
if let accentValue = Self.accents[name] { if let accentValue = Self.accents[name] {
return MTAccent(value: accentValue) return MTAccent(value: accentValue)
} }
@@ -626,7 +658,7 @@ public class MTMathAtomFactory {
/** Returns the accent name for the given accent. This is the reverse of the above /** Returns the accent name for the given accent. This is the reverse of the above
function. */ function. */
public static func getName(of accent: MTAccent) -> String? { public static func accentName(_ accent: MTAccent) -> String? {
return Self.sharedInstance.accentValueToName[accent.nucleus] return Self.sharedInstance.accentValueToName[accent.nucleus]
} }
@@ -681,7 +713,7 @@ public class MTMathAtomFactory {
@note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some @note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some
matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms. matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms.
*/ */
public static func table(withEnvironment env: String?, rows: [[MTMathList]]) -> MTMathAtom? { public static func table(withEnvironment env: String?, rows: [[MTMathList]], error:inout NSError?) -> MTMathAtom? {
let table = MTMathTable(environment: env) let table = MTMathTable(environment: env)
for i in 0..<rows.count { for i in 0..<rows.count {
@@ -703,12 +735,12 @@ public class MTMathAtomFactory {
if env == nil { if env == nil {
table.interColumnSpacing = 0 table.interColumnSpacing = 0
table.interRowAdditionalSpacing = 1 table.interRowAdditionalSpacing = 1
for i in 0..<table.numberOfCols() { for i in 0..<table.numColumns {
table.set(alignment: .left, forCol: i) table.set(alignment: .left, forColumn: i)
} }
return table return table
} else { } else if let env = env {
if let delims = matrixEnvs[env!] { if let delims = matrixEnvs[env] {
table.environment = "matrix" table.environment = "matrix"
table.interRowAdditionalSpacing = 0 table.interRowAdditionalSpacing = 0
table.interColumnSpacing = 18 table.interColumnSpacing = 18
@@ -731,8 +763,11 @@ public class MTMathAtomFactory {
return table return table
} }
} else if env == "eqalign" || env == "split" || env == "aligned" { } else if env == "eqalign" || env == "split" || env == "aligned" {
if table.numberOfCols() != 2 { if table.numColumns != 2 {
print("\(env!) environment can only have 2 columns") let message = "\(env) environment can only have 2 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil return nil
} }
@@ -747,47 +782,56 @@ public class MTMathAtomFactory {
table.interRowAdditionalSpacing = 1 table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 0 table.interColumnSpacing = 0
table.set(alignment: .right, forCol: 0) table.set(alignment: .right, forColumn: 0)
table.set(alignment: .left, forCol: 1) table.set(alignment: .left, forColumn: 1)
return table return table
} else if env == "displaylines" || env == "gather" { } else if env == "displaylines" || env == "gather" {
if table.numberOfCols() != 1 { if table.numColumns != 1 {
print("\(env!) environment can only have 1 columns") let message = "\(env) environment can only have 1 column"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil return nil
} }
table.interRowAdditionalSpacing = 1 table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 0 table.interColumnSpacing = 0
table.set(alignment: .center, forCol: 0) table.set(alignment: .center, forColumn: 0)
return table return table
} else if env == "eqnarray" { } else if env == "eqnarray" {
if table.numberOfCols() != 3 { if table.numColumns != 3 {
print("\(env!) environment can only have 3 columns") let message = "\(env) environment can only have 3 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil return nil
} }
table.interRowAdditionalSpacing = 1 table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 18 table.interColumnSpacing = 18
table.set(alignment: .right, forCol: 0) table.set(alignment: .right, forColumn: 0)
table.set(alignment: .center, forCol: 1) table.set(alignment: .center, forColumn: 1)
table.set(alignment: .left, forCol: 2) table.set(alignment: .left, forColumn: 2)
return table return table
} else if env == "cases" { } else if env == "cases" {
if table.numberOfCols() != 2 { if table.numColumns != 2 {
print("\(env!) environment can only have 2 columns") let message = "cases environment can only have 2 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil return nil
} }
table.interRowAdditionalSpacing = 0 table.interRowAdditionalSpacing = 0
table.interColumnSpacing = 18 table.interColumnSpacing = 18
table.set(alignment: .left, forCol: 0) table.set(alignment: .left, forColumn: 0)
table.set(alignment: .left, forCol: 1) table.set(alignment: .left, forColumn: 1)
let style = MTMathStyle(style: .text) let style = MTMathStyle(style: .text)
@@ -807,9 +851,13 @@ public class MTMathAtomFactory {
return inner return inner
} else { } else {
print("Unknown table environment") let message = "Unknown environment \(env)"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil return nil
} }
} }
return nil
} }
} }

View File

@@ -78,7 +78,7 @@ public enum MTFontStyle:Int {
boldItalic boldItalic
} }
public class MTMathAtom: Any, CustomStringConvertible { public class MTMathAtom: CustomStringConvertible {
public var type: MTMathAtomType public var type: MTMathAtomType
public var subScript: MTMathList? public var subScript: MTMathList?
public var superScript: MTMathList? public var superScript: MTMathList?
@@ -271,10 +271,10 @@ public class MTFraction: MTMathAtom {
public class MTRadical: MTMathAtom { public class MTRadical: MTMathAtom {
// Under the roof // Under the roof
var radicand: MTMathList? = MTMathList() public var radicand: MTMathList? = MTMathList()
// Value on radical sign // Value on radical sign
var degree: MTMathList? public var degree: MTMathList?
convenience init() { convenience init() {
self.init(type: .radical, value: "") self.init(type: .radical, value: "")
@@ -312,7 +312,7 @@ public class MTRadical: MTMathAtom {
} }
public class MTLargeOperator: MTMathAtom { public class MTLargeOperator: MTMathAtom {
var limits: Bool = false public var limits: Bool = false
convenience init(value: String, limits: Bool = false) { convenience init(value: String, limits: Bool = false) {
self.init(type: .largeOperator, value: value) self.init(type: .largeOperator, value: value)
@@ -323,15 +323,15 @@ public class MTLargeOperator: MTMathAtom {
// MARK: - MTInner // MARK: - MTInner
public class MTInner: MTMathAtom { public class MTInner: MTMathAtom {
var innerList: MTMathList? public var innerList: MTMathList?
var leftBoundary: MTMathAtom? { public var leftBoundary: MTMathAtom? {
didSet { didSet {
if leftBoundary != nil && leftBoundary!.type != .boundary { if leftBoundary != nil && leftBoundary!.type != .boundary {
assertionFailure("Left boundary must be of type .boundary") assertionFailure("Left boundary must be of type .boundary")
} }
} }
} }
var rightBoundary: MTMathAtom? { public var rightBoundary: MTMathAtom? {
didSet { didSet {
if rightBoundary != nil && rightBoundary!.type != .boundary { if rightBoundary != nil && rightBoundary!.type != .boundary {
assertionFailure("Right boundary must be of type .boundary") assertionFailure("Right boundary must be of type .boundary")
@@ -383,7 +383,7 @@ public class MTInner: MTMathAtom {
} }
public class MTOverLine: MTMathAtom { public class MTOverLine: MTMathAtom {
var innerList: MTMathList? public var innerList: MTMathList?
override public var finalized: MTMathAtom { override public var finalized: MTMathAtom {
let finalized: MTOverLine = super.finalized as! MTOverLine let finalized: MTOverLine = super.finalized as! MTOverLine
@@ -399,7 +399,7 @@ public class MTOverLine: MTMathAtom {
} }
public class MTUnderLine: MTMathAtom { public class MTUnderLine: MTMathAtom {
var innerList: MTMathList? public var innerList: MTMathList?
override public var finalized: MTMathAtom { override public var finalized: MTMathAtom {
let finalized: MTUnderLine = super.finalized as! MTUnderLine let finalized: MTUnderLine = super.finalized as! MTUnderLine
@@ -415,7 +415,7 @@ public class MTUnderLine: MTMathAtom {
} }
public class MTAccent: MTMathAtom { public class MTAccent: MTMathAtom {
var innerList: MTMathList? public var innerList: MTMathList?
override public var finalized: MTMathAtom { override public var finalized: MTMathAtom {
let finalized: MTAccent = super.finalized as! MTAccent let finalized: MTAccent = super.finalized as! MTAccent
@@ -431,7 +431,7 @@ public class MTAccent: MTMathAtom {
} }
public class MTMathSpace: MTMathAtom { public class MTMathSpace: MTMathAtom {
var space: CGFloat = 0 public var space: CGFloat = 0
convenience init(space: CGFloat) { convenience init(space: CGFloat) {
self.init(type: .space, value: "") self.init(type: .space, value: "")
@@ -460,10 +460,10 @@ public enum MTLineStyle {
} }
public class MTMathStyle: MTMathAtom { public class MTMathStyle: MTMathAtom {
var style: MTLineStyle = .display public var style: MTLineStyle = .display
convenience init(style: MTLineStyle = .display) { convenience init(style: MTLineStyle = .display) {
self.init(type: .space, value: "") self.init(type: .style, value: "")
self.style = style self.style = style
} }
} }
@@ -517,14 +517,14 @@ public enum MTColumnAlignment {
} }
public class MTMathTable: MTMathAtom { public class MTMathTable: MTMathAtom {
var alignments = [MTColumnAlignment]() public var alignments = [MTColumnAlignment]()
var cells = [[MTMathList]]() public var cells = [[MTMathList]]()
var environment: String? public var environment: String?
var interColumnSpacing: CGFloat = 0 public var interColumnSpacing: CGFloat = 0
var interRowAdditionalSpacing: CGFloat = 0 public var interRowAdditionalSpacing: CGFloat = 0
var numColumns = 0 // public var numColumns = 0
var numRows = 0 // public var numRows = 0
override public var finalized: MTMathAtom { override public var finalized: MTMathAtom {
let finalized: MTMathTable = super.finalized as! MTMathTable let finalized: MTMathTable = super.finalized as! MTMathTable
@@ -543,22 +543,22 @@ public class MTMathTable: MTMathAtom {
self.environment = environment self.environment = environment
} }
func set(cell list: MTMathList, forRow row:Int, column:Int) { public func set(cell list: MTMathList, forRow row:Int, column:Int) {
if self.cells.count <= row { if self.cells.count <= row {
for _ in self.cells.count...row { for _ in self.cells.count...row {
self.cells.append([]) self.cells.append([])
} }
} }
var rowArray = self.cells[row] let rows = self.cells[row].count
if rowArray.count <= column { if rows <= column {
for _ in rowArray.count...column { for _ in rows...column {
rowArray.append(MTMathList()) self.cells[row].append(MTMathList())
} }
} }
rowArray[column] = list self.cells[row][column] = list
} }
func set(alignment: MTColumnAlignment, forCol col: Int) { public func set(alignment: MTColumnAlignment, forColumn col: Int) {
if self.alignments.count <= col { if self.alignments.count <= col {
for _ in self.alignments.count...col { for _ in self.alignments.count...col {
self.alignments.append(MTColumnAlignment.center) self.alignments.append(MTColumnAlignment.center)
@@ -568,7 +568,7 @@ public class MTMathTable: MTMathAtom {
self.alignments[col] = alignment self.alignments[col] = alignment
} }
func getAlignmentOf(col: Int) -> MTColumnAlignment { public func get(alignmentForColumn col: Int) -> MTColumnAlignment {
if self.alignments.count <= col { if self.alignments.count <= col {
return MTColumnAlignment.center return MTColumnAlignment.center
} else { } else {
@@ -576,7 +576,7 @@ public class MTMathTable: MTMathAtom {
} }
} }
func numberOfCols() -> Int { public var numColumns: Int {
var numberOfCols = 0 var numberOfCols = 0
for row in self.cells { for row in self.cells {
@@ -586,7 +586,7 @@ public class MTMathTable: MTMathAtom {
return numberOfCols return numberOfCols
} }
func numberOfRows() -> Int { public var numRows: Int {
return self.cells.count return self.cells.count
} }
} }

View File

@@ -92,6 +92,7 @@ public class MTMathListBuilder {
] ]
init(string: String) { init(string: String) {
self.error = nil
self.string = string self.string = string
self.currentCharIndex = string.startIndex self.currentCharIndex = string.startIndex
self.currentFontStyle = .defaultStyle self.currentFontStyle = .defaultStyle
@@ -115,31 +116,39 @@ public class MTMathListBuilder {
return builder.build() return builder.build()
} }
public static func build(fromString string: String, error:inout NSError?) -> MTMathList? { public static func build(fromString string: String, error:inout NSError) -> MTMathList? {
let builder = MTMathListBuilder(string: string) let builder = MTMathListBuilder(string: string)
let output = builder.build() let output = builder.build()
if builder.error != nil { if builder.error != nil {
if error != nil { error = builder.error!
error = builder.error
}
return nil return nil
} else {
error = NSError()
} }
return output return output
} }
public static func mathListToString(_ ml: MTMathList?) -> String { public static func mathListToString(_ ml: MTMathList?) -> String {
var output = "" var str = ""
var currentfontStyle = MTFontStyle.defaultStyle
if let atomList = ml { if let atomList = ml {
for atom in atomList.atoms { for atom in atomList.atoms {
switch atom.type { if currentfontStyle != atom.fontStyle {
case .fraction: if currentfontStyle != .defaultStyle {
str += "}"
}
if atom.fontStyle != .defaultStyle {
let fontStyleName = MTMathAtomFactory.fontNameForStyle(atom.fontStyle)
str += "\\\(fontStyleName){"
}
currentfontStyle = atom.fontStyle
}
if atom.type == .fraction {
if let frac = atom as? MTFraction { if let frac = atom as? MTFraction {
if frac.hasRule { if frac.hasRule {
output += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}"
} else { } else {
var command: String? = nil var command: String? = nil
if frac.leftDelimiter == nil && frac.rightDelimiter == nil { if frac.leftDelimiter == nil && frac.rightDelimiter == nil {
command = "atop" command = "atop"
} else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" { } else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" {
@@ -151,51 +160,47 @@ public class MTMathListBuilder {
} else { } else {
command = "atopwithdelims\(frac.leftDelimiter!)\(frac.rightDelimiter!)" command = "atopwithdelims\(frac.leftDelimiter!)\(frac.rightDelimiter!)"
} }
str += "{\(mathListToString(frac.numerator!)) \\\(command!) \(mathListToString(frac.denominator!))}"
output += "{\(mathListToString(frac.numerator!)) \\\(command!) \(mathListToString(frac.denominator!))}"
} }
} }
break } else if atom.type == .radical {
case .radical: str += "\\sqrt"
output += "\\sqrt"
if let rad = atom as? MTRadical { if let rad = atom as? MTRadical {
if rad.degree != nil { if rad.degree != nil {
output += "[\(mathListToString(rad.degree!))]" str += "[\(mathListToString(rad.degree!))]"
} }
output += "{\(mathListToString(rad.radicand!))}" str += "{\(mathListToString(rad.radicand!))}"
} }
break } else if atom.type == .inner {
case .inner:
if let inner = atom as? MTInner { if let inner = atom as? MTInner {
if inner.leftBoundary != nil || inner.rightBoundary != nil { if inner.leftBoundary != nil || inner.rightBoundary != nil {
if inner.leftBoundary != nil { if inner.leftBoundary != nil {
output += "\\left\(delimToString(delim: inner.leftBoundary!))" str += "\\left\(delimToString(delim: inner.leftBoundary!)) "
} else { } else {
output += "\\left. " str += "\\left. "
} }
output += mathListToString(inner.innerList!) str += mathListToString(inner.innerList!)
if inner.rightBoundary != nil { if inner.rightBoundary != nil {
output += "\\right\(delimToString(delim: inner.rightBoundary!))" str += "\\right\(delimToString(delim: inner.rightBoundary!)) "
} else { } else {
output += "\\right. " str += "\\right. "
} }
} else { } else {
output += "{\(mathListToString(inner.innerList!))}" str += "{\(mathListToString(inner.innerList!))}"
} }
} }
break } else if atom.type == .table {
case .table:
if let table = atom as? MTMathTable { if let table = atom as? MTMathTable {
if table.environment != nil { if table.environment != nil {
output += "\\begin{\(table.environment!)}" str += "\\begin{\(table.environment!)}"
} }
for i in 0..<table.numberOfRows() { for i in 0..<table.numRows {
for j in 0..<table.cells[i].count { let row = table.cells[i]
let cell = table.cells[i][j] for j in 0..<row.count {
let cell = row[j]
if table.environment == "matrix" { if table.environment == "matrix" {
if cell.atoms.count >= 1 && cell.atoms[0].type == .style { if cell.atoms.count >= 1 && cell.atoms[0].type == .style {
// remove first atom // remove first atom
@@ -209,82 +214,90 @@ public class MTMathListBuilder {
} }
} }
output += mathListToString(cell) str += mathListToString(cell)
if j < table.cells[i].count { if j < row.count - 1 {
output += "&" str += "&"
} }
} }
if i < table.numberOfRows() - 1 { if i < table.numRows - 1 {
output += "\\\\ " str += "\\\\ "
} }
} }
if table.environment != nil { if table.environment != nil {
output += "\\end{\(table.environment!)}" str += "\\end{\(table.environment!)}"
} }
} }
break } else if atom.type == .overline {
case .overline:
if let overline = atom as? MTOverLine { if let overline = atom as? MTOverLine {
output += "\\overline" str += "\\overline"
output += "{\(mathListToString(overline.innerList!))}" str += "{\(mathListToString(overline.innerList!))}"
} }
break } else if atom.type == .underline {
case .underline:
if let underline = atom as? MTUnderLine { if let underline = atom as? MTUnderLine {
output += "\\underline" str += "\\underline"
output += "{\(mathListToString(underline.innerList!))}" str += "{\(mathListToString(underline.innerList!))}"
} }
break } else if atom.type == .accent {
case .accent:
if let accent = atom as? MTAccent { if let accent = atom as? MTAccent {
output += "\\\(MTMathAtomFactory.getName(of: accent)!){\(mathListToString(accent.innerList!))}" str += "\\\(MTMathAtomFactory.accentName(accent)!){\(mathListToString(accent.innerList!))}"
} }
break } else if atom.type == .largeOperator {
case .space: let op = atom as! MTLargeOperator
let command = MTMathAtomFactory.latexSymbolName(for: atom)
let originalOp = MTMathAtomFactory.atom(forLatexSymbol: command!) as! MTLargeOperator
str += "\\\(command!) "
if originalOp.limits != op.limits {
if op.limits {
str += "\\limits "
} else {
str += "\\nolimits "
}
}
} else if atom.type == .space {
if let space = atom as? MTMathSpace { if let space = atom as? MTMathSpace {
if let command = MTMathListBuilder.spaceToCommands[space.space] { if let command = MTMathListBuilder.spaceToCommands[space.space] {
output += "\\\(command)" str += "\\\(command) "
} else { } else {
output += String.init(format: "\\mkern%.1fmu", space.space) str += String(format: "\\mkern%.1fmu", space.space)
} }
} }
break } else if atom.type == .style {
case .style:
if let style = atom as? MTMathStyle { if let style = atom as? MTMathStyle {
if let command = MTMathListBuilder.styleToCommands[style.style] { if let command = MTMathListBuilder.styleToCommands[style.style] {
output += "\\\(command)" str += "\\\(command) "
} }
} }
break } else if atom.nucleus.isEmpty {
default: str += "{}"
if atom.nucleus.count == 0 {
output += "{}"
} else if atom.nucleus == "\u{2236}" { } else if atom.nucleus == "\u{2236}" {
output += ":" // math colon
str += ":"
} else if atom.nucleus == "\u{2212}" { } else if atom.nucleus == "\u{2212}" {
output += "-" // math minus
str += "-"
} else { } else {
if let command = MTMathAtomFactory.latexSymbolName(for: atom) { if let command = MTMathAtomFactory.latexSymbolName(for: atom) {
output += "\\\(command)" str += "\\\(command) "
} else { } else {
output += "\(atom.nucleus)" str += "\(atom.nucleus)"
} }
} }
break
}
if atom.superScript != nil { if atom.superScript != nil {
output += "^{\(mathListToString(atom.superScript!))}" str += "^{\(mathListToString(atom.superScript!))}"
} }
if atom.subScript != nil { if atom.subScript != nil {
output += "_{\(mathListToString(atom.subScript!))}" str += "_{\(mathListToString(atom.subScript!))}"
} }
} }
} }
return output if currentfontStyle != .defaultStyle {
str += "}"
}
return str
} }
public static func delimToString(delim: MTMathAtom) -> String { public static func delimToString(delim: MTMathAtom) -> String {
@@ -302,15 +315,12 @@ public class MTMathListBuilder {
return "" return ""
} }
func getNextCharacter() -> Character? { func getNextCharacter() -> Character {
assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)") assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)")
if self.hasCharacters {
let ch = string[currentCharIndex] let ch = string[currentCharIndex]
currentCharIndex = string.index(after: currentCharIndex) currentCharIndex = string.index(after: currentCharIndex)
return ch return ch
} }
return nil
}
func unlookCharacter() { func unlookCharacter() {
assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.") assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.")
@@ -322,10 +332,10 @@ public class MTMathListBuilder {
} }
func buildInternal(_ oneCharOnly: Bool) -> MTMathList? { func buildInternal(_ oneCharOnly: Bool) -> MTMathList? {
return self.buildInternal(oneCharOnly, stop: nil) return self.buildInternal(oneCharOnly, stopChar: nil)
} }
func buildInternal(_ oneCharOnly: Bool, stop: Character?) -> MTMathList? { func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? {
let list = MTMathList() let list = MTMathList()
assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.") assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.")
var prevAtom: MTMathAtom? = nil var prevAtom: MTMathAtom? = nil
@@ -333,7 +343,7 @@ public class MTMathListBuilder {
if error != nil { return nil } if error != nil { return nil }
var atom: MTMathAtom? = nil var atom: MTMathAtom? = nil
let char = self.getNextCharacter()! let char = self.getNextCharacter()
if oneCharOnly { if oneCharOnly {
if char == "^" || char == "}" || char == "_" || char == "&" { if char == "^" || char == "}" || char == "_" || char == "&" {
@@ -342,7 +352,7 @@ public class MTMathListBuilder {
} }
} }
if stop != nil && char == stop { if stop != nil && char == stop! {
return list return list
} }
@@ -355,7 +365,7 @@ public class MTMathListBuilder {
list.add(prevAtom!) list.add(prevAtom!)
} }
prevAtom!.setSuperScript(self.buildInternal(true)) prevAtom!.superScript = self.buildInternal(true)
continue continue
} else if char == "_" { } else if char == "_" {
assert(!oneCharOnly, "This should have been handled before") assert(!oneCharOnly, "This should have been handled before")
@@ -365,46 +375,43 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "") prevAtom = MTMathAtom(type: .ordinary, value: "")
list.add(prevAtom!) list.add(prevAtom!)
} }
prevAtom!.setSubScript(self.buildInternal(true)) prevAtom!.subScript = self.buildInternal(true)
continue continue
} else if char == "{" { } else if char == "{" {
// this puts us in a recursive routine, and sets oneCharOnly to false and no stop character // this puts us in a recursive routine, and sets oneCharOnly to false and no stop character
if let subList = self.buildInternal(false, stop: "}") { let subList = self.buildInternal(false, stopChar: "}")
prevAtom = subList.atoms.last prevAtom = subList!.atoms.last
list.append(subList) list.append(subList!)
if oneCharOnly { if oneCharOnly {
return list return list
} }
continue continue
} else {
print("open brackets but inner...")
return nil
}
} else if char == "}" { } else if char == "}" {
assert(!oneCharOnly, "This should have been handled before") assert(!oneCharOnly, "This should have been handled before")
assert(stop == nil, "This should have been handled before") assert(stop == nil, "This should have been handled before")
// We encountered a closing brace when there is no stop set, that means there was no // We encountered a closing brace when there is no stop set, that means there was no
// corresponding opening brace. // corresponding opening brace.
print("Mismatched braces") let errorMessage = "Mismatched braces."
self.setError(.mismatchBraces, message:errorMessage)
return nil return nil
} else if char == "\\" { } else if char == "\\" {
let command = readCommand() let command = readCommand()
let done = stopCommand(command, list:list, stop:stop) let done = stopCommand(command, list:list, stopChar:stop)
if done != nil { if done != nil {
return done return done
} else if error != nil { } else if error != nil {
return nil return nil
} }
if self.applyModifier(command, atom:prevAtom) { if self.applyModifier(command, atom:prevAtom) {
continue; continue
} }
let fontStyle = MTMathAtomFactory.fontStyleWithName(command)
if fontStyle != nil { if let fontStyle = MTMathAtomFactory.fontStyleWithName(command) {
let oldSpacesAllowed = spacesAllowed let oldSpacesAllowed = spacesAllowed
// Text has special consideration where it allows spaces without escaping. // Text has special consideration where it allows spaces without escaping.
spacesAllowed = command == "text" spacesAllowed = command == "text"
let oldFontStyle = currentFontStyle let oldFontStyle = currentFontStyle
currentFontStyle = fontStyle! currentFontStyle = fontStyle
let sublist = self.buildInternal(true)! let sublist = self.buildInternal(true)!
// Restore the font style. // Restore the font style.
currentFontStyle = oldFontStyle currentFontStyle = oldFontStyle
@@ -431,27 +438,20 @@ public class MTMathListBuilder {
if self.currentEnv != nil { if self.currentEnv != nil {
return list return list
} else { } else {
if let table = self.buildTable(env: nil, firstList: list, isRow: false) { let table = self.buildTable(env: nil, firstList: list, isRow: false)
return MTMathList(atom: table) return MTMathList(atom: table!)
}
} }
} else if spacesAllowed && char == " " { } else if spacesAllowed && char == " " {
atom = MTMathAtomFactory.atom(forLatexSymbol: " ") atom = MTMathAtomFactory.atom(forLatexSymbol: " ")
} else { } else {
atom = MTMathAtomFactory.atom(for: String(char)) atom = MTMathAtomFactory.atom(forCharacter: char)
if atom == nil { if atom == nil {
continue continue
} }
} }
assert(atom != nil, "Atom shouldn't be nil") assert(atom != nil, "Atom shouldn't be nil")
atom?.fontStyle = currentFontStyle
if atom == nil {
print("wtf atom why nil?")
return nil
}
list.add(atom!) list.add(atom!)
prevAtom = atom prevAtom = atom
@@ -462,12 +462,14 @@ public class MTMathListBuilder {
if stop != nil { if stop != nil {
if stop == "}" { if stop == "}" {
print("Missing Closing Brace") // We did not find a corresponding closing brace.
self.setError(.mismatchBraces, message:"Missing closing brace")
} else { } else {
print("Expected Character not found: \(stop!)") // we never found our stop character
let errorMessage = "Expected character not found: \(stop!)"
self.setError(.characterNotFound, message:errorMessage)
} }
} }
return list return list
} }
@@ -475,7 +477,7 @@ public class MTMathListBuilder {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom return atom
} }
if let accent = MTMathAtomFactory.getAccent(withName: command) { if let accent = MTMathAtomFactory.accent(withName: command) {
// The command is an accent // The command is an accent
accent.innerList = self.buildInternal(true) accent.innerList = self.buildInternal(true)
return accent; return accent;
@@ -499,7 +501,7 @@ public class MTMathListBuilder {
let ch = self.getNextCharacter() let ch = self.getNextCharacter()
if (ch == "[") { if (ch == "[") {
// special handling for sqrt[degree]{radicand} // special handling for sqrt[degree]{radicand}
rad.degree = self.buildInternal(false, stop:"]") rad.degree = self.buildInternal(false, stopChar:"]")
rad.radicand = self.buildInternal(true) rad.radicand = self.buildInternal(true)
} else { } else {
self.unlookCharacter() self.unlookCharacter()
@@ -574,8 +576,8 @@ public class MTMathListBuilder {
// a string of all upper and lower case characters. // a string of all upper and lower case characters.
var mutable = "" var mutable = ""
while self.hasCharacters { while self.hasCharacters {
let ch = self.getNextCharacter()! let ch = self.getNextCharacter()
if (ch == "#" || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f") || (ch >= "0" && ch <= "9")) { if ch == "#" || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f") || (ch >= "0" && ch <= "9") {
mutable.append(ch) // appendString:[NSString stringWithCharacters:&ch length:1]]; mutable.append(ch) // appendString:[NSString stringWithCharacters:&ch length:1]];
} else { } else {
// we went too far // we went too far
@@ -594,7 +596,7 @@ public class MTMathListBuilder {
func skipSpaces() { func skipSpaces() {
while self.hasCharacters { while self.hasCharacters {
let ch = self.getNextCharacter()?.asciiValue ?? 0 let ch = self.getNextCharacter().utf32Char
if ch < 0x21 || ch > 0x7E { if ch < 0x21 || ch > 0x7E {
// skip non ascii characters and spaces // skip non ascii characters and spaces
continue; continue;
@@ -605,7 +607,7 @@ public class MTMathListBuilder {
} }
} }
func stopCommand(_ command: String, list:MTMathList, stop:Character?) -> MTMathList? { func stopCommand(_ command: String, list:MTMathList, stopChar:Character?) -> MTMathList? {
var fractionCommands: [String:[Character]] { var fractionCommands: [String:[Character]] {
[ [
"over": [], "over": [],
@@ -627,20 +629,19 @@ public class MTMathListBuilder {
} }
// return the list read so far. // return the list read so far.
return list return list
} else if let _ = fractionCommands[command] { } else if let delims = fractionCommands[command] {
var frac:MTFraction! = nil; var frac:MTFraction! = nil;
if command == "over" { if command == "over" {
frac = MTFraction() frac = MTFraction()
} else { } else {
frac = MTFraction(hasRule: false) frac = MTFraction(hasRule: false)
} }
let delims = fractionCommands[command]!
if delims.count == 2 { if delims.count == 2 {
frac.leftDelimiter = String(delims[0]) frac.leftDelimiter = String(delims[0])
frac.rightDelimiter = String(delims[1]) frac.rightDelimiter = String(delims[1])
} }
frac.numerator = list; frac.numerator = list;
frac.denominator = self.buildInternal(false, stop: stop) frac.denominator = self.buildInternal(false, stopChar: stopChar)
if error != nil { if error != nil {
return nil; return nil;
} }
@@ -658,7 +659,7 @@ public class MTMathListBuilder {
return MTMathList(atom: table!) return MTMathList(atom: table!)
} }
} else if command == "end" { } else if command == "end" {
if currentEnv != nil { if currentEnv == nil {
let errorMessage = "Missing \\begin"; let errorMessage = "Missing \\begin";
self.setError(.missingBegin, message:errorMessage) self.setError(.missingBegin, message:errorMessage)
return nil; return nil;
@@ -691,7 +692,7 @@ public class MTMathListBuilder {
} }
return true return true
} else if modifier == "nolimits" { } else if modifier == "nolimits" {
if atom!.type != .largeOperator { if atom?.type != .largeOperator {
let errorMessage = "No limits can only be applied to an operator." let errorMessage = "No limits can only be applied to an operator."
self.setError(.invalidLimits, message:errorMessage) self.setError(.invalidLimits, message:errorMessage)
return true return true
@@ -715,8 +716,7 @@ public class MTMathListBuilder {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom return atom
} }
if let accent = MTMathAtomFactory.accent(withName: command) {
if let accent = MTMathAtomFactory.getAccent(withName: command) {
accent.innerList = self.buildInternal(true) accent.innerList = self.buildInternal(true)
return accent return accent
} else if command == "frac" { } else if command == "frac" {
@@ -734,9 +734,8 @@ public class MTMathListBuilder {
} else if command == "sqrt" { } else if command == "sqrt" {
let rad = MTRadical() let rad = MTRadical()
let char = self.getNextCharacter() let char = self.getNextCharacter()
if char == "[" { if char == "[" {
rad.degree = self.buildInternal(false, stop: "]") rad.degree = self.buildInternal(false, stopChar: "]")
rad.radicand = self.buildInternal(true) rad.radicand = self.buildInternal(true)
} else { } else {
self.unlookCharacter() self.unlookCharacter()
@@ -752,7 +751,7 @@ public class MTMathListBuilder {
} }
self.currentInnerAtom!.innerList = self.buildInternal(false) self.currentInnerAtom!.innerList = self.buildInternal(false)
if self.currentInnerAtom?.rightBoundary == nil { if self.currentInnerAtom?.rightBoundary == nil {
print("Missing \\right") self.setError(.missingRight, message: "Missing \\right")
return nil return nil
} }
let newInner = self.currentInnerAtom let newInner = self.currentInnerAtom
@@ -775,86 +774,24 @@ public class MTMathListBuilder {
} else { } else {
return nil return nil
} }
} else if command == "color" {
// A color command has 2 arguments
let mathColor = MTMathColor()
mathColor.colorString = self.readColor()!
mathColor.innerList = self.buildInternal(true)
return mathColor
} else if command == "colorbox" {
// A color command has 2 arguments
let mathColorbox = MTMathColorbox()
mathColorbox.colorString = self.readColor()!
mathColorbox.innerList = self.buildInternal(true)
return mathColorbox
} else { } else {
print("Invalid Command") self.setError(.invalidCommand, message: "Invalid command \\\(command)")
return nil return nil
} }
} }
// func stop(command: String, list: MTMathList, stopChar: Character) -> MTMathList? {
// let fractionCommands = [
// "over": [],
// "atop": [],
// "choose": ["(", ")"],
// "brack": ["[", "]"],
// "brace": ["{", "}"]
// ]
//
// if command == "right" {
// if self.currentInnerAtom == nil {
// print("missing left")
// return nil
// }
//
// if let rightBoundary = self.getBoundaryAtom("right") {
// self.currentInnerAtom!.rightBoundary = rightBoundary
//
// return list
// } else {
// return nil
// }
// } else if let delims = fractionCommands[command] {
// let frac: MTFraction
// if command == "over" {
// frac = MTFraction()
// } else {
// frac = MTFraction(hasRule: false)
// }
//
// if delims.count == 2 {
// frac.leftDelimiter = delims[0]
// frac.rightDelimiter = delims[1]
// }
//
// frac.numerator = list
// frac.denominator = self.buildInternal(false, stopChar: stopChar)
//
// let fracList = MTMathList()
// fracList.add(frac)
// return fracList
// } else if command == "\\" || command == "cr" {
// if currentEnv != nil {
// currentEnv!.numRows += 1
// return list
// } else {
// if let table = self.buildTable(env: nil, firstList: list, isRow: true) {
// return MTMathList(atoms: [table])
// } else {
// return nil
// }
// }
// } else if command == "end" {
// if self.currentEnv == nil {
// print("Missing \\begin")
// return nil
// }
//
// if let env = self.readEnvironment() {
// if env != self.currentEnv?.envName {
// print("Begin environment name \(currentEnv!.envName!) does not match end name: \(env)")
// return nil
// }
//
// currentEnv?.ended = true
//
// return list
// } else {
// return nil
// }
// }
// return nil
// }
func readEnvironment() -> String? { func readEnvironment() -> String? {
if !self.expectCharacter("{") { if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found. // We didn't find an opening brace, so no env found.
@@ -862,7 +799,7 @@ public class MTMathListBuilder {
return nil return nil
} }
self.skipSpace() self.skipSpaces()
let env = self.readString() let env = self.readString()
if !self.expectCharacter("}") { if !self.expectCharacter("}") {
@@ -873,13 +810,18 @@ public class MTMathListBuilder {
return env return env
} }
func expectCharacter(_ char: Character) -> Bool { func MTAssertNotSpace(_ ch: Character) {
self.skipSpace() assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
}
func expectCharacter(_ ch: Character) -> Bool {
MTAssertNotSpace(ch)
self.skipSpaces()
if self.hasCharacters { if self.hasCharacters {
let nextChar = self.getNextCharacter()! let nextChar = self.getNextCharacter()
MTAssertNotSpace(nextChar)
if nextChar == char { if nextChar == ch {
return true return true
} else { } else {
self.unlookCharacter() self.unlookCharacter()
@@ -899,64 +841,68 @@ public class MTMathListBuilder {
var currentCol = 0 var currentCol = 0
var rows = [[MTMathList]]() var rows = [[MTMathList]]()
rows.append([MTMathList]())
if firstList != nil { if firstList != nil {
rows[currentRow][currentCol] = firstList! rows[currentRow].append(firstList!)
if isRow { if isRow {
currentEnv?.numRows += 1 currentEnv!.numRows+=1
currentRow += 1 currentRow+=1
rows.append([MTMathList]())
} else { } else {
currentCol += 1 currentCol+=1
} }
} }
while !currentEnv!.ended && self.hasCharacters { while !currentEnv!.ended && self.hasCharacters {
if let list = self.buildInternal(false) { let list = self.buildInternal(false)
rows[currentRow][currentCol] = list if list == nil {
currentCol += 1 // If there is an error building the list, bail out early.
if self.currentEnv!.numRows > currentRow { return nil
currentRow = self.currentEnv!.numRows }
rows[currentRow].append(list!)
currentCol+=1
if currentEnv!.numRows > currentRow {
currentRow = currentEnv!.numRows
rows.append([MTMathList]())
currentCol = 0 currentCol = 0
} }
} else {
return nil
}
} }
if !currentEnv!.ended && currentEnv?.envName == nil { if !currentEnv!.ended && currentEnv!.envName != nil {
print("Missing \\end") self.setError(.missingEnd, message: "Missing \\end")
return nil return nil
} }
if let table = MTMathAtomFactory.table(withEnvironment: currentEnv?.envName, rows: rows) { var error:NSError?
let table = MTMathAtomFactory.table(withEnvironment: currentEnv?.envName, rows: rows, error: &error)
if table == nil && self.error == nil {
self.error = error
return nil
}
self.currentEnv = oldEnv self.currentEnv = oldEnv
return table return table
} else {
return nil
}
} }
func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? { func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? {
let delim = self.readDelimiter() let delim = self.readDelimiter()
if delim == nil { if delim == nil {
assertionFailure("Missing delimiter for \(delimiterType)") let errorMessage = "Missing delimiter for \\\(delimiterType)"
self.setError(.missingDelimiter, message:errorMessage)
return nil return nil
} }
let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!) let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!)
if boundary == nil { if boundary == nil {
assertionFailure("Invalid delimiter for \(delimiterType): \(delim!)") let errorMessage = "Invalid delimiter for \(delimiterType): \(delim!)"
self.setError(.missingDelimiter, message:errorMessage)
return nil return nil
} }
return boundary return boundary
} }
func readDelimiter() -> String? { func readDelimiter() -> String? {
self.skipSpace() self.skipSpaces()
while self.hasCharacters { while self.hasCharacters {
let char = self.getNextCharacter()! let char = self.getNextCharacter()
MTAssertNotSpace(char)
if char == "\\" { if char == "\\" {
let command = self.readCommand() let command = self.readCommand()
if command == "|" { if command == "|" {
@@ -970,31 +916,16 @@ public class MTMathListBuilder {
return nil return nil
} }
func skipSpace() {
while self.hasCharacters {
let char = self.getNextCharacter()
let asciiCharacterSet = CharacterSet(charactersIn: UnicodeScalar(0x21)...UnicodeScalar(0x7e))
if String(char!).rangeOfCharacter(from: asciiCharacterSet) != nil {
self.unlookCharacter()
return
} else {
continue
}
}
}
func readCommand() -> String { func readCommand() -> String {
let singleChars = Array("{}$#%_| ,>;!\\") let singleChars = "{}$#%_| ,>;!\\"
if self.hasCharacters { if self.hasCharacters {
if let char = self.getNextCharacter() { let char = self.getNextCharacter()
if let _ = singleChars.firstIndex(of: char) { if let _ = singleChars.firstIndex(of: char) {
return String(char) return String(char)
} else { } else {
self.unlookCharacter() self.unlookCharacter()
} }
} }
}
return self.readString() return self.readString()
} }
@@ -1002,7 +933,7 @@ public class MTMathListBuilder {
// a string of all upper and lower case characters. // a string of all upper and lower case characters.
var output = "" var output = ""
while self.hasCharacters { while self.hasCharacters {
if let char = self.getNextCharacter() { let char = self.getNextCharacter()
if char.isLowercase || char.isUppercase { if char.isLowercase || char.isUppercase {
output.append(char) output.append(char)
} else { } else {
@@ -1010,7 +941,6 @@ public class MTMathListBuilder {
break break
} }
} }
}
return output return output
} }
} }

View File

@@ -71,12 +71,12 @@ class MTMathUILabel : MTView {
var latex = "" { var latex = "" {
didSet { didSet {
self.error = nil self.error = nil
var error: NSError? = nil var error = NSError()
self.mathList = MTMathListBuilder.build(fromString: latex, error: &error) self.mathList = MTMathListBuilder.build(fromString: latex, error: &error)
if error != nil { if error != NSError() {
self.mathList = nil self.mathList = nil
self.error = error self.error = error
self.errorLabel?.text = error?.localizedDescription self.errorLabel?.text = error.localizedDescription
self.errorLabel?.frame = self.bounds self.errorLabel?.frame = self.bounds
self.errorLabel?.isHidden = !self.displayErrorInline self.errorLabel?.isHidden = !self.displayErrorInline
} else { } else {

View File

@@ -1637,7 +1637,7 @@ class MTTypesetter {
for i in 0..<cols.count { for i in 0..<cols.count {
let col = cols[i] let col = cols[i]
let colWidth = columnWidths[i] let colWidth = columnWidths[i]
let alignment = table?.getAlignmentOf(col: i) let alignment = table?.get(alignmentForColumn: i)
var cellPos = columnStart; var cellPos = columnStart;
switch alignment { switch alignment {
case .right: case .right:

View File

@@ -47,6 +47,34 @@ final class SwiftMathRenderTests: XCTestCase {
} }
} }
func getTestData() -> [TestRecord] {
[
TestRecord(build: "x", atomType: [.variable ], types: [], result: "x"),
TestRecord(build: "1", atomType: [.number ] , types: [], result: "1"),
TestRecord(build: "*", atomType: [.binaryOperator ] ,types: [], result:"*"),
TestRecord(build: "+", atomType: [.binaryOperator ], types: [], result:"+"),
TestRecord(build: ".", atomType: [.number ], types: [], result:"."),
TestRecord(build: "(", atomType: [.open ], types: [], result:"(" ),
TestRecord(build: ")", atomType: [.close ], types: [], result:")"),
TestRecord(build: ",", atomType: [.punctuation], types: [], result:","),
TestRecord(build: "!", atomType: [.close], types: [], result:"!"),
TestRecord(build: "=", atomType: [.relation], types: [], result:"="),
TestRecord(build: "x+2", atomType: [.variable, .binaryOperator, .number ], types: [], result:"x+2"),
// spaces are ignored
TestRecord(build: "(2.3 * 8)", atomType: [.open, .number, .number, .number, .binaryOperator, .number , .close ], types: [], result:"(2.3*8)"),
// braces are just for grouping
TestRecord(build: "5{3+4}", atomType: [.number, .number, .binaryOperator, .number], types: [], result:"53+4"),
// commands
TestRecord(build: "\\pi+\\theta\\geq 3",atomType: [.variable, .binaryOperator, .variable, .relation, .number], types: [], result:"\\pi +\\theta \\geq 3"),
// aliases
TestRecord(build: "\\pi\\ne 5 \\land 3", atomType: [.variable, .relation, .number, .binaryOperator, .number], types: [], result:"\\pi \\neq 5\\wedge 3"),
// control space
TestRecord(build: "x \\ y", atomType: [ .variable, .ordinary, .variable], types: [], result:"x\\ y"),
// spacing
TestRecord(build: "x \\quad y \\; z \\! q", atomType: [ .variable, .space, .variable,.space, .variable, .space, .variable], types: [], result:"x\\quad y\\; z\\! q")
]
}
func getTestDataSuperScript() -> [TestRecord] { func getTestDataSuperScript() -> [TestRecord] {
[ [
TestRecord(build: "x^2", atomType: [.variable], types: [.number], result: "x^{2}"), TestRecord(build: "x^2", atomType: [.variable], types: [.number], result: "x^{2}"),
@@ -125,13 +153,68 @@ final class SwiftMathRenderTests: XCTestCase {
] ]
} }
func getTestDataParseErrors() -> [(String, MTParseErrors)] {
return [
("}a", .mismatchBraces),
("\\notacommand", .invalidCommand),
("\\sqrt[5+3", .characterNotFound),
("{5+3", .mismatchBraces),
("5+3}", .mismatchBraces),
("{1+\\frac{3+2", .mismatchBraces),
("1+\\left", .missingDelimiter),
("\\left(\\frac12\\right", .missingDelimiter),
("\\left 5 + 3 \\right)", .invalidDelimiter),
("\\left(\\frac12\\right + 3", .invalidDelimiter),
("\\left\\lmoustache 5 + 3 \\right)", .invalidDelimiter),
("\\left(\\frac12\\right\\rmoustache + 3", .invalidDelimiter),
("5 + 3 \\right)", .missingLeft),
("\\left(\\frac12", .missingRight),
("\\left(5 + \\left| \\frac12 \\right)", .missingRight),
("5+ \\left|\\frac12\\right| \\right)", .missingLeft),
("\\begin matrix \\end matrix", .characterNotFound), // missing {
("\\begin", .characterNotFound), // missing {
("\\begin{", .characterNotFound), // missing }
("\\begin{matrix parens}", .characterNotFound), // missing } (no spaces in env)
("\\begin{matrix} x", .missingEnd),
("\\begin{matrix} x \\end", .characterNotFound), // missing {
("\\begin{matrix} x \\end + 3", .characterNotFound), // missing {
("\\begin{matrix} x \\end{", .characterNotFound), // missing }
("\\begin{matrix} x \\end{matrix + 3", .characterNotFound), // missing }
("\\begin{matrix} x \\end{pmatrix}", .invalidEnv),
("x \\end{matrix}", .missingBegin),
("\\begin{notanenv} x \\end{notanenv}", .invalidEnv),
("\\begin{matrix} \\notacommand \\end{matrix}", .invalidCommand),
("\\begin{displaylines} x & y \\end{displaylines}", .invalidNumColumns),
("\\begin{eqalign} x \\end{eqalign}", .invalidNumColumns),
("\\nolimits", .invalidLimits),
("\\frac\\limits{1}{2}", .invalidLimits)
]
}
func testBuilder() throws {
let data = getTestData()
for testCase in data {
let str = testCase.build
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error: &error)
XCTAssert(error.code == 0)
let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType
self.checkAtomTypes(list, types:atomTypes, desc:desc)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, testCase.result, desc)
}
}
func testSuperScript() throws { func testSuperScript() throws {
let data = getTestDataSuperScript() let data = getTestDataSuperScript()
for testCase in data { for testCase in data {
let str = testCase.build let str = testCase.build
var error:NSError? var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error) let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error) XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)" let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc) checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -163,9 +246,9 @@ final class SwiftMathRenderTests: XCTestCase {
let data = getTestDataSubScript() let data = getTestDataSubScript()
for testCase in data { for testCase in data {
let str = testCase.build let str = testCase.build
var error:NSError? var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error) let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error) XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)" let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc) checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -197,9 +280,9 @@ final class SwiftMathRenderTests: XCTestCase {
let data = getTestDataSuperSubScript() let data = getTestDataSuperSubScript()
for testCase in data { for testCase in data {
let str = testCase.build let str = testCase.build
var error:NSError? var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error) let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error) XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)" let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc) checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -425,7 +508,7 @@ final class SwiftMathRenderTests: XCTestCase {
for testCase in data { for testCase in data {
let str = testCase.build let str = testCase.build
var error:NSError? var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error: &error)! let list = MTMathListBuilder.build(fromString: str, error: &error)!
XCTAssertNotNil(list, str); XCTAssertNotNil(list, str);
@@ -456,6 +539,824 @@ final class SwiftMathRenderTests: XCTestCase {
} }
} }
func testOver() throws {
let str = "1 \\over c";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\frac{1}{c}", desc);
}
func testOverInParens() throws {
let str = "5 + {1 \\over c} + 8";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 5, desc);
let types = [MTMathAtomType.number, .binaryOperator, .fraction, .binaryOperator, .number]
self.checkAtomTypes(list, types:types, desc:desc)
let frac = list.atoms[2] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "5+\\frac{1}{c}+8", desc);
}
func testAtop() throws {
let str = "1 \\atop c";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{1 \\atop c}", desc);
}
func testAtopInParens() throws {
let str = "5 + {1 \\atop c} + 8";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 5, desc);
let types = [MTMathAtomType.number, .binaryOperator, .fraction, .binaryOperator, .number]
self.checkAtomTypes(list, types:types, desc:desc)
let frac = list.atoms[2] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "5+{1 \\atop c}+8", desc);
}
func testChoose() throws {
let str = "n \\choose k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, ")");
XCTAssertEqual(frac.leftDelimiter, "(");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\choose k}", desc);
}
func testBrack() throws {
let str = "n \\brack k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, "]");
XCTAssertEqual(frac.leftDelimiter, "[");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\brack k}", desc);
}
func testBrace() throws {
let str = "n \\brace k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, "}");
XCTAssertEqual(frac.leftDelimiter, "{");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\brace k}", desc);
}
func testBinom() throws {
let str = "\\binom{n}{k}";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, ")");
XCTAssertEqual(frac.leftDelimiter, "(");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex (binom converts to choose)
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\choose k}", desc);
}
func testOverLine() throws {
let str = "\\overline 2";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let over = list.atoms[0] as! MTOverLine
XCTAssertEqual(over.type, .overline, desc);
XCTAssertEqual(over.nucleus, "", desc);
let subList = over.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "2", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\overline{2}", desc);
}
func testUnderline() throws {
let str = "\\underline 2";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let under = list.atoms[0] as! MTUnderLine
XCTAssertEqual(under.type, .underline, desc);
XCTAssertEqual(under.nucleus, "", desc);
let subList = under.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "2", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\underline{2}", desc);
}
func testAccent() throws {
let str = "\\bar x";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let accent = list.atoms[0] as! MTAccent
XCTAssertEqual(accent.type, .accent, desc);
XCTAssertEqual(accent.nucleus, "\u{0304}", desc);
let subList = accent.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "x", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\bar{x}", desc);
}
func testMathSpace() throws {
let str = "\\!";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let space = list.atoms[0] as! MTMathSpace
XCTAssertEqual(space.type, .space, desc);
XCTAssertEqual(space.nucleus, "", desc);
XCTAssertEqual(space.space, -3);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\! ", desc);
}
func testMathStyle() throws {
let str = "\\textstyle y \\scriptstyle x";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 4, desc);
let style = list.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style, desc);
XCTAssertEqual(style.nucleus, "", desc);
XCTAssertEqual(style.style, .text);
let style2 = list.atoms[2] as! MTMathStyle
XCTAssertEqual(style2.type, .style, desc);
XCTAssertEqual(style2.nucleus, "", desc);
XCTAssertEqual(style2.style, .script);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\textstyle y\\scriptstyle x", desc);
}
func testMatrix() throws {
let str = "\\begin{matrix} x & y \\\\ z & w \\end{matrix}";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.environment, "matrix");
XCTAssertEqual(table.interRowAdditionalSpacing, 0);
XCTAssertEqual(table.interColumnSpacing, 18);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 2);
let style = cell.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style);
XCTAssertEqual(style.style, .text);
let atom = cell.atoms[1];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\begin{matrix}x&y\\\\ z&w\\end{matrix}");
}
func testPMatrix() throws {
let str = "\\begin{pmatrix} x & y \\\\ z & w \\end{pmatrix}";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let inner = list.atoms[0] as! MTInner
XCTAssertEqual(inner.type, .inner, str);
XCTAssertEqual(inner.nucleus, "", str);
let innerList = inner.innerList!
XCTAssertNotNil(innerList, str);
XCTAssertNotNil(inner.leftBoundary, str);
XCTAssertEqual(inner.leftBoundary!.type, .boundary, str);
XCTAssertEqual(inner.leftBoundary!.nucleus, "(", str);
XCTAssertNotNil(inner.rightBoundary, str);
XCTAssertEqual(inner.rightBoundary!.type, .boundary, str);
XCTAssertEqual(inner.rightBoundary!.nucleus, ")", str);
XCTAssertEqual((innerList.atoms.count), 1);
let table = innerList.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.environment, "matrix");
XCTAssertEqual(table.interRowAdditionalSpacing, 0);
XCTAssertEqual(table.interColumnSpacing, 18);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 2);
let style = cell.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style);
XCTAssertEqual(style.style, .text);
let atom = cell.atoms[1];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\left( \\begin{matrix}x&y\\\\ z&w\\end{matrix}\\right) ");
}
func testDefaultTable() throws {
let str = "x \\\\ y";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual(list.atoms.count, 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment);
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 1);
for i in 0..<1 {
let alignment = table.get(alignmentForColumn: i)
XCTAssertEqual(alignment, .left);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "x\\\\ y");
}
func testDefaultTableWithCols() throws {
let str = "x & y \\\\ z & w";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment);
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .left);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "x&y\\\\ z&w");
}
func testEqalign() throws {
let str1 = "\\begin{eqalign}x&y\\\\ z&w\\end{eqalign}";
let str2 = "\\begin{split}x&y\\\\ z&w\\end{split}";
let str3 = "\\begin{aligned}x&y\\\\ z&w\\end{aligned}";
for str in [str1, str2, str3] {
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, (i == 0) ? .right: .left);
for j in 0..<2 {
let cell = table.cells[j][i];
if (i == 0) {
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
} else {
XCTAssertEqual(cell.atoms.count, 2);
self.checkAtomTypes(cell, types:[.ordinary, .variable], desc:str)
}
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, str);
}
}
func testDisplayLines() throws {
let str1 = "\\begin{displaylines}x\\\\ y\\end{displaylines}";
let str2 = "\\begin{gather}x\\\\ y\\end{gather}";
for str in [str1, str2] {
let list = MTMathListBuilder.build(fromString:str)
XCTAssertNotNil(list)
XCTAssertEqual(list?.atoms.count, 1);
let table = list?.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 1);
for i in 0..<1 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, str);
}
}
func testErrors() throws {
let data = getTestDataParseErrors()
for testCase in data {
let str = testCase.0
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error)
let desc = "Error for string:\(str)"
XCTAssertNil(list, desc)
XCTAssertNotNil(error, desc)
XCTAssertEqual(error.domain, MTParseError, desc)
let num = testCase.1
let code = num.rawValue
XCTAssertEqual(error.code, code, desc)
}
}
func testCustom() throws {
let str = "\\lcm(a,b)";
var error = NSError()
var list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(list);
XCTAssert(error.code == NSNotFound)
MTMathAtomFactory.add(latexSymbol: "lcm", value: MTMathAtomFactory.operatorWithName("lcm", limits:false))
list = MTMathListBuilder.build(fromString: str, error:&error)
let atomTypes = [MTMathAtomType.largeOperator, .open, .variable, .punctuation, .variable, .close]
self.checkAtomTypes(list, types:atomTypes, desc:"Error for lcm")
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\lcm (a,b)");
}
func testFontSingle() throws {
let str = "\\mathbf x";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual(list.atoms.count, 1, desc)
let atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .bold)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathbf{x}", desc)
}
func testFontOneChar() throws {
let str = "\\cal xy";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .caligraphic);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .defaultStyle);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathcal{x}y", desc)
}
func testFontMultipleChars() throws {
let str = "\\frak{xy}";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .fraktur);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .fraktur);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathfrak{xy}", desc)
}
func testFontOneCharInside() throws {
let str = "\\sqrt \\mathrm x y";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
let rad = list.atoms[0] as! MTRadical
XCTAssertEqual(rad.type, .radical, desc)
XCTAssertEqual(rad.nucleus, "", desc)
let subList = rad.radicand!
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .roman);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .defaultStyle)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sqrt{\\mathrm{x}}y", desc)
}
func testText() throws {
let str = "\\text{x y}";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 3, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .roman);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .ordinary, desc)
XCTAssertEqual(atom.nucleus, " ", desc)
atom = list.atoms[2];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .roman);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathrm{x\\ y}", desc)
}
func testLimits() throws {
// Int with no limits (default)
var str = "\\int";
var list = MTMathListBuilder.build(fromString: str)!
var desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
var op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertFalse(op.limits);
// convert it back to latex
var latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\int ", desc)
// Int with limits
str = "\\int\\limits"
list = MTMathListBuilder.build(fromString: str)!
desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertTrue(op.limits);
// convert it back to latex
latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\int \\limits ", desc)
}
func testNoLimits() throws {
// Sum with limits (default)
var str = "\\sum";
var list = MTMathListBuilder.build(fromString: str)!
var desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
var op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertTrue(op.limits);
// convert it back to latex
var latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sum ", desc)
// Int with limits
str = "\\sum\\nolimits";
list = MTMathListBuilder.build(fromString: str)!
desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual(list.atoms.count, 1, desc)
op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertFalse(op.limits);
// convert it back to latex
latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sum \\nolimits ", desc)
}
// func testPerformanceExample() throws { // func testPerformanceExample() throws {
// // This is an example of a performance test case. // // This is an example of a performance test case.