from fastcore.test import *Dyalog sessions
Import Apl and AplError from basedpl.dyalog. Sessions return Dyalog output or JSON-converted Python values, not bAsedPL arrays. Importing this module does not start Dyalog or register notebook magics.
Install Dyalog separately. The examples use a local interpreter and run with nbdev-test --flags dyalog. The default test run skips them.
Connecting
start_dyalog
def start_dyalog(
dyalog:NoneType=None, # Path to the interpreter binary; `find_dyalog()` result if None
timeout:int=10, # Socket timeout during the startup handshake, in seconds
):Spawn a Dyalog interpreter that connects back to us over RIDE; return (socket,Popen)
find_dyalog
def find_dyalog():Locate the Dyalog interpreter binary
find_dyalog checks the command path and standard macOS/Linux installation locations. Pass dyalog= to Apl to choose another executable. timeout bounds the startup handshake, not evaluation.
ride_recv
def ride_recv(
sock
):Receive one RIDE message, JSON-decoded unless it’s a handshake string
ride_send
def ride_send(
sock, msg
):Send one RIDE message: a handshake str, or a [cmd,args] list sent as JSON
RIDE uses length-prefixed messages over a local socket. ride_run collects session output until Dyalog is ready for another expression. Interactive and incomplete-input prompts are handled by Apl.run.
ride_run
def ride_run(
sock, code
):Run APL lines; return (output,errno) once the session is ready again
AplPrompt
def AplPrompt(
ptype
):A non-ready prompt: 2=⎕ input, 3=incomplete input, 4=⍞ input
AplError
def AplError(
msg, reset:bool=False
):A Dyalog diagnostic; reset means the interpreter was replaced and workspace state lost
Apl.__exit__
def __exit__(
*args
):Apl.__enter__
def __enter__():Apl.close
def close():Shut down the interpreter and close the connection
Apl
def Apl(
dyalog:NoneType=None, timeout:int=10
):A Dyalog APL session over the RIDE protocol
Evaluating APL
Apl.run
def run(
code
):Run code, returning session output; raises AplError on APL errors
run returns session output as text. Assignments persist across calls:
dyalog = Apl()
out = dyalog.run('x←1 2 3\n+/x')
test_eq(out.strip(), '6')
out'6\n'
Apl.__call__
def __call__(
code
):Run code, returning displayable session output, or None if there is none
Calling a session returns AplOut, the same text with notebook display support. A silent assignment returns None:
test_is(dyalog('quiet←7'), None)
dyalog('2×x')2 4 6
Python values
Apl.pyval
def pyval(
expr
):Evaluate expr and return its JSON-converted Python value
pyval uses Dyalog’s JSON conversion. Scalars become Python values and numeric arrays become lists. This is the interface used by the reference checker:
test_eq(dyalog.pyval('+/x'), 6)
matrix = dyalog.pyval('2 3⍴⍳6')
test_eq(matrix, [[1,2,3], [4,5,6]])
matrix[[1, 2, 3], [4, 5, 6]]
Apl.__setitem__
def __setitem__(
nm, v
):Apl.__getitem__
def __getitem__(
expr
):Square brackets read an expression or assign a JSON-compatible Python value. Quotes in strings are escaped for APL:
dyalog['values'] = [3,1,4]
dyalog['message'] = "can't"
test_eq(dyalog['message'], "can't")
test_eq(dyalog['values'], [3,1,4])
dyalog['values'][3, 1, 4]
Apl.fn
def fn(
code
):A Python callable applying APL function code monadically or dyadically
fn converts Python arguments through JSON and evaluates the function expression on each call. One argument supplies ⍵; two supply ⍺ and ⍵:
mean = dyalog.fn('{(+/⍵)÷≢⍵}')
test_eq(mean([1,2,3]), 2)
add = dyalog.fn('+')
test_eq(add([1,2,3], 10), [11,12,13])
add([1,2,3], 10)[11, 12, 13]
Errors and lifetime
Dyalog errors raise this module’s AplError, which is separate from bAsedPL’s exception. Completed assignments remain in the session:
with expect_fail(AplError, contains='DOMAIN ERROR'): dyalog.run('saved←42 ⋄ 1÷0')
test_eq(dyalog['saved'], 42)
dyalog['saved']42
Interactive ⎕ and ⍞ input is rejected. An incomplete-input prompt restarts the interpreter and raises AplError with reset=True; that restart loses the workspace.
Call close() when finished, or use a context manager for a bounded reference check:
dyalog.close()
with Apl() as reference: total = reference.pyval('+/⍳10')
test_eq(total, 55)
total55