Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol terminal ansi cached_parser.ex
Raw

lib/raxol/terminal/ansi/cached_parser.ex

defmodule Raxol.Terminal.ANSI.CachedParser do
@moduledoc """
Optimized ANSI parser with caching for common sequences.
Phase 1 optimization to reduce memory overhead and improve
performance for frequently used escape sequences.
"""
@type parsed_token ::
{:text, binary()}
| {:csi, binary(), binary()}
| {:osc, binary()}
| {:dcs, binary()}
| {:escape, binary()}
# Cache for common ANSI sequences
# These are the most frequently used sequences in terminal applications
@common_sequences %{
# SGR (Select Graphic Rendition) - Colors and styles
# Reset
"\e[0m" => [{:csi, "0", "m"}],
# Reset (short form)
"\e[m" => [{:csi, "", "m"}],
# Bold
"\e[1m" => [{:csi, "1", "m"}],
# Dim
"\e[2m" => [{:csi, "2", "m"}],
# Italic
"\e[3m" => [{:csi, "3", "m"}],
# Underline
"\e[4m" => [{:csi, "4", "m"}],
# Reverse
"\e[7m" => [{:csi, "7", "m"}],
# Normal intensity
"\e[22m" => [{:csi, "22", "m"}],
# Not italic
"\e[23m" => [{:csi, "23", "m"}],
# Not underlined
"\e[24m" => [{:csi, "24", "m"}],
# Not reverse
"\e[27m" => [{:csi, "27", "m"}],
# Common colors
# Black foreground
"\e[30m" => [{:csi, "30", "m"}],
# Red foreground
"\e[31m" => [{:csi, "31", "m"}],
# Green foreground
"\e[32m" => [{:csi, "32", "m"}],
# Yellow foreground
"\e[33m" => [{:csi, "33", "m"}],
# Blue foreground
"\e[34m" => [{:csi, "34", "m"}],
# Magenta foreground
"\e[35m" => [{:csi, "35", "m"}],
# Cyan foreground
"\e[36m" => [{:csi, "36", "m"}],
# White foreground
"\e[37m" => [{:csi, "37", "m"}],
# Default foreground
"\e[39m" => [{:csi, "39", "m"}],
# Black background
"\e[40m" => [{:csi, "40", "m"}],
# Red background
"\e[41m" => [{:csi, "41", "m"}],
# Green background
"\e[42m" => [{:csi, "42", "m"}],
# Yellow background
"\e[43m" => [{:csi, "43", "m"}],
# Blue background
"\e[44m" => [{:csi, "44", "m"}],
# Magenta background
"\e[45m" => [{:csi, "45", "m"}],
# Cyan background
"\e[46m" => [{:csi, "46", "m"}],
# White background
"\e[47m" => [{:csi, "47", "m"}],
# Default background
"\e[49m" => [{:csi, "49", "m"}],
# Cursor movement
# Cursor up
"\e[A" => [{:csi, "", "A"}],
# Cursor down
"\e[B" => [{:csi, "", "B"}],
# Cursor right
"\e[C" => [{:csi, "", "C"}],
# Cursor left
"\e[D" => [{:csi, "", "D"}],
# Cursor home
"\e[H" => [{:csi, "", "H"}],
# Cursor to 1,1
"\e[1;1H" => [{:csi, "1;1", "H"}],
# Erase functions
# Erase line
"\e[K" => [{:csi, "", "K"}],
# Erase to end of line
"\e[0K" => [{:csi, "0", "K"}],
# Erase to start of line
"\e[1K" => [{:csi, "1", "K"}],
# Erase entire line
"\e[2K" => [{:csi, "2", "K"}],
# Erase display
"\e[J" => [{:csi, "", "J"}],
# Erase to end of screen
"\e[0J" => [{:csi, "0", "J"}],
# Erase to start of screen
"\e[1J" => [{:csi, "1", "J"}],
# Erase entire screen
"\e[2J" => [{:csi, "2", "J"}],
# Mode changes
# Show cursor
"\e[?25h" => [{:csi, "?25", "h"}],
# Hide cursor
"\e[?25l" => [{:csi, "?25", "l"}],
# Use alternate screen
"\e[?47h" => [{:csi, "?47", "h"}],
# Use main screen
"\e[?47l" => [{:csi, "?47", "l"}],
# Enable alternate screen
"\e[?1049h" => [{:csi, "?1049", "h"}],
# Disable alternate screen
"\e[?1049l" => [{:csi, "?1049", "l"}]
}
@doc """
Parses ANSI escape sequences with caching optimization.
First checks if the input exactly matches a common cached sequence,
then falls back to full parsing for complex inputs.
"""
@spec parse(binary()) :: list(parsed_token())
def parse(input) when is_binary(input) do
case Map.get(@common_sequences, input) do
nil ->
# Not a simple cached sequence, use full parser
parse_with_cache_lookup(input)
cached_result ->
# Direct cache hit for entire input
cached_result
end
end
def parse(_), do: []
# Parse with cache lookup for subsequences
defp parse_with_cache_lookup(input) do
case check_for_cached_prefix(input) do
{cached_result, remaining} ->
# Found cached prefix, parse remainder
cached_result ++ parse_with_cache_lookup(remaining)
nil ->
# No cached prefix, use original parser
parse_bytes(input, [], [])
|> Enum.reverse()
end
end
# Check if input starts with any cached sequence
defp check_for_cached_prefix(input) do
@common_sequences
|> Enum.find_value(fn {cached_seq, result} ->
if String.starts_with?(input, cached_seq) do
remaining = String.slice(input, byte_size(cached_seq)..-1//1)
{result, remaining}
else
nil
end
end)
end
# Original parsing logic for non-cached sequences
# (Simplified version - delegates to original parser for complex cases)
defp parse_bytes(input, _text_acc, _acc) do
# For Phase 1, delegate to original parser
# This ensures correctness while we optimize the common cases
Raxol.Terminal.ANSI.Parser.parse(input)
|> Enum.reverse()
end
@doc """
Benchmark comparison between cached and original parser.
"""
def benchmark_comparison(input, iterations \\ 10_000) do
# Warmup
Enum.each(1..100, fn _ ->
_ = parse(input)
_ = Raxol.Terminal.ANSI.Parser.parse(input)
end)
# Benchmark cached parser
{cached_time, _} =
:timer.tc(fn ->
Enum.each(1..iterations, fn _ -> parse(input) end)
end)
# Benchmark original parser
{original_time, _} =
:timer.tc(fn ->
Enum.each(1..iterations, fn _ ->
Raxol.Terminal.ANSI.Parser.parse(input)
end)
end)
cached_avg = cached_time / iterations
original_avg = original_time / iterations
improvement = (original_avg - cached_avg) / original_avg * 100
%{
input: input,
cached_time_us: cached_avg,
original_time_us: original_avg,
improvement_percent: improvement,
cached_hit: Map.has_key?(@common_sequences, input)
}
end
@doc """
Get statistics about cache coverage.
"""
def cache_stats do
%{
cached_sequences: map_size(@common_sequences),
sequence_types: %{
# Color and style sequences
sgr_colors: 21,
# Cursor positioning
cursor_movement: 6,
# Screen/line clearing
erase_functions: 8,
# Terminal mode switches
mode_changes: 6
},
total_cache_entries: map_size(@common_sequences)
}
end
end