Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manifest.toml
9 changes: 8 additions & 1 deletion src/parser/commands_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ relation_commands = split(raw"""
\preceq \succeq \simeq \mid
\ll \gg \asymp \parallel
\subset \supset \approx \bowtie
\subseteq \supseteq \cong \Join
\subseteq \supseteq \cong \join
\sqsubset \sqsupset \neq \smile
\sqsubseteq \sqsupseteq \doteq \frown
\in \ni \propto \vdash
Expand Down Expand Up @@ -110,5 +110,12 @@ punctuation_symbols = split(raw", ; . !")
delimiter_symbols = split(raw"| / ( ) [ ] < >")
font_names = split(raw"rm cal it tt sf bf default bb frak scr regular")

##
## Env
##

supported_env = split("matrix pmatrix bmatrix")


# TODO Add to the parser what come below, if needed
wide_accent_commands = split(raw"\widehat \widetilde \widebar")
7 changes: 7 additions & 0 deletions src/parser/commands_registration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ function get_symbol_char(command)
return first(latex_symbols[command])
end

is_supported(env) = env in supported_env

# Numbers
for char in join(0:9)
symbol_to_canonical[char] = TeXExpr(:digit, char)
Expand All @@ -63,6 +65,10 @@ end
command_to_canonical[raw"\frac"] = TeXExpr(:argument_gatherer, [:frac, 2])
command_to_canonical[raw"\sqrt"] = TeXExpr(:argument_gatherer, [:sqrt, 1])

# Commands for env
command_to_canonical[raw"\begin"] = TeXExpr(:argument_gatherer, [:begin_env, 1])
command_to_canonical[raw"\end"] = TeXExpr(:argument_gatherer, [:end_env, 1])

##
## Commands from the commands_data.jl file
##
Expand Down Expand Up @@ -118,6 +124,7 @@ for command in combining_accents
command_to_canonical[command] = TeXExpr(:argument_gatherer, [:combining_accent, 2, symbol_expr])
end


for symbol in punctuation_symbols
symbol = first(symbol)
symbol_to_canonical[symbol] = TeXExpr(:punctuation, symbol)
Expand Down
96 changes: 79 additions & 17 deletions src/parser/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function show_state(io::IO, stack, position, data)
if position > lastindex(data)
println(io, "after the end of the data")
println(io, data)
else
elseif position > 0
# Compute the index of the char from the byte index
position_to_id = zeros(Int, lastindex(data))
id = 0
Expand Down Expand Up @@ -137,7 +137,7 @@ _begin_group!(stack, p, data) = push!(stack, TeXExpr(:group))

function _end_group!(stack, p, data)
current_head(stack) != :group && throw(
TeXParseError("Unexpected '}' at position $(p-1)", stack, p, data))
TeXParseError("unexpected '}'", stack, p, data))
group = pop!(stack)

# Remplace empty groups by a zero-width space
Expand All @@ -148,28 +148,78 @@ function _end_group!(stack, p, data)
group = first(group.args)
end

if requirement(stack) == :argument
push_to_current!(stack, group)
push_to_current!(stack, group)

if requirement(stack) == :argument
command_builder = current(stack)
head, required_n_args = command_builder.args[1:2]
args = command_builder.args[3:end]

if required_n_args == length(args)
pop!(stack)
command = TeXExpr(head, args)
push_to_current!(stack, command)
if head == :begin_env
# Transform the content of the argument back to a single string
env_name = String(Char.(first(args).args))
!is_supported(env_name) && throw(
TeXParseError(
"env '$env_name' is not supported",
stack, p, data))
push!(stack, TeXExpr(:env, Any[env_name]))
push!(stack, TeXExpr(:env_row))
push!(stack, TeXExpr(:env_cell))
elseif head == :end_env
env_name = String(Char.(args[1].args))
current(stack).head != :env_cell && throw(
TeXParseError(
"unexpected end of environnement '$env_name'",
stack, p, data))

cell = pop!(stack)
push_to_current!(stack, cell)
row = pop!(stack)
push_to_current!(stack, row)
open_env_name = current(stack).args[1]
env_name != open_env_name && throw(
TeXParseError(
"found an end for environnement '$env_name', but it is not matching the currently open env",
stack, p, data))

# Make a matrix of the arguments
env_data = pop!(stack)
rows = env_data.args[2:end]
n_cols = maximum([length(row.args) for row in rows])
matrix = fill(TeXExpr(:space, 0), length(rows), n_cols)
for (i, row) in enumerate(rows)
for (j, cell) in enumerate(row.args)
# Remove the :env_cell wrapper
if length(cell.args) == 1
matrix[i, j] = first(cell.args)
else
matrix[i, j] = TeXExpr(:group, cell.args)
end
end
end

env = TeXExpr(:env, [env_name, matrix])
push_to_current!(stack, env)
else
command = TeXExpr(head, args)
push_to_current!(stack, command)
end
end
else
push_to_current!(stack, group)
end
end

function _push_char!(stack, p, data)
if current_head(stack) == :skip_char
pop!(stack)
elseif isvalid(data, p-1)
char = data[prevind(data, p)]
current_head(stack) == :skip_char && return pop!(stack)
!isvalid(data, p-1) && return

char = data[prevind(data, p)]

if current_head(stack) == :env_cell && char == '&'
push_to_current!(stack, pop!(stack))
push!(stack, TeXExpr(:env_cell))
else
push_to_current!(stack, canonical_expr(char))
end
end
Expand All @@ -192,7 +242,10 @@ function _setup_decorated!(stack, p, data)
end
end

_begin_command_builder!(stack, p, data) = push!(stack, TeXExpr(:command_builder))
function _begin_command_builder!(stack, p, data)
current_head(stack) == :skip_char && return pop!(stack)
push!(stack, TeXExpr(:command_builder))
end

function _end_command_builder!(stack, p, data)
if current_head(stack) == :command_builder
Expand Down Expand Up @@ -220,6 +273,16 @@ function _end_command_builder!(stack, p, data)
TeXParseError("unexpected '\\right' at position $(p-1)",
stack, p, data))
push!(stack, TeXExpr(:right_delimiter))
elseif command_name == "\\"
current(stack).head != :env_cell && throw(
TeXParseError("'\\' for newline is only supported inside env",
stack, p, data))
current_cell = pop!(stack)
push_to_current!(stack, current_cell)
current_row = pop!(stack)
push_to_current!(stack, current_row)
push!(stack, TeXExpr(:env_row))
push!(stack, TeXExpr(:env_cell))
elseif haskey(command_to_canonical, command)
expr = command_to_canonical[command]

Expand All @@ -235,7 +298,7 @@ function _end_command_builder!(stack, p, data)
stack, p, data))
end

if skip_char
if skip_char
push!(stack, TeXExpr(:skip_char))
end
end
Expand Down Expand Up @@ -290,10 +353,9 @@ context = Automa.CodeGenContext()
end

if length(stack) > 1
err = TeXParseError(
throw(TeXParseError(
"end of string reached with unfinished $(current(stack).head)",
stack, p_eof, data)
throw(err)
stack, p_eof, data))
end

final_expr = current(stack)
Expand Down
49 changes: 47 additions & 2 deletions src/parser/texexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,53 @@ Base.copy(texexpr::TeXExpr) = TeXExpr(texexpr.head, deepcopy(texexpr.args))
AbstractTrees.children(texexpr::TeXExpr) = texexpr.args
AbstractTrees.printnode(io::IO, texexpr::TeXExpr) = print(io, "TeXExpr :$(texexpr.head)")

function Base.show(io::IO, texexpr::TeXExpr)
print_tree(io, texexpr, maxdepth=10)
to_latex(::Nothing) = nothing

function to_latex(texexpr::TeXExpr)
head = texexpr.head
args = texexpr.args

head in [:char, :digit, :punctuation, :symbol] && return string(first(args))
head == :function && return "\\$(first(args))"
head == :frac && return "\\frac{$(to_latex(args[1]))}{$(to_latex(args[2]))}"
head == :group && return join(to_latex.(args), " ")
head == :sqrt && return "\\sqrt{$(to_latex(first(args)))}"
head == :spaced && return string(first(first(args).args))
head == :space && return ""

if head in [:decorated, :integral, :underover]
core, sub, sup = to_latex.(args)

if !isnothing(sub)
if length(sub) == 1
core *= "_$sub"
else
core *= "_{$sub}"
end
end

if !isnothing(sup)
if length(sup) == 1
core *= "^$sup"
else
core *= "^{$sup}"
end
end
return core
end

# TODO :accent, :space properly

# Fallback
return "to_latex($texexpr)"
end

function Base.show(io::IO, ::MIME"text/plain", texexpr::TeXExpr)
if haskey(io, :compact) && io[:compact]
print(io, "TeX\"$(to_latex(texexpr))\"")
else
return print_tree(io, texexpr, maxdepth=10)
end
end

function Base.:(==)(tex1::TeXExpr, tex2::TeXExpr)
Expand Down