##// END OF EJS Templates
Use flex in html
Use flex in html

File last commit:

r6:a4c4f07f default
r10:a65783dd default
Show More
parser.lisp
571 lines | 17.5 KiB | text/x-common-lisp | CommonLispLexer
(in-package sugar-qsp)
;;;; Parses TXT source to an intermediate representation
;;; Utility
(defun remove-nth (list nth)
(append (subseq list 0 nth)
(subseq list (1+ nth))))
(defun not-quote (char)
(not (eql #\' char)))
(defun not-doublequote (char)
(not (eql #\" char)))
(defun not-brace (char)
(not (eql #\} char)))
(defun not-integer (string)
(when (find-if-not #'digit-char-p string)
t))
(defun not-newline (char)
(not (eql #\newline char)))
(defun id-any-char (char)
(and
(not (digit-char-p char))
(not (eql #\newline char))
(not (find char " !:&=<>+-*/,'\"()[]{}"))))
(defun intern-first (list)
(list* (intern (string-upcase (first list)))
(rest list)))
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun remove-nil (list)
(remove nil list)))
(defun binop-rest (list)
(destructuring-bind (ws1 operator ws2 operand2)
list
(declare (ignore ws1 ws2))
(list (intern (string-upcase operator)) operand2)))
(defun do-binop% (left-op other-ops)
(if (null other-ops)
left-op
(destructuring-bind ((operator right-op) &rest rest-ops)
other-ops
(if (and (listp left-op)
(eq (first left-op)
operator))
(do-binop% (append left-op (list right-op)) rest-ops)
(do-binop% (list operator left-op right-op) rest-ops)))))
(defun do-binop (list)
(destructuring-bind (left-op rest-ops)
list
(do-binop% left-op
(mapcar #'binop-rest rest-ops))))
(p:defrule line-continuation (and #\_ #\newline)
(:constant nil))
(p:defrule text-spaces (+ (or #\space #\tab line-continuation))
(:text t))
(p:defrule spaces (+ (or #\space #\tab line-continuation))
(:constant nil))
(p:defrule spaces? (* (or #\space #\tab line-continuation))
(:constant nil))
(p:defrule colon #\:
(:constant nil))
(p:defrule alphanumeric (alphanumericp character))
(p:defrule not-newline (not-newline character))
(p:defrule squote-esc "''"
(:lambda (list)
(p:text (elt list 0))))
(p:defrule dquote-esc "\"\""
(:lambda (list)
(p:text (elt list 0))))
(p:defrule sstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "\"exec:")))
(or squote-esc (not-quote character))))
(:lambda (list)
(p:text (mapcar #'second list))))
(p:defrule dstring-chars (+ (and (and (p:! "<<") (p:! (p:~ "'exec:")))
(or dquote-esc (not-doublequote character))))
(:lambda (list)
(p:text (mapcar #'second list))))
;;; Identifiers
;; From the official docs
(defparameter *keywords* '(act addlib addobj addqst and arrcomp arrpos arrsize cla clear *clear close clr *clr cls cmdclear cmdclr copyarr $counter countobj $curacts curloc debug delact dellib delobj desc disablescroll disablesubex dynamic dyneval else elseif end exit fcolor $fname freelib fsize func getobj gosub goto gs gt if iif inclib input instr isnum isplay jump killall killobj killqst killvar lcase lcolor len let loc $maintxt max menu mid min mod msecscount msg nl *nl no nosave obj $onactsel $ongload $ongsave $onnewloc $onobjadd $onobjdel $onobjsel opengame openqst or p *p pl *pl play qspver rand refint replace rgb rnd savegame selact selobj set settimer showacts showinput showobjs showstat $stattxt str strcomp strfind strpos trim ucase unsel unselect $usercom user_text usrtxt val view wait xgoto xgt))
(defun qsp-keyword-p (id)
(member (intern (string-upcase id)) *keywords*))
(defun not-qsp-keyword-p (id)
(not (member (intern (string-upcase id)) *keywords*)))
(p:defrule qsp-keyword (qsp-keyword-p identifier-raw))
(p:defrule id-first (id-any-char character))
(p:defrule id-next (or (id-any-char character)
(digit-char-p character)))
(p:defrule identifier-raw (and id-first (* id-next))
(:lambda (list)
(let ((id (p:text list)))
(when (member id *keywords*)
(error "~A is a keyword" id))
(intern (string-upcase id)))))
(p:defrule identifier (not-qsp-keyword-p identifier-raw))
;;; Strings
(p:defrule qsp-string (or normal-string brace-string))
(p:defrule normal-string (or sstring dstring)
(:lambda (str)
(list* 'str (or str (list "")))))
(p:defrule sstring (and #\' (* (or string-interpol
sstring-exec
sstring-chars))
#\')
(:function second))
(p:defrule dstring (and #\" (* (or string-interpol
dstring-exec
dstring-chars))
#\")
(:function second))
(p:defrule string-interpol (and "<<" expression ">>")
(:function second))
(p:defrule sstring-exec-body (+ (or squote-esc (not-doublequote character)))
(:text t))
(p:defrule dstring-exec-body (+ (or dquote-esc (not-quote character)))
(:text t))
(p:defrule sstring-exec (and (p:~ "\"exec:") sstring-exec-body #\")
(:lambda (list)
(list* 'exec (p:parse 'exec-body (second list)))))
(p:defrule dstring-exec (and (p:~ "'exec:") dstring-exec-body #\')
(:lambda (list)
(list* 'exec (p:parse 'exec-body (second list)))))
(p:defrule brace-string (and #\{ before-statement block-body #\})
(:lambda (list)
(list* 'qspblock (third list))))
;;; Location
(p:defrule sugar-qsp-grammar (and (* (or spaces #\newline))
(* location))
(:function second))
(p:defrule location (and location-header block-body location-end)
(:destructure (header body end)
(declare (ignore end))
`(location (,header) ,@body)))
(p:defrule location-header (and #\#
(+ not-newline)
(and #\newline spaces? before-statement))
(:destructure (spaces1 name spaces2)
(declare (ignore spaces1 spaces2))
(string-upcase (string-trim " " (p:text name)))))
(p:defrule location-end (and #\- #\newline before-statement)
(:constant nil))
;;; Block body
(p:defrule newline-block-body (and #\newline spaces? block-body)
(:function third))
(p:defrule block-body (* statement)
(:function remove-nil))
;; Just for <a href="exec:...'>
;; Explicitly called from that rule's production
(p:defrule exec-body (and before-statement line-body)
(:function second))
(p:defrule line-body (and inline-statement (* next-inline-statement))
(:lambda (list)
(list* (first list) (second list))))
(p:defrule before-statement (* (or #\newline spaces))
(:constant nil))
(p:defrule statement-end (or statement-end-real statement-end-block-close))
(p:defrule statement-end-real (and (or #\newline
(and #\& spaces? (p:& statement%)))
before-statement)
(:constant nil))
(p:defrule statement-end-block-close (or (p:& #\}))
(:constant nil))
(p:defrule inline-statement (and statement% spaces?)
(:function first))
(p:defrule next-inline-statement (and #\& spaces? inline-statement)
(:function third))
(p:defrule not-a-non-statement (and (p:! (p:~ "elseif"))
(p:! (p:~ "else"))
(p:! (p:~ "end"))))
(p:defrule statement (and inline-statement statement-end)
(:function first))
(p:defrule statement% (and not-a-non-statement
(or label comment string-output
block non-returning-intrinsic assignment
expression-output))
(:function second))
(p:defrule expr-stopper (or comment block non-returning-intrinsic))
(p:defrule string-output qsp-string
(:lambda (string)
(list 'main-pl string)))
(p:defrule expression-output expression
(:lambda (list)
(list 'main-pl list)))
(p:defrule label (and colon identifier)
(:lambda (list)
(intern (string (second list)) :keyword)))
(p:defrule comment (and #\! (* (or text-spaces qsp-string brace-string not-newline)))
(:constant nil))
;;; Blocks
(p:defrule block (or block-act block-if))
(p:defrule block-if (and block-if-head block-if-body)
(:destructure (head body)
`(qspcond (,@head ,@(first body))
,@(rest body))))
(p:defrule block-if-head (and (p:~ "if") spaces expression spaces? colon spaces?)
(:function remove-nil)
(:function cdr))
(p:defrule block-if-body (or block-if-ml block-if-sl)
(:destructure (if-body elseifs else &rest ws)
(declare (ignore ws))
`(,(remove-nil if-body) ,@elseifs ,@(when else `((else ,@else))))))
(p:defrule block-if-sl (and line-body
(p:? block-if-elseif-inline)
(p:? block-if-else-inline)
spaces?))
(p:defrule block-if-ml (and (and #\newline spaces?)
block-body
(p:? block-if-elseif)
(p:? block-if-else)
block-if-end)
(:lambda (list)
(cdr list)))
(p:defrule block-if-elseif-inline (and block-if-elseif-head line-body (p:? block-if-elseif-inline))
(:destructure (head statements elseif)
`((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
(p:defrule block-if-elseif (and block-if-elseif-head statement-end block-body (p:? block-if-elseif))
(:destructure (head ws statements elseif)
(declare (ignore ws))
`((,@(rest head) ,@(remove-nil statements)) ,@elseif)))
(p:defrule block-if-elseif-head (and (p:~ "elseif") spaces expression spaces? colon spaces?)
(:function remove-nil)
(:function intern-first))
(p:defrule block-if-else-inline (and block-if-else-head line-body)
(:function second))
(p:defrule block-if-else (and block-if-else-head #\newline spaces? block-body)
(:function fourth))
(p:defrule block-if-else-head (and (p:~ "else") spaces?)
(:constant nil))
(p:defrule block-if-end (and (p:~ "end")
(p:? (and spaces (p:~ "if"))))
(:constant nil))
(p:defrule block-act (and block-act-head (or block-act-ml block-act-sl))
(:lambda (list)
(apply #'append list)))
(p:defrule block-act-sl line-body)
(p:defrule block-act-ml (and newline-block-body block-act-end)
(:lambda (list)
(apply #'list* (butlast list))))
(p:defrule block-act-head (and (p:~ "act") spaces qsp-string spaces?
(p:? block-act-head-img)
colon spaces?)
(:lambda (list)
(intern-first (list (first list)
(third list)
(or (fifth list) '(str ""))))))
(p:defrule block-act-head-img (and #\, spaces? qsp-string spaces?)
(:lambda (list)
(or (third list) "")))
(p:defrule block-act-end (and (p:~ "end"))
(:constant nil))
;;; Calls
(p:defrule first-argument (and expression spaces?)
(:function first))
(p:defrule next-argument (and "," spaces? expression)
(:function third))
(p:defrule arguments (or parenthesized-arguments plain-arguments no-arguments))
(p:defrule parenthesized-arguments (and spaces? #\( base-arguments #\))
(:function third))
(p:defrule plain-arguments (and spaces base-arguments)
(:function second))
(p:defrule no-arguments (or spaces (p:& #\newline) (p:& #\&))
(:constant nil))
(p:defrule base-arguments (and first-argument (* next-argument))
(:destructure (first rest)
(list* first rest)))
;;; Intrinsics
(defmacro defintrinsics ((rule-name returning-rule-name non-returning-rule-name) &body clauses)
`(progn
,@(loop :for clause :in clauses
:collect `(defintrinsic ,@clause))
(p:defrule ,returning-rule-name (or ,@(remove-nil
(mapcar (lambda (clause)
(when (second clause)
(alexandria:symbolicate
'intrinsic- (first clause))))
clauses))))
(p:defrule ,non-returning-rule-name (or ,@(remove-nil
(mapcar (lambda (clause)
(unless (second clause)
(alexandria:symbolicate
'intrinsic- (first clause))))
clauses))))
(p:defrule ,rule-name (or ,returning-rule-name ,non-returning-rule-name))))
(defmacro defintrinsic (sym returning &optional (min-arity 0) (max-arity 10) &rest names)
(declare (ignore returning))
(setf names
(if names
(mapcar #'string-upcase names)
(list (string sym))))
`(p:defrule ,(alexandria:symbolicate 'intrinsic- sym)
(and (p:? #\$) (or ,@(loop :for name :in names :collect `(p:~ ,name)))
arguments)
(:destructure (dollar name arguments)
(declare (ignore dollar))
(unless (<= ,min-arity (length arguments) ,max-arity)
(error "Intrinsic ~A expects between ~A and ~A arguments but ~A were provided:~%~S"
name ,min-arity ,max-arity (length arguments) arguments))
(list* ',sym arguments))))
(defintrinsics (intrinsic returning-intrinsic non-returning-intrinsic)
;; Transitions
(goto nil 0 10 "gt" "goto")
(xgoto nil 0 10 "xgt" "xgoto")
;; Variables
(killvar nil 0 2)
;; Expressions
(obj t 1 1)
(loc t 1 1)
(no t 1 1)
;; Basic
(qspver t 0 0)
(curloc t 0 0)
(rand t 1 2)
(rnd t 0 0)
(qspmax t 1 10 "max")
(qspmin t 1 10 "min")
;; Arrays
(killall nil 0 0)
(copyarr nil 2 4)
(arrsize t 1 1)
(arrpos t 2 3)
(arrcomp t 2 3)
;; Strings
(len t 1 1)
(mid t 2 3)
(ucase t 1 1)
(lcase t 1 1)
(trim t 1 1)
(replace t 2 3)
(instr t 2 3)
(isnum t 1 1)
(val t 1 1)
(qspstr t 1 1 "str")
(strcomp t 2 2)
(strfind t 2 3)
(strpos t 2 3)
;; IF
(iif t 2 3)
;; Subs
(gosub nil 1 10 "gosub" "gs")
(func t 1 10)
(exit nil 0 0)
;; Jump
(jump nil 1 1)
;; Dynamic
(dynamic nil 1 10)
(dyneval t 1 10)
;; Main window
(main-p nil 1 1 "*p")
(main-pl nil 1 1 "*pl")
(main-nl nil 0 1 "*nl")
(maintxt t 0 0)
(desc t 1 1)
(main-clear nil 0 0 "*clear" "*clr")
;; Aux window
(showstat nil 1 1)
(stat-p nil 1 1 "p")
(stat-pl nil 1 1 "pl")
(stat-nl nil 0 1 "nl")
(stattxt t 0 0)
(stat-clear nil 0 0 "clear" "clr")
(cls nil 0 0)
;; Dialog
(msg nil 1 1)
;; Acts
(showacts nil 1 1)
(delact nil 1 1 "delact" "del act")
(curacts t 0 0)
(cla nil 0 0)
;; Objects
(showobjs nil 1 1)
(addobj nil 1 3 "addobj" "add obj")
(delobj nil 1 1 "delobj" "del obj")
(killobj nil 0 1)
(countobj t 0 0)
(getobj t 1 1)
;; Menu
(menu nil 1 1)
;; Sound
(play nil 1 2)
(isplay t 1 1)
(close nil 1 1)
(closeall nil 0 0 "close all")
;; Images
(refint nil 0 0)
(view nil 0 1)
;; Fonts
(rgb t 3 3)
;; Input
(showinput nil 1 1)
(usertxt t 0 0 "user_text" "usrtxt")
(cmdclear nil 0 0 "cmdclear" "cmdclr")
(input t 1 1)
;; Files
(openqst nil 1 1)
(addqst nil 1 1 "addqst" "addlib" "inclib")
(killqst nil 1 1 "killqst" "dellib" "freelib")
(opengame nil 0 1)
(savegame nil 0 1)
;; Real time
(wait nil 1 1)
(msecscount t 0 0)
(settimer nil 1 1))
;;; Expression
(p:defrule expression or-expr)
(p:defrule or-expr (and and-expr (* (and spaces? or-op spaces? and-expr)))
(:function do-binop))
(p:defrule and-expr (and eq-expr (* (and spaces? and-op spaces? eq-expr)))
(:function do-binop))
(p:defrule eq-expr (and cat-expr (* (and spaces? (or "<>" "<=" ">=" "=<" "=>"
#\= #\< #\> #\!)
spaces? cat-expr)))
(:function do-binop))
(p:defrule cat-expr (and sum-expr (* (and spaces? #\& spaces? (p:! expr-stopper) sum-expr)))
(:lambda (list)
(do-binop (list (first list) (mapcar (lambda (l)
(remove-nth l 3))
(second list))))))
(p:defrule sum-expr (and mod-expr (* (and spaces? (or #\+ #\-) spaces? mod-expr)))
(:function do-binop))
(p:defrule mod-expr (and mul-expr (* (and spaces? (p:~ "mod") spaces? mul-expr)))
(:function do-binop))
(p:defrule mul-expr (and unary-expr (* (and spaces? (or #\* #\/) spaces? unary-expr)))
(:function do-binop))
(p:defrule unary-expr (and (p:? (or #\+ #\-)) atom-expr)
(:lambda (list)
(let ((expr (remove-nil list)))
(if (= 1 (length expr))
(first expr)
(intern-first expr)))))
(p:defrule atom-expr (and (or variable literal returning-intrinsic paren-expr) spaces?)
(:function first))
(p:defrule paren-expr (and #\( spaces? expression spaces? #\))
(:function third))
(p:defrule or-op (p:~ "or")
(:constant "or"))
(p:defrule and-op (p:~ "and")
(:constant "and"))
;;; Variables
(p:defrule variable (and identifier (p:? array-index))
(:destructure (id idx)
(list 'var id (or idx 0))))
(p:defrule array-index (and #\[ spaces? (p:? expression) spaces? #\])
(:lambda (list)
(or (third list) :end)))
(p:defrule assignment (or kw-assignment plain-assignment)
(:destructure (var eq expr)
(declare (ignore eq))
(list 'set var expr)))
(p:defrule plain-assignment (and variable spaces? #\= spaces? expression)
(:function remove-nil))
(p:defrule kw-assignment (and (or (p:~ "let") (p:~ "set")) spaces? plain-assignment)
(:function third))
;;; Non-string literals
(p:defrule literal (or qsp-string brace-string number))
(p:defrule number (+ (or #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
(:lambda (list)
(parse-integer (p:text list))))