diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2251642 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Manifest.toml \ No newline at end of file diff --git a/src/parser/commands_data.jl b/src/parser/commands_data.jl index 090abc1..157c31a 100644 --- a/src/parser/commands_data.jl +++ b/src/parser/commands_data.jl @@ -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 @@ -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") \ No newline at end of file diff --git a/src/parser/commands_registration.jl b/src/parser/commands_registration.jl index 3986bbe..09f0001 100644 --- a/src/parser/commands_registration.jl +++ b/src/parser/commands_registration.jl @@ -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) @@ -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 ## @@ -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) diff --git a/src/parser/parser.jl b/src/parser/parser.jl index 80bda78..f50273c 100644 --- a/src/parser/parser.jl +++ b/src/parser/parser.jl @@ -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 @@ -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 @@ -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 @@ -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 @@ -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] @@ -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 @@ -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) diff --git a/src/parser/texexpr.jl b/src/parser/texexpr.jl index 4be350b..a0f9099 100644 --- a/src/parser/texexpr.jl +++ b/src/parser/texexpr.jl @@ -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)