DDirectorWikiDirector & Lingo Encyclopedia

Lists and Property Lists

Linear lists and property lists - creation, access, mutation, sorting, reference semantics, and the getProp vs getaProp trap.

Two list flavors

lingo
linear = [10, 20, 30]                         -- linear list
props  = [#name: "Bob", #age: 42]             -- property list (ordered key/value)
emptyL = []
emptyP = [:]                                  -- note the colon

Both are 1-based, ordered, heterogeneous, and passed by reference.

Access

lingo
linear[2]              -- 20
getAt(linear, 2)       -- 20 (verbose equivalent)
linear[2] = 25         -- replace (position must exist)
setAt(linear, 5, 99)   -- extends a LINEAR list with placeholder 0s if needed

props[#name]           -- "Bob"  (bracket by key)
props["name"]          -- "Bob"  (string keys coerce to match symbols)
props[1]               -- "Bob"  (bracket by POSITION also works on propLists)
props.name             -- "Bob"  (dot access, D7+)
getProp(props, #age)   -- 42, ERROR if the key is missing
getaProp(props, #age)  -- 42, VOID if the key is missing
getPropAt(props, 1)    -- #name (key at position)

getProp vs getaProp is the classic list trap: getProp raises a script error on a missing key, getaProp quietly returns VOID. Old code deliberately uses getaProp + voidP() as "has key?". On linear lists, getaProp(list, n) behaves like a safe positional get, and getProp is invalid.

Mutation

OperationLinear listProperty list
Appendadd(l, v) / l.append(v)addProp(l, #k, v)
Insert at positionaddAt(l, pos, v)setaProp (by key)
Sorted insertadd(l, v) after sort(l) keeps orderaddProp keeps sort by key after sort(l)
ReplacesetAt(l, pos, v) / l[pos] = vsetaProp(l, #k, v) (adds if missing); setProp(l, #k, v) (errors if missing)
Delete by positiondeleteAt(l, pos)deleteAt(l, pos)
Delete by value/keydeleteOne(l, v) (first match; returns nothing, check via getOne first)deleteProp(l, #k)
Empty the listdeleteAll(l) (undocumented in some versions but present) or reassign []same
lingo
add linear, 40            -- [10, 25, 30, 40]  command form
linear.add(40)            -- dot form
addAt(linear, 1, 5)       -- [5, 10, 25, 30, 40]
deleteAt(linear, 2)       -- [5, 25, 30, 40]

addProp(props, #city, "NYC")
setaProp(props, #age, 43)     -- replace or add
deleteProp(props, #city)

Duplicate keys are legal. addProp appends even if the key already exists, so a property list can hold the same key twice; getaProp/getProp then find the first occurrence, and deleteProp deletes the first. Well-behaved code uses setaProp to avoid duplicates, but an emulator must reproduce the duplicate-tolerant behavior because real content depends on it.

Search and aggregate

lingo
count(l)  /  l.count       -- length ("the count of l" also works)
getOne(l, v)               -- position of first value match, 0 if absent
                           -- (on propLists: returns the KEY of the value)
getPos(l, v)               -- position of value, 0 if absent
findPos(l, #k)             -- propList: position of key, VOID if absent
findPosNear(sortedL, v)    -- nearest position in a SORTED list
getLast(l)                 -- last element
max(l), min(l)             -- extremes (also max(a,b,c) form)

Sorting

lingo
sort(l)
  • Linear lists sort by value (numbers numerically, strings alphabetically, mixed types by internal type order).
  • Property lists sort by key.
  • A sorted list stays sorted: subsequent add() inserts in order rather than appending. This is a real behavioral flag on the list object, not a one-time operation, and code depends on it.

Reference semantics and copying

lingo
a = [1, 2, 3]
b = a               -- b is the SAME list
b[1] = 99
put a               -- [99, 2, 3]

c = a.duplicate()   -- independent deep copy
  • Assignment and parameter passing share the list. Mutations inside a handler are visible to the caller.
  • duplicate() performs a deep copy (nested lists are copied too).
  • Equality (=) on lists compares contents, not identity.
  • Strings, integers, floats, symbols are value types; lists, property lists, images, and objects are reference types. Points and rects behave as value-ish types in most operations but are implemented as small lists; duplicate() also works on them.

Lists as data structures

Idioms seen constantly in shipped games:

lingo
-- struct via propList
user = [#id: 7, #name: "guest", #pos: point(3, 5)]

-- table of structs
users = []
add users, user

-- lookup table / dictionary
byId = [:]
setaProp(byId, user.id, user)      -- integer keys work as prop keys
found = getaProp(byId, 7)

-- queue
add pending, msg                    -- enqueue
msg = pending[1]
deleteAt(pending, 1)                -- dequeue

-- nested score-like data
grid = []
repeat with y = 1 to 10
  row = []
  repeat with x = 1 to 10
    add row, 0
  end repeat
  add grid, row
end repeat
grid[3][4] = 1

Performance notes (for emulator authors)

  • Native lists are contiguous arrays; addAt/deleteAt are O(n) shifts, add on an unsorted list is amortized append, add on a sorted list is a binary-search insert.
  • getaProp/findPos on property lists are linear scans in the general case; sorted property lists allow binary search by key.
  • Property list keys may be any value type (symbols dominate, but integers and strings occur); key comparison follows Lingo equality, so string keys match case-insensitively and props["Name"] finds #name.