Skip to content

Commit

Permalink
Add optional support for math syntax (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatanklosko authored Nov 24, 2023
1 parent 82a8150 commit bf4092b
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 5 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [Pure links](#pure-links)
- [Wikilinks...](#wikilinks)
- [Sub and Sup HTML Elements](#sub-and-sup-html-elements)
- [Mathematical expressions](#mathematical-expressions)
- [Github Flavored Markdown](#github-flavored-markdown)
- [Strike Through](#strike-through)
- [GFM Tables](#gfm-tables)
Expand Down Expand Up @@ -170,6 +171,34 @@ But by specifying `sub_sup: true`
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}
```

### Mathematical expressions

> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.
This feature is not enabled by default but can be enabled with the option `math: true`.

When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.

#### Inline expressions

Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).

```elixir
iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}
```

There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).

#### Expressions as blocks

Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).

```elixir
iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}
```

### Github Flavored Markdown

GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
Expand Down
24 changes: 24 additions & 0 deletions lib/earmark_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ defmodule EarmarkParser do
iex(10)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^", sub_sup: true)
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}
### Mathematical expressions
> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.
This feature is not enabled by default but can be enabled with the option `math: true`.
When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.
#### Inline expressions
Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).
iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}
There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).
#### Expressions as blocks
Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).
iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}
### Github Flavored Markdown
GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
Expand Down
29 changes: 28 additions & 1 deletion lib/earmark_parser/ast/inline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ defmodule EarmarkParser.Ast.Inline do
converter_for_sub: &converter_for_sub/1,
converter_for_sup: &converter_for_sup/1,
#
converter_for_math_display: &converter_for_math_display/1,
converter_for_math_inline: &converter_for_math_inline/1,
#
converter_for_code: &converter_for_code/1,
converter_for_br: &converter_for_br/1,
converter_for_inline_ial: &converter_for_inline_ial/1,
Expand All @@ -78,7 +81,7 @@ defmodule EarmarkParser.Ast.Inline do
# Converters
#
######################
@escape_rule ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>])}
@escape_rule ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>$])}
def converter_for_escape({src, lnb, context, use_linky?}) do
if match = Regex.run(@escape_rule, src) do
[match, escaped] = match
Expand Down Expand Up @@ -227,6 +230,30 @@ defmodule EarmarkParser.Ast.Inline do

def converter_for_sup(_), do: nil

@math_inline_rgx ~r{\A\$(?=[^\s$])([\s\S]*?[^\s\\])\$}
def converter_for_math_inline({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_inline_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_inline(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end

def converter_for_math_inline(_), do: nil

@math_display_rgx ~r{\A\$\$([\s\S]+?)\$\$}
def converter_for_math_display({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_display_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_display(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end

def converter_for_math_display(_), do: nil

@squash_ws ~r{\s+}
@code ~r{^
(`+) # $1 = Opening run of `
Expand Down
10 changes: 8 additions & 2 deletions lib/earmark_parser/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,22 @@ defmodule EarmarkParser.Context do
else
""
end
math =
if options.math do
"$"
else
""
end
rule_updates =
if options.gfm do
rules = [
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{subsup}]|https?://| \{2,\}\n|$)}
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{math}#{subsup}]|https?://| \{2,\}\n|$)}
]

if options.breaks do
break_updates = [
br: ~r{^ *\n(?!\s*$)},
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{subsup}]|https?://| *\n|$)}
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{math}#{subsup}]|https?://| *\n|$)}
]

Keyword.merge(rules, break_updates)
Expand Down
11 changes: 10 additions & 1 deletion lib/earmark_parser/helpers/ast_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule EarmarkParser.Helpers.AstHelpers do
[{t, merge_attrs(a, atts), c, m}|tags]
end
def augment_tag_with_ial([], _atts) do
[]
[]
end

@doc false
Expand All @@ -41,6 +41,15 @@ defmodule EarmarkParser.Helpers.AstHelpers do
{"class", classes |> Enum.join(" ")}
end

@doc false
def math_inline(text, lnb) do
emit("code", text, [class: "math-inline"], %{line: lnb})
end

def math_display(text, lnb) do
emit("code", text, [class: "math-display"], %{line: lnb})
end

@doc false
def codespan(text, lnb) do
emit("code", text, [class: "inline"], %{line: lnb})
Expand Down
3 changes: 2 additions & 1 deletion lib/earmark_parser/options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule EarmarkParser.Options do
messages: MapSet.new([]),
pure_links: true,
sub_sup: false,
math: false,

# deprecated
pedantic: false,
Expand Down Expand Up @@ -57,7 +58,7 @@ defmodule EarmarkParser.Options do
# deprecated
pedantic: boolean(),
smartypants: boolean(),
timeout: nil | non_neg_integer()
timeout: nil | non_neg_integer()

}

Expand Down
Loading

0 comments on commit bf4092b

Please sign in to comment.