DrBrainf-ck/gui.rkt
2018-12-10 18:17:46 +01:00

253 lines
No EOL
7.7 KiB
Racket

#lang racket/gui
(require "interpreter.rkt"
framework
racket/set
racket/block)
; The editor width in pixels
(define EDITOR-WIDTH 600)
(define EDITOR-HEIGHT 600)
; The frame racket/gui base object for the editor
(define F (new frame%
[label "DrBrainf*ck"]
[width EDITOR-WIDTH]
[height EDITOR-HEIGHT]))
; execute-content: ((ProgState) -> _) -> _
; Given a callback, calls the callback with the last state of execution of the
; program loaded in the editor
(define (execute-content done)
(execute
(program->prog-state
(string->program
(send EDITOR get-flattened-text)))
done on-input))
; on-input: (Byte -> _) -> _
; Given a a callback to resume execution, sets up the run input area (callback
; included).
(define (on-input callback)
(send RUN-INPUT set-field-background run-col)
(if (non-empty-string? (send RUN-INPUT get-value))
(on-input-done callback)
(set! input-done-cb (lambda (_ __) (on-input-done callback)))))
; on-input-done: (Byte -> _) -> _
; Given a callback to resume execution, fetches the first character from
; `RUN-INPUT` "consuming" it and resumes execution.
(define (on-input-done callback)
(define old-str (send RUN-INPUT get-value))
; Continue only if there is input
(if (non-empty-string? old-str)
(block
; Disable input callback
(set! input-done-cb (lambda (_ __) (void)))
; Pick first char from run input "consuming" it
(define in (string-ref old-str 0))
(define new-str (substring old-str 1))
(send RUN-INPUT set-value new-str)
; Remove waiting color from run-input
(send RUN-INPUT set-field-background in-done-col)
; Resume execution
(callback (char->integer in)))
(void)))
; run-program: Button ControlEvent -> Nothing
; Given a button and an event, runs the program currently loaded in the editor.
(define (run-program button event)
; empty output text and set color to running
(send RUN-OUTPUT set-value "")
(send RUN-OUTPUT set-field-background run-col)
; run the program
(execute-content
(lambda (ps)
(define out (prog-state-output ps))
; set output and change color to ended
(send RUN-OUTPUT set-value out)
(send RUN-OUTPUT set-field-background end-col))))
; Definition of the run button widget
(define RUN-BTN (new button% [parent F]
[label "Run"]
; Callback procedure for a button click:
[callback run-program]))
; Definition of the editor canves
(define C (new editor-canvas% [parent F]))
; Definition of editor text object.
(define text-pro% (text:line-numbers-mixin
(editor:standard-style-list-mixin
(color:text-mixin
(text:basic-mixin
(editor:basic-mixin text%))))))
(define EDITOR (new text-pro%))
; Show line numbers in editor
(send EDITOR show-line-numbers! #t)
; A BFToken is one of:
; - 'comment
; - 'instruction
; - 'shift
; - 'loop
; Interp: category of a bf program token
; A BFStyle is one of:
; - "Comment"
; - "Instruction"
; - "Shift"
; - "Loop"
; Interp: name of the style for a bf program token
; bf-token->bf-style: BFToken -> BFStyle
; Given a BFToken, returns the corresponding BFStyle.
(define (bf-token->bf-style token)
(cond [(symbol=? token 'comment) "Comment"]
[(symbol=? token 'loop) "Loop"]
[(symbol=? token 'shift) "Shift"]
[(symbol=? token 'instruction) "Instruction"]))
; bf-lexer: InputPort -> (values 1String BFToken #f Option<Nat> Option<Nat>)
; Given an input port, returns the BFToken of the instruction pointed by the
; port.
(define (bf-lexer port)
(define-values (_ __ pos) (port-next-location port))
(define c (read-char port))
(cond [(eof-object? c) (values c 'eof #f #f #f)]
[else
(define 1str (string c))
(define bf-token
(cond [(set-member? (set "+" "-" "," ".") 1str) 'instruction]
[(set-member? (set "<" ">") 1str) 'shift]
[(set-member? (set "[" "]") 1str) 'loop]
[else 'comment]))
(values 1str bf-token #f (+ pos) (+ pos 1))]))
; Syntax highlighting for brainf*ck
(send EDITOR start-colorer bf-token->bf-style bf-lexer '())
; Define basic style for instructions (+ - , .)
(define delta (make-object style-delta%))
(send delta set-delta-foreground "blue")
(editor:set-standard-style-list-delta "Instruction" delta)
; Define style for shifting operations (< >)
(send delta set-delta-foreground "red")
(editor:set-standard-style-list-delta "Shift" delta)
; Define basic style for looping instructions ([ ])
(send delta set-delta-foreground "forestgreen")
(editor:set-standard-style-list-delta "Loop" delta)
; Define style of comments
(send delta set-delta-foreground "orange")
(editor:set-standard-style-list-delta "Comment" delta)
; Input done color
(define in-done-col (make-object color% 255 255 255))
; Running output color
(define run-col (make-object color% 250 250 200))
; Output completed color
(define end-col (make-object color% 200 250 200))
; Populate menu bar
(define MB (new menu-bar% [parent F]))
(define m-file (new menu% [label "File"] [parent MB]))
(define m-edit (new menu% [label "Edit"] [parent MB]))
; The tape panel
(define tape-panel
(new horizontal-panel%
[parent F]
[min-height 50]
[style (list 'auto-hscroll)]
[stretchable-height #f]))
; make-cell: Number String Boolean -> Canvas
(define (make-cell index content hl)
(new canvas%
[parent tape-panel]
[min-height 50]
[min-width 50]
[stretchable-height #f]
[label (string-append (number->string index) ":")]
[paint-callback
(lambda (c dc)
(send c set-canvas-background (if hl end-col run-col))
(send dc draw-text (send c get-label) 12.5 5)
(send dc draw-text content 12.5 25))]))
(make-cell 0 "000" #f)
(make-cell 1 "255" #t)
(make-cell 2 "100" #f)
(make-cell 300000000 "100" #f)
(make-cell 4 "100" #f)
(make-cell 5 "100" #f)
(make-cell 6 "100" #f)
(make-cell 7 "100" #f)
(make-cell 8 "100" #f)
(make-cell 9 "100" #f)
(make-cell 10 "100" #f)
(make-cell 11 "100" #f)
(make-cell 12 "100" #f)
(make-cell 13 "100" #f)
; The input horizontal panel
(define input-panel
(new horizontal-panel%
[parent F]
[min-height 50]
[stretchable-height #f]))
; Input needed in program execution
(define RUN-INPUT (new text-field%
[label "Execution input:"]
[parent input-panel]))
; The input done callback (defaults to nothing)
; Type: (Button Event) -> _
(define input-done-cb (lambda (btn ev) (void)))
; Definition of the run button widget
(define INPUT-DONE-BTN (new button%
[parent input-panel]
[label "Confirm"]
[callback (lambda (btn ev) (input-done-cb btn ev))]))
; The run output text object
(define RUN-OUTPUT (new text-field%
[style (list 'multiple 'vertical-label)]
[label "Execution output:"]
[parent F]))
(define mi-open
(new menu-item%
[label "Open"]
[parent m-file]
[callback
(lambda (i e)
(define path (get-file #f F))
(when path
(send EDITOR load-file path 'text)))]
[shortcut #\o]
[shortcut-prefix '(ctl)]))
(define mi-save
(new menu-item%
[label "Save"]
[parent m-file]
[callback
(lambda (i e)
(send EDITOR save-file #f 'text))]
[shortcut #\s]
[shortcut-prefix '(ctl)]))
(append-editor-operation-menu-items m-edit #f)
(send EDITOR set-max-undo-history 100)
(send C set-editor EDITOR)
(send F show #t)