Skip to content

Console, scripts & GUI

D/BASIC can meet a person at a prompt, quietly do a job for another program, or keep a graphical application alive for hours. Those are different program shapes, not different versions of the language.

The important split is between two launch faces:

  • a console face runs the module's top-level statements in sequence; and
  • a window face lets the host call lifecycle and input handlers in bounded turns.

A console tool and a GUI can use the same types, procedures, objects, arrays, imports, optimizer, and DBC bytecode. What changes is who owns the conversation.

Three useful shapes

Shape Who drives? Input Output Lifetime A good fit
interactive console top-level D/BASIC code prompts, lines, keys PRINT start → converse → finish lessons, small tools, text adventures
console script top-level D/BASIC code arguments, embedded data, mounted services stable text and exit status start → do one job → finish reports, conversions, tests, automation
GUI application the host's face dispatcher normalized events windows, widgets, semantic Intents OPEN → event/draw turns → CLOSE editors, dashboards, games, long-lived apps

The console is not merely the beginner mode, and the GUI is not a second dialect. A fifty-line report generator is a real program. So is a two-window document tool. D/BASIC keeps the climb between them gradual.

The console: a conversation in order

Top-level statements execute from top to bottom. INPUT can pause for an answer; PRINT replies; END completes successfully.

DEFINT A-Z

PRINT "HOW MANY STARS";
INPUT Count

IF Count < 0 THEN Count = 0
IF Count > 40 THEN Count = 40

PRINT STRING$(Count, "*")
END

This shape is wonderful for learning because control flow is visible. It is also useful whenever a human really does want a prompt.

A script: the same face without the small talk

A script is a console program designed to be run non-interactively. Avoid prompts, make input sources explicit, keep output stable, and give the caller an honest completion result.

DEFINT A-Z

DIM Scores(1 TO 5)
DATA 17, 23, 19, 31, 26

Total = 0
FOR I = LBOUND(Scores) TO UBOUND(Scores)
  READ Scores(I)
  Total = Total + Scores(I)
NEXT I

PRINT "COUNT="; UBOUND(Scores) - LBOUND(Scores) + 1
PRINT "TOTAL="; Total
PRINT "MEAN="; Total / 5
END

Run source directly while developing, then build it once for repeatable delivery:

dbasic run score-report.bas
dbasic build score-report.bas -o score-report.dbc
dbasic exec score-report.dbc

END reports normal completion. STOP reports an intentional stopped status. A governed refusal is separate from both: it tells the caller that a required capability, version, or operation was unavailable rather than disguising that condition as ordinary output.

Process arguments and console reads are Service-backed, so a desktop shell, browser workbench, and D/OS launcher can supply their own honest routes. The script sees canonical argument text and console semantics, never a borrowed native pointer or host API object.

Habits that make good scripts

  • do not print a prompt when another program will consume the output;
  • keep headings and field names stable;
  • separate calculation from presentation in procedures;
  • use embedded DATA for small fixed tables, FILE Intents for named filesystem bytes, and DATA Intents for database identities and revisions;
  • treat refusal as a real outcome; and
  • build a .dbc when the exact compiled artifact matters.

A GUI: state across turns

A graphical application does not sit inside INPUT waiting for the next gesture. The host calls OPEN, DRAW, normalized input handlers, lifecycle handlers, and finally CLOSE. Your model survives between those turns.

DIM SHARED Message AS STRING
DIM SHARED Width AS INTEGER
DIM SHARED Height AS INTEGER

WINDOW SUB OPEN()
  Message = "PRESS ENTER"
  Width = 320
  Height = 192
  SERVICE SET_TITLE("A tiny GUI")
  SERVICE INVALIDATE()
END SUB

ON KEY SUB (K AS INTEGER)
  IF K = 13 THEN Message = "HELLO FROM THE EVENT LOOP"
  SERVICE INVALIDATE()
END SUB

WINDOW SUB DRAW()
  INTENT GFX.BEGIN_FRAME()
  INTENT GFX.FILL_RECT(0, 0, Width, Height, 2081)
  INTENT GFX.DRAW_TEXT(12, 14, Message, 3, -1, 2081)
  INTENT GFX.PRESENT()
END SUB

WINDOW SUB RESIZE(W AS INTEGER, H AS INTEGER)
  Width = W
  Height = H
  SERVICE INVALIDATE()
END SUB

WINDOW SUB CLOSE()
END SUB

The key handler changes the model; the draw handler projects that model. Neither polls a native event queue. That is why the same source can inhabit a browser surface, a Windows/Linux/macOS window, or a D/OS composed head.

A menu is not a second event system. It is an application-owned surface that turns a choice into the same stable command your toolbar button or keyboard shortcut would publish. WorksMenu provides one bounded pane; WorksMenuCascade connects a root and up to three child panes without allocating a new window for every submenu.

The cascade chooses right-opening or left-opening placement from available space, gives the deepest pane pointer precedence, skips disabled and separator rows during keyboard navigation, and rejects cycles. A checked mark or glyph is presentation; the stable command string is behavior.

This chapter's lesson is architectural: menus publish commands into the same controller as buttons and shortcuts. Build a complete File → Export → PDF/Text cascade in D/Works Widgets, where construction, painting, focus, pointer routing, dismissal, and semantic font roles are taught together.

One image can offer both faces

Face selection is structural:

  1. no window lifecycle or input handlers means console;
  2. window handlers mean windowed;
  3. top-level executable statements plus window handlers mean both; and
  4. the launch surface selects a face—there is no source fork or target flag.

A dual-face program is useful when the same engine deserves both a scriptable report and an interactive view:

DECLARE FUNCTION Greeting$ (Name AS STRING)

' Console face
PRINT Greeting$("CONSOLE")
END

FUNCTION Greeting$ (Name AS STRING)
  Greeting$ = "HELLO, " + Name
END FUNCTION

' Window face
WINDOW SUB OPEN()
  SERVICE SET_TITLE("Dual-face greeting")
  SERVICE INVALIDATE()
END SUB

WINDOW SUB DRAW()
  INTENT GFX.BEGIN_FRAME()
  INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
  INTENT GFX.DRAW_TEXT(12, 14, Greeting$("WINDOW"), 3, -1, 2081)
  INTENT GFX.PRESENT()
END SUB

WINDOW SUB CLOSE()
END SUB

The console launch executes the top-level report. The window launch dispatches the window faces. Both call the same Greeting$ function from the same bytecode image.

What should be shared—and what should not

Keep these independent of the face:

  • records, classes, validation, calculations, and document state;
  • whole-array operations and other data transformations;
  • imported libraries; and
  • semantic operations whose meaning is not presentation-specific.

Keep these at the edge:

  • prompts and console formatting;
  • focus, hit testing, window layout, and widget routing;
  • host admission and presentation; and
  • conversion from a user gesture into an application command.

That separation pays twice: console tests can exercise the model without a window, and the GUI can evolve without rewriting the model.

Choosing a face

Choose a console program when a line of output is the product. Choose a script when another program or a repeatable workflow is the audience. Choose a GUI when state, spatial layout, and events are part of the experience. Offer both when people deserve an interface and automation deserves an entry point.


Next: Source, Names & Types →