Add array environment parsing
Agent-Logs-Url: https://github.com/wesleyel/swiftui-math/sessions/56436444-e15b-4dd0-8a70-c87df1e3dc4e Co-authored-by: wesleyel <48174882+wesleyel@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
8201156af8
commit
f8f668f649
@@ -658,10 +658,12 @@ extension Math {
|
||||
static func table(
|
||||
withEnvironment env: String?,
|
||||
alignment: Table.ColumnAlignment? = nil,
|
||||
columnAlignments: [Table.ColumnAlignment]? = nil,
|
||||
columnFormat: String? = nil,
|
||||
rows: [[AtomList]],
|
||||
error: inout ParserError?
|
||||
) -> Atom? {
|
||||
let table = Table(environment: env ?? "")
|
||||
let table = Table(environment: env ?? "", columnFormat: columnFormat)
|
||||
|
||||
for i in 0..<rows.count {
|
||||
let row = rows[i]
|
||||
@@ -709,6 +711,38 @@ extension Math {
|
||||
} else {
|
||||
return table
|
||||
}
|
||||
} else if env == "array" {
|
||||
guard let columnAlignments, !columnAlignments.isEmpty else {
|
||||
let message = "array environment requires at least 1 column alignment"
|
||||
if error == nil {
|
||||
error = ParserError(code: .invalidEnvironment, message: message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if table.numberOfColumns > columnAlignments.count {
|
||||
let message = "array environment has more columns than alignment specifiers"
|
||||
if error == nil {
|
||||
error = ParserError(code: .invalidNumberOfColumns, message: message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
table.interRowAdditionalSpacing = 0
|
||||
table.interColumnSpacing = 18
|
||||
|
||||
let style = Style(level: .text)
|
||||
for i in 0..<table.cells.count {
|
||||
for j in 0..<table.cells[i].count {
|
||||
table.cells[i][j].insert(style, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
for (column, alignment) in columnAlignments.enumerated() {
|
||||
table.setAlignment(alignment, forColumn: column)
|
||||
}
|
||||
|
||||
return table
|
||||
} else if env == "eqalign" || env == "split" || env == "aligned" {
|
||||
if table.numberOfColumns != 2 {
|
||||
let message = "\(env) environment can only have 2 columns"
|
||||
|
||||
@@ -11,6 +11,7 @@ extension Math {
|
||||
var alignments: [ColumnAlignment]
|
||||
var cells: [[AtomList]]
|
||||
var environment: String
|
||||
var columnFormat: String?
|
||||
var interColumnSpacing: CGFloat
|
||||
var interRowAdditionalSpacing: CGFloat
|
||||
|
||||
@@ -32,6 +33,7 @@ extension Math {
|
||||
row.map { AtomList($0) }
|
||||
}
|
||||
self.environment = table.environment
|
||||
self.columnFormat = table.columnFormat
|
||||
self.interColumnSpacing = table.interColumnSpacing
|
||||
self.interRowAdditionalSpacing = table.interRowAdditionalSpacing
|
||||
|
||||
@@ -42,12 +44,14 @@ extension Math {
|
||||
alignments: [ColumnAlignment] = [],
|
||||
cells: [[AtomList]] = [],
|
||||
environment: String = "",
|
||||
columnFormat: String? = nil,
|
||||
interColumnSpacing: CGFloat = 0,
|
||||
interRowAdditionalSpacing: CGFloat = 0
|
||||
) {
|
||||
self.alignments = alignments
|
||||
self.cells = cells
|
||||
self.environment = environment
|
||||
self.columnFormat = columnFormat
|
||||
self.interColumnSpacing = interColumnSpacing
|
||||
self.interRowAdditionalSpacing = interRowAdditionalSpacing
|
||||
super.init(type: .table)
|
||||
|
||||
@@ -31,12 +31,21 @@ extension Math {
|
||||
var ended: Bool
|
||||
var numberOfRows: Int
|
||||
var alignment: Table.ColumnAlignment? // Optional alignment for starred matrix environments
|
||||
var columnAlignments: [Table.ColumnAlignment]?
|
||||
var columnFormat: String?
|
||||
|
||||
init(name: String?, alignment: Table.ColumnAlignment? = nil) {
|
||||
init(
|
||||
name: String?,
|
||||
alignment: Table.ColumnAlignment? = nil,
|
||||
columnAlignments: [Table.ColumnAlignment]? = nil,
|
||||
columnFormat: String? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.numberOfRows = 0
|
||||
self.ended = false
|
||||
self.alignment = alignment
|
||||
self.columnAlignments = columnAlignments
|
||||
self.columnFormat = columnFormat
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,13 +528,16 @@ extension Math {
|
||||
if let table = atom as? Table {
|
||||
if !table.environment.isEmpty {
|
||||
str += "\\begin{\(table.environment)}"
|
||||
if table.environment == "array" {
|
||||
str += "{\(table.columnFormat ?? "")}"
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..<table.numberOfRows {
|
||||
let row = table.cells[i]
|
||||
for j in 0..<row.count {
|
||||
let cell = row[j]
|
||||
if table.environment == "matrix" {
|
||||
if table.environment == "matrix" || table.environment == "array" {
|
||||
if cell.atoms.count >= 1 && cell.atoms[0].type == Math.AtomType.style {
|
||||
// remove first atom
|
||||
cell.atoms.removeFirst()
|
||||
@@ -797,7 +809,30 @@ extension Math {
|
||||
if env == nil {
|
||||
return nil
|
||||
}
|
||||
let table = self.buildTable(environment: env, firstList: nil, isRow: false)
|
||||
var alignment: Table.ColumnAlignment? = nil
|
||||
var columnAlignments: [Table.ColumnAlignment]? = nil
|
||||
var columnFormat: String? = nil
|
||||
if let env {
|
||||
if env.hasSuffix("*") {
|
||||
alignment = self.readOptionalAlignment()
|
||||
if self.error != nil {
|
||||
return nil
|
||||
}
|
||||
} else if env == "array" {
|
||||
(columnAlignments, columnFormat) = self.readArrayColumnFormat()
|
||||
if self.error != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
let table = self.buildTable(
|
||||
environment: env,
|
||||
alignment: alignment,
|
||||
columnAlignments: columnAlignments,
|
||||
columnFormat: columnFormat,
|
||||
firstList: nil,
|
||||
isRow: false
|
||||
)
|
||||
return table
|
||||
} else if command == "color" {
|
||||
// A color command has 2 arguments
|
||||
@@ -1156,16 +1191,25 @@ extension Math {
|
||||
if let env = self.readEnvironment() {
|
||||
// Check if this is a starred matrix environment and read optional alignment
|
||||
var alignment: Table.ColumnAlignment? = nil
|
||||
var columnAlignments: [Table.ColumnAlignment]? = nil
|
||||
var columnFormat: String? = nil
|
||||
if env.hasSuffix("*") {
|
||||
alignment = self.readOptionalAlignment()
|
||||
if self.error != nil {
|
||||
return nil
|
||||
}
|
||||
} else if env == "array" {
|
||||
(columnAlignments, columnFormat) = self.readArrayColumnFormat()
|
||||
if self.error != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
let table = self.buildTable(
|
||||
environment: env,
|
||||
alignment: alignment,
|
||||
columnAlignments: columnAlignments,
|
||||
columnFormat: columnFormat,
|
||||
firstList: nil,
|
||||
isRow: false
|
||||
)
|
||||
@@ -1254,6 +1298,59 @@ extension Math {
|
||||
return alignment
|
||||
}
|
||||
|
||||
mutating func readArrayColumnFormat() -> ([Table.ColumnAlignment]?, String?) {
|
||||
guard self.expectCharacter("{") else {
|
||||
self.setError(.characterNotFound, message: "Missing { after \\begin{array}")
|
||||
return (nil, nil)
|
||||
}
|
||||
|
||||
self.skipSpaces()
|
||||
|
||||
var columnAlignments = [Table.ColumnAlignment]()
|
||||
var format = ""
|
||||
var foundClosingBrace = false
|
||||
|
||||
while self.hasCharacters {
|
||||
let char = self.nextCharacter()
|
||||
if char == "}" {
|
||||
foundClosingBrace = true
|
||||
break
|
||||
}
|
||||
|
||||
switch char {
|
||||
case "l":
|
||||
columnAlignments.append(.left)
|
||||
format.append(char)
|
||||
case "c":
|
||||
columnAlignments.append(.center)
|
||||
format.append(char)
|
||||
case "r":
|
||||
columnAlignments.append(.right)
|
||||
format.append(char)
|
||||
case "|":
|
||||
format.append(char)
|
||||
case " ":
|
||||
continue
|
||||
default:
|
||||
self.setError(
|
||||
.invalidEnvironment,
|
||||
message: "Unsupported array column format specifier: \(char)"
|
||||
)
|
||||
return (nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if self.error == nil && !foundClosingBrace {
|
||||
self.setError(.characterNotFound, message: "Missing } after array column format")
|
||||
}
|
||||
|
||||
if self.error == nil && columnAlignments.isEmpty {
|
||||
self.setError(.invalidEnvironment, message: "array environment requires column alignment specifiers")
|
||||
}
|
||||
|
||||
return self.error == nil ? (columnAlignments, format) : (nil, nil)
|
||||
}
|
||||
|
||||
func assertNotSpace(_ ch: Character) {
|
||||
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
|
||||
}
|
||||
@@ -1261,13 +1358,20 @@ extension Math {
|
||||
mutating func buildTable(
|
||||
environment: String?,
|
||||
alignment: Table.ColumnAlignment? = nil,
|
||||
columnAlignments: [Table.ColumnAlignment]? = nil,
|
||||
columnFormat: String? = nil,
|
||||
firstList: AtomList?,
|
||||
isRow: Bool
|
||||
) -> Atom? {
|
||||
// Save the current env till an new one gets built.
|
||||
let oldEnv = self.currentEnvironment
|
||||
|
||||
currentEnvironment = Environment(name: environment, alignment: alignment)
|
||||
currentEnvironment = Environment(
|
||||
name: environment,
|
||||
alignment: alignment,
|
||||
columnAlignments: columnAlignments,
|
||||
columnFormat: columnFormat
|
||||
)
|
||||
|
||||
var currentRow = 0
|
||||
var currentCol = 0
|
||||
@@ -1306,7 +1410,10 @@ extension Math {
|
||||
|
||||
var error: ParserError? = self.error
|
||||
let table = AtomFactory.table(
|
||||
withEnvironment: currentEnvironment?.name, alignment: currentEnvironment?.alignment,
|
||||
withEnvironment: currentEnvironment?.name,
|
||||
alignment: currentEnvironment?.alignment,
|
||||
columnAlignments: currentEnvironment?.columnAlignments,
|
||||
columnFormat: currentEnvironment?.columnFormat,
|
||||
rows: rows, error: &error)
|
||||
if table == nil && self.error == nil {
|
||||
self.error = error
|
||||
|
||||
@@ -1052,6 +1052,43 @@ struct ParserTests {
|
||||
#expect(latex == "\\left( \\begin{matrix}x&y\\\\ z&w\\end{matrix}\\right) ")
|
||||
}
|
||||
|
||||
@Test
|
||||
func array() throws {
|
||||
let str = "\\left\\{\\begin{array}{ll}1,&|x|\\leq1,\\\\0,&|x|>1,\\end{array}\\right."
|
||||
let list = try #require(Math.Parser.build(fromString: str))
|
||||
#expect(list.atoms.count == 1)
|
||||
|
||||
let inner = try #require(list.atoms[0] as? Math.Inner)
|
||||
let leftBoundary = try #require(inner.leftBoundary)
|
||||
#expect(leftBoundary.type == .boundary)
|
||||
#expect(leftBoundary.nucleus == "{")
|
||||
#expect(inner.rightBoundary == nil)
|
||||
|
||||
let innerList = try #require(inner.innerList)
|
||||
#expect(innerList.atoms.count == 1)
|
||||
|
||||
let table = try #require(innerList.atoms[0] as? Math.Table)
|
||||
#expect(table.environment == "array")
|
||||
#expect(table.columnFormat == "ll")
|
||||
#expect(table.interRowAdditionalSpacing == 0)
|
||||
#expect(table.interColumnSpacing == 18)
|
||||
#expect(table.numberOfRows == 2)
|
||||
#expect(table.numberOfColumns == 2)
|
||||
#expect(table.alignment(forColumn: 0) == .left)
|
||||
#expect(table.alignment(forColumn: 1) == .left)
|
||||
|
||||
for row in 0..<table.numberOfRows {
|
||||
for column in 0..<table.numberOfColumns {
|
||||
let style = try #require(table.cells[row][column].atoms.first as? Math.Style)
|
||||
#expect(style.level == .text)
|
||||
}
|
||||
}
|
||||
|
||||
let latex = Math.Parser.atomListToString(list)
|
||||
#expect(latex.contains("\\begin{array}{ll}"))
|
||||
#expect(latex.contains("\\right."))
|
||||
}
|
||||
|
||||
@Test
|
||||
func defaultTable() throws {
|
||||
let str = "x \\\\ y"
|
||||
|
||||
Reference in New Issue
Block a user