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(
|
static func table(
|
||||||
withEnvironment env: String?,
|
withEnvironment env: String?,
|
||||||
alignment: Table.ColumnAlignment? = nil,
|
alignment: Table.ColumnAlignment? = nil,
|
||||||
|
columnAlignments: [Table.ColumnAlignment]? = nil,
|
||||||
|
columnFormat: String? = nil,
|
||||||
rows: [[AtomList]],
|
rows: [[AtomList]],
|
||||||
error: inout ParserError?
|
error: inout ParserError?
|
||||||
) -> Atom? {
|
) -> Atom? {
|
||||||
let table = Table(environment: env ?? "")
|
let table = Table(environment: env ?? "", columnFormat: columnFormat)
|
||||||
|
|
||||||
for i in 0..<rows.count {
|
for i in 0..<rows.count {
|
||||||
let row = rows[i]
|
let row = rows[i]
|
||||||
@@ -709,6 +711,38 @@ extension Math {
|
|||||||
} else {
|
} else {
|
||||||
return table
|
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" {
|
} else if env == "eqalign" || env == "split" || env == "aligned" {
|
||||||
if table.numberOfColumns != 2 {
|
if table.numberOfColumns != 2 {
|
||||||
let message = "\(env) environment can only have 2 columns"
|
let message = "\(env) environment can only have 2 columns"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension Math {
|
|||||||
var alignments: [ColumnAlignment]
|
var alignments: [ColumnAlignment]
|
||||||
var cells: [[AtomList]]
|
var cells: [[AtomList]]
|
||||||
var environment: String
|
var environment: String
|
||||||
|
var columnFormat: String?
|
||||||
var interColumnSpacing: CGFloat
|
var interColumnSpacing: CGFloat
|
||||||
var interRowAdditionalSpacing: CGFloat
|
var interRowAdditionalSpacing: CGFloat
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ extension Math {
|
|||||||
row.map { AtomList($0) }
|
row.map { AtomList($0) }
|
||||||
}
|
}
|
||||||
self.environment = table.environment
|
self.environment = table.environment
|
||||||
|
self.columnFormat = table.columnFormat
|
||||||
self.interColumnSpacing = table.interColumnSpacing
|
self.interColumnSpacing = table.interColumnSpacing
|
||||||
self.interRowAdditionalSpacing = table.interRowAdditionalSpacing
|
self.interRowAdditionalSpacing = table.interRowAdditionalSpacing
|
||||||
|
|
||||||
@@ -42,12 +44,14 @@ extension Math {
|
|||||||
alignments: [ColumnAlignment] = [],
|
alignments: [ColumnAlignment] = [],
|
||||||
cells: [[AtomList]] = [],
|
cells: [[AtomList]] = [],
|
||||||
environment: String = "",
|
environment: String = "",
|
||||||
|
columnFormat: String? = nil,
|
||||||
interColumnSpacing: CGFloat = 0,
|
interColumnSpacing: CGFloat = 0,
|
||||||
interRowAdditionalSpacing: CGFloat = 0
|
interRowAdditionalSpacing: CGFloat = 0
|
||||||
) {
|
) {
|
||||||
self.alignments = alignments
|
self.alignments = alignments
|
||||||
self.cells = cells
|
self.cells = cells
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
|
self.columnFormat = columnFormat
|
||||||
self.interColumnSpacing = interColumnSpacing
|
self.interColumnSpacing = interColumnSpacing
|
||||||
self.interRowAdditionalSpacing = interRowAdditionalSpacing
|
self.interRowAdditionalSpacing = interRowAdditionalSpacing
|
||||||
super.init(type: .table)
|
super.init(type: .table)
|
||||||
|
|||||||
@@ -31,12 +31,21 @@ extension Math {
|
|||||||
var ended: Bool
|
var ended: Bool
|
||||||
var numberOfRows: Int
|
var numberOfRows: Int
|
||||||
var alignment: Table.ColumnAlignment? // Optional alignment for starred matrix environments
|
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.name = name
|
||||||
self.numberOfRows = 0
|
self.numberOfRows = 0
|
||||||
self.ended = false
|
self.ended = false
|
||||||
self.alignment = alignment
|
self.alignment = alignment
|
||||||
|
self.columnAlignments = columnAlignments
|
||||||
|
self.columnFormat = columnFormat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,13 +528,16 @@ extension Math {
|
|||||||
if let table = atom as? Table {
|
if let table = atom as? Table {
|
||||||
if !table.environment.isEmpty {
|
if !table.environment.isEmpty {
|
||||||
str += "\\begin{\(table.environment)}"
|
str += "\\begin{\(table.environment)}"
|
||||||
|
if table.environment == "array" {
|
||||||
|
str += "{\(table.columnFormat ?? "")}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..<table.numberOfRows {
|
for i in 0..<table.numberOfRows {
|
||||||
let row = table.cells[i]
|
let row = table.cells[i]
|
||||||
for j in 0..<row.count {
|
for j in 0..<row.count {
|
||||||
let cell = row[j]
|
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 {
|
if cell.atoms.count >= 1 && cell.atoms[0].type == Math.AtomType.style {
|
||||||
// remove first atom
|
// remove first atom
|
||||||
cell.atoms.removeFirst()
|
cell.atoms.removeFirst()
|
||||||
@@ -797,7 +809,30 @@ extension Math {
|
|||||||
if env == nil {
|
if env == nil {
|
||||||
return 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
|
return table
|
||||||
} else if command == "color" {
|
} else if command == "color" {
|
||||||
// A color command has 2 arguments
|
// A color command has 2 arguments
|
||||||
@@ -1156,16 +1191,25 @@ extension Math {
|
|||||||
if let env = self.readEnvironment() {
|
if let env = self.readEnvironment() {
|
||||||
// Check if this is a starred matrix environment and read optional alignment
|
// Check if this is a starred matrix environment and read optional alignment
|
||||||
var alignment: Table.ColumnAlignment? = nil
|
var alignment: Table.ColumnAlignment? = nil
|
||||||
|
var columnAlignments: [Table.ColumnAlignment]? = nil
|
||||||
|
var columnFormat: String? = nil
|
||||||
if env.hasSuffix("*") {
|
if env.hasSuffix("*") {
|
||||||
alignment = self.readOptionalAlignment()
|
alignment = self.readOptionalAlignment()
|
||||||
if self.error != nil {
|
if self.error != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
} else if env == "array" {
|
||||||
|
(columnAlignments, columnFormat) = self.readArrayColumnFormat()
|
||||||
|
if self.error != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let table = self.buildTable(
|
let table = self.buildTable(
|
||||||
environment: env,
|
environment: env,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
|
columnAlignments: columnAlignments,
|
||||||
|
columnFormat: columnFormat,
|
||||||
firstList: nil,
|
firstList: nil,
|
||||||
isRow: false
|
isRow: false
|
||||||
)
|
)
|
||||||
@@ -1254,6 +1298,59 @@ extension Math {
|
|||||||
return alignment
|
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) {
|
func assertNotSpace(_ ch: Character) {
|
||||||
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
|
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
|
||||||
}
|
}
|
||||||
@@ -1261,13 +1358,20 @@ extension Math {
|
|||||||
mutating func buildTable(
|
mutating func buildTable(
|
||||||
environment: String?,
|
environment: String?,
|
||||||
alignment: Table.ColumnAlignment? = nil,
|
alignment: Table.ColumnAlignment? = nil,
|
||||||
|
columnAlignments: [Table.ColumnAlignment]? = nil,
|
||||||
|
columnFormat: String? = nil,
|
||||||
firstList: AtomList?,
|
firstList: AtomList?,
|
||||||
isRow: Bool
|
isRow: Bool
|
||||||
) -> Atom? {
|
) -> Atom? {
|
||||||
// Save the current env till an new one gets built.
|
// Save the current env till an new one gets built.
|
||||||
let oldEnv = self.currentEnvironment
|
let oldEnv = self.currentEnvironment
|
||||||
|
|
||||||
currentEnvironment = Environment(name: environment, alignment: alignment)
|
currentEnvironment = Environment(
|
||||||
|
name: environment,
|
||||||
|
alignment: alignment,
|
||||||
|
columnAlignments: columnAlignments,
|
||||||
|
columnFormat: columnFormat
|
||||||
|
)
|
||||||
|
|
||||||
var currentRow = 0
|
var currentRow = 0
|
||||||
var currentCol = 0
|
var currentCol = 0
|
||||||
@@ -1306,7 +1410,10 @@ extension Math {
|
|||||||
|
|
||||||
var error: ParserError? = self.error
|
var error: ParserError? = self.error
|
||||||
let table = AtomFactory.table(
|
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)
|
rows: rows, error: &error)
|
||||||
if table == nil && self.error == nil {
|
if table == nil && self.error == nil {
|
||||||
self.error = error
|
self.error = error
|
||||||
|
|||||||
@@ -1052,6 +1052,43 @@ struct ParserTests {
|
|||||||
#expect(latex == "\\left( \\begin{matrix}x&y\\\\ z&w\\end{matrix}\\right) ")
|
#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
|
@Test
|
||||||
func defaultTable() throws {
|
func defaultTable() throws {
|
||||||
let str = "x \\\\ y"
|
let str = "x \\\\ y"
|
||||||
|
|||||||
Reference in New Issue
Block a user