# Pastebin IY0Z8pcR #lang racket ;; ========================================================= ;; AST DEFINITIONS ;; ========================================================= (struct invoice (id date client items tax) #:transparent) (struct item (desc hours rate) #:transparent) ;; ========================================================= ;; SIMPLE INDENTATION PARSER ;; Converts DSL text → nested S-expressions ;; ========================================================= (define (indent-level line) (length (regexp-match* #px"^ *" line))) (define (tokenize line) (map string->symbol (regexp-split #px"\\s+" (string-trim line)))) (define (parse-dsl text) (define lines (filter (λ (l) (not (regexp-match? #px"^\\s*$" l))) (string-split text "\n"))) (define stack '()) ;; (indent . node) (define root '()) (for ([line lines]) (define ind (indent-level line)) (define node (tokenize line)) ;; pop until correct parent (set! stack (let loop ([st stack]) (cond [(empty? st) st] [(<= ind (caar st)) (loop (cdr st))] [else st]))) (cond [(empty? stack) (set! root (cons node root))] [else (define parent (cdar stack)) (set-cdr! (car stack) (append parent (list node)))]) (set! stack (cons (cons ind node) stack))) (reverse root)) ;; ========================================================= ;; DSL → AST ;; ========================================================= (define (parse-invoice expr) (match expr [(list 'invoice id clauses ...) (define date #f) (define client #f) (define items '()) (define tax 0.0) (for ([c clauses]) (match c [(list 'date d) (set! date d)] [(list 'client cl) (set! client cl)] [(list 'item desc 'hours h 'rate r) (set! items (cons (item desc (string->number (symbol->string h)) (string->number (symbol->string r))) items))] [(list 'tax t) (define s (symbol->string t)) (define num (string->number (regexp-replace #px"%" s ""))) (set! tax (/ num 100.0))])) (invoice id date client (reverse items) tax)])) ;; ========================================================= ;; EVALUATION ;; ========================================================= (define (subtotal inv) (for/sum ([i (invoice-items inv)]) (* (item-hours i) (item-rate i)))) (define (total inv) (define sub (subtotal inv)) (+ sub (* sub (invoice-tax inv)))) ;; ========================================================= ;; RUNNER ;; ========================================================= (define (run text) (define parsed (parse-dsl text)) (define inv (parse-invoice (car parsed))) (values inv (total inv))) ;; ========================================================= ;; EXAMPLE DSL INPUT ;; ========================================================= (define input "invoice INV-001 date 2026-04-29 client AcmeCorp item WebsiteRedesign hours 10 rate 150 item HostingSetup hours 2 rate 100 tax 8%") ;; ========================================================= ;; EXECUTION ;; ========================================================= (define-values (inv total-cost) (run input)) (displayln inv) (displayln total-cost)