Dyalog sessions

Run Dyalog APL through RIDE for reference checks.

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.

from fastcore.test import *

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)
total
55