from fastcore.test import *
from basedpl import Array, apl
from IPython.utils.capture import capture_output
import numpy as npNotebook magics
bAsedPL adds %apl and %%apl magics to IPython. Both evaluate in basedpl.apl, the workspace that Python code also uses. The magic renders the captured output and loads the language bar.
The line magic returns a native array or function. The cell magic displays what the APL prompt would show, including implicit expression results.
Rendering output
AplOut
def AplOut(
*args, **kwargs
):APL output text, displayed in the SAX2 font where HTML is available.
display_events
def display_events(
events
):Display captured output in order, using MIME bundles for rich results.
AplOut preserves APL spacing. Its HTML representation escapes the output before putting it in a <pre> element.
out = AplOut('1 < 2')
assert '<' in out._repr_html_()
out1 < 2
The magics
The magics evaluate in basedpl.apl, so Python calls through apl share their workspace. The language bar and SAX2 stylesheet are loaded once for each magic instance.
APLMagic
def APLMagic():IPython APL magics that evaluate in the workspace basedpl.apl.
APLMagic.apl
def apl(
line, cell:NoneType=None
):Evaluate a line as a native value or display a cell as an APL session.
Cell output comes from apl(code, 'repl'), which includes implicit expression results. Line output comes from apl(code, 'explicit'), which captures explicit output and returns native values. A trailing ; suppresses all cell display without skipping execution. Output produced before an error is displayed before the error is raised.
Tab completes user and system names in APL input. Completion reads the workspace without evaluating anything.
APLMagic.complete
def complete(
context
):Complete visible APL names in line and cell magics.
create_magic
def create_magic(
shell:NoneType=None
):Register line and cell magics with shell. They evaluate in basedpl.apl.
The cell magic displays each expression. Assignments remain silent.
%%apl
m←2 3⍴⍳6
m×1010 20 30 40 50 60
The line magic returns a native basedpl.Array. Use .np or .py for conversion. Comments are parsed by bAsedPL, including ⍝ inside quoted strings.
z = %apl m ⍝ a matrix
test_is(type(z), Array)
np.testing.assert_array_equal(z.np, [[1, 2, 3], [4, 5, 6]])
test_eq(magic.apl("'a⍝b' ⍝ comment").py, 'a⍝b')
z1 2 3
4 5 6
Python values bound through apl are visible to the magics. Python integers stay exact.
apl(x=np.arange(1, 4))
value = %apl +/x
test_eq(value.py, 6)
test_is(type(value.py), int)
value6ₓ
bAsedPL’s display commands also work in a cell.
%%apl
]Display (1 2)'ab'┌→───────────┐ │ ┌→──┐ ┌→─┐ │ │ │1 2│ │ab│ │ │ └~──┘ └──┘ │ └∊───────────┘
A suppressed cell still updates the workspace.
%%apl
quiet←7;SVG elements and custom MIME renderers display in cell magics and as Python values.
%%apl
circle←•element 'circle'
•svg ('cx':50 ⋄ 'cy':50 ⋄ 'r':30 ⋄ 'fill':'orange') circle ''Names and help
Use ]help name for help and ]help name -source for source, in either magic. Leading definition comments supply help. Inspection leaves the function uncalled.
%%apl
double←{⍝ Double the argument
⎕←'called' ⋄ ⍵×2}with capture_output() as cap: magic.apl(']help double')
assert 'Double the argument' in cap.outputs[0].data['text/markdown']
with capture_output() as cap: magic.apl('', ']help double -source')
assert "⎕←'called'" in cap.outputs[0].data['text/markdown']
test_eq(len(cap.outputs), 1)Completion follows the magic’s workspace, including names defined after registration. Quoted text and comments stay literal.
from IPython.core.completer import provisionalcompletershell = get_ipython()
for text, expected in [('%apl dou', 'double'), ('v = %apl dou', 'double'), ('%%apl\n1+•sr', '•src')]:
with provisionalcompleter(): matches = list(shell.Completer.completions(text, len(text)))
assert expected in [m.text for m in matches]
for text in ["%%apl\n'dou", '%%apl\n⍝ dou', 'ordinary_python']:
with provisionalcompleter(): matches = list(shell.Completer.completions(text, len(text)))
assert not any(m.type == 'APL name' for m in matches)Errors and workspace state
Errors retain source locations and leave completed assignments in the workspace. Incomplete input raises a syntax error and leaves the workspace unchanged.
with capture_output() as cap: test_fail(lambda: magic.apl('', '⎕←42 ⋄ 1÷0'), contains='DOMAIN ERROR')
assert '42' in cap.outputs[0].data['text/html']
test_fail(lambda: magic.apl('', '{⍵+1'), contains='SYNTAX ERROR')
test_eq(magic.apl('quiet').py, 9)Set apl.timeout to a per-evaluation deadline in seconds. Ctrl-C interrupts the evaluation. Output captured before cancellation is displayed. The workspace keeps its names after cancellation. Native-library calls and individual BigInt operations can delay cancellation.
Installation
%load_ext basedpl.notebooks registers the magics in the current kernel. bapl-install-magic adds the extension to the default IPython profile. Registration does not start an APL worker.
load_ipython_extension
def load_ipython_extension(
ipython
):Register the APL magics when IPython loads this extension.
create_ipython_config
def create_ipython_config():Called by bapl-install-magic to install magic
Symbol input
The browser input method uses bAsedPL’s symbol names and aliases. It is active in %%apl cells, %apl lines and Monaco editors configured for APL. Matching prefers exact names, then prefixes, then abbreviations. A unique match is accepted with Tab or a delimiter. Enter accepts it before the notebook’s usual newline or execution action. Ambiguous matches remain unchanged and appear in a clickable list.
Strings, comments and pasted text are not expanded. Cursor movement and Escape cancel automatic expansion. The clickable language bar can still insert literal glyphs.
Hold left Alt/Option to type glyphs using bAsedPL’s keyboard: Alt-h ←, Alt-minus ×, Alt-equals ÷, Alt-Shift-a ⍶. These chords insert literal glyphs, including inside APL strings and comments. Right Option keeps its native keyboard behavior. The glyph map is shared with the bAsedPL REPL.
The JavaScript assertions live in tests/input.js. Pytest runs them through fastcdp’s QUnit runner using bAsedPL’s actual symbol catalogue. Editor integration checks exercise CodeMirror, Monaco and textarea. Install the dev extra and Chrome for Testing (fastcdp-setup --install stable), then run pytest -q. QUnit and the editor fixtures load from CDNs. Node is not required.