Skip to content

Parser: numeric literals & "..."/$...$ leaves; copy-to-clipboard for input and image#10

Merged
psaegert merged 2 commits into
masterfrom
copilot/fix-invalid-character-in-expression
Apr 21, 2026
Merged

Parser: numeric literals & "..."/$...$ leaves; copy-to-clipboard for input and image#10
psaegert merged 2 commits into
masterfrom
copilot/fix-invalid-character-in-expression

Conversation

Copilot AI commented Apr 21, 2026

Copy link
Copy Markdown

The tokenizer rejected digits, there was no way to put verbatim text or raw LaTeX into a single node, and the UI only supported downloading — not copying — the expression or rendered image.

Parser

  • Numbers (integers and decimals) are now leaf tokens, so sin(x - y + (c / (a + 5))) parses.
  • "..." → single leaf rendered as \text{…} with contents escaped (displayed literally).
  • $...$ → single leaf passed through to KaTeX as raw LaTeX.
  • Dropped the upfront ALLOWED_CHARACTERS_REGEX gate; the tokenizer is now the single source of truth for valid input, which keeps the new delimiters (", $) from needing regex maintenance and lets it skip any whitespace (not just ' ').
  • canvas.js no longer strips whitespace before tokenizing, so spaces inside "..." are preserved.
a + "3x"             → [a] + [3x]            (one leaf, literal)
x + $\frac{1}{2}$    → [x] + [½]             (one leaf, rendered LaTeX)
sin(x - y + (c/(a+5)))                       (now parses)

Copy actions

  • Copy icon added inside the input (next to clear) — writes input.value via navigator.clipboard.writeText, with a brief checkmark confirmation and execCommand('copy') fallback for insecure contexts.
  • Copy button added to the existing export popover next to Download, sharing the Format (PNG/SVG) and Scale controls. PNG uses ClipboardItem({'image/png': blob}); SVG uses image/svg+xml with a text/plain fallback. A small status line reports success/failure.
  • Export code refactored into buildPngBlob / buildSvgString so download and copy share one production path.

Token model

New literal token shape consumed by tree.js (getTokenArity/Label/Latex):

{ type: 'literal', kind: 'text' | 'latex', value: '...' }

Screenshot

demo

@psaegert

Copy link
Copy Markdown
Owner

@copilot The latex in $$ should be rendered though. It's just that it should be put into one node instead if parsed and split into multiple nodes.

Copilot AI commented Apr 21, 2026

Copy link
Copy Markdown
Author

@copilot The latex in $$ should be rendered though. It's just that it should be put into one node instead if parsed and split into multiple nodes.

It does render — the earlier screenshot was captured in a sandbox where the jsdelivr CDN was blocked, so the node fell back to raw text. getTokenLatex passes the contents of $...$ straight to KaTeX (no escaping, no \text{…} wrapper), so $\alpha^2$ becomes a single node rendering α². New screenshot with KaTeX loaded: https://github.com/user-attachments/assets/b6067384-8aca-482b-8779-701bb0b4d860

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.jsdelivr.net
    • Triggering command: /usr/bin/python3 python3 -c import urllib.request, os base='REDACTED' for f in ['katex.min.css','katex.min.js']: urllib.request.urlretrieve(base+f, f) print(f, os.path.getsize(f)) (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from psaegert April 21, 2026 15:42
@psaegert psaegert marked this pull request as ready for review April 21, 2026 15:43
Copilot AI review requested due to automatic review settings April 21, 2026 15:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for numeric literals and verbatim text / raw LaTeX leaf nodes in the expression parser, and introduces copy-to-clipboard actions for both the input expression and exported render output.

Changes:

  • Extend tokenizer/parser to support numeric literals plus "..." (escaped text) and $...$ (raw LaTeX) as leaf tokens.
  • Update tree rendering to handle the new literal token type and render numeric literals as-is.
  • Add UI + logic for copying the input expression and copying exported PNG/SVG to the clipboard (sharing the export pipeline with download).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
infixToPostfix.js Tokenizer/parsing updates for numbers and "..." / $...$ literal leaf tokens.
tree.js Renders new literal tokens and numeric literals correctly as LaTeX.
index.html Adds “Copy expression” icon and “Copy” action in export popover with status line.
canvas.js Refactors export generation into reusable builders and implements clipboard copy flows.
app.css Layout/styling updates for new copy buttons and export status text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread infixToPostfix.js
Comment on lines +41 to +45
function isLeafToken(token) {
return isVariableToken(token) || isNumberToken(token) || isLiteralToken(token) ||
(isCustomToken(token) && typeof token.arity === 'number' && token.arity === 0)
}

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isLeafToken is defined but never used in this file (only its declaration exists). Consider removing it to avoid dead code, or using it where leaf-token checks are currently duplicated (e.g., in parsePrimary / postfix emission).

Suggested change
function isLeafToken(token) {
return isVariableToken(token) || isNumberToken(token) || isLiteralToken(token) ||
(isCustomToken(token) && typeof token.arity === 'number' && token.arity === 0)
}

Copilot uses AI. Check for mistakes.
Comment thread canvas.js
Comment on lines +483 to +487
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
navigator.clipboard.writeText(text).then(showCopied, function () {
// Ignore clipboard rejection (e.g. insecure context).
})
} else {

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navigator.clipboard.writeText(...) rejection handler is currently a no-op, so in insecure contexts / permission-denied cases you never fall back to the execCommand('copy') path (despite the comment/PR intent). Consider invoking the fallback from the rejection handler (or at least surfacing an error state).

Copilot uses AI. Check for mistakes.
Comment thread canvas.js
Comment on lines +351 to +353
return navigator.clipboard.writeText(svgString).then(function () {
setExportStatus('Copied as text', false)
})

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the canWriteClipboardItems() branch, the fallback path calls navigator.clipboard.writeText(svgString) without checking that writeText exists. If a browser supports clipboard.write() but not writeText(), this will throw. Consider guarding writeText (or using another fallback) and setting an appropriate status message if it's unavailable.

Suggested change
return navigator.clipboard.writeText(svgString).then(function () {
setExportStatus('Copied as text', false)
})
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
return navigator.clipboard.writeText(svgString).then(function () {
setExportStatus('Copied as text', false)
})
}
setExportStatus('Clipboard unsupported', true)

Copilot uses AI. Check for mistakes.
@psaegert psaegert merged commit 2aa2f46 into master Apr 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants