Add JSON API for route matching and TCB list

master
y5nw 2021-08-17 22:57:47 +02:00
parent c10e06674c
commit d7bff59e1c
5 changed files with 176 additions and 16 deletions

View File

@ -3,6 +3,8 @@
(:export :program-entry))
(in-package :ywatds)
(defparameter *ildb* nil)
(defun program-entry ()
(let* ((argv (uiop:command-line-arguments))
(worldpath (uiop:ensure-pathname (car argv)
@ -14,16 +16,49 @@
(server (make-instance 'easy-routes:routes-acceptor
:port serverport)))
(macrolet ((savefilepath (n)
`(uiop:subpathname worldpath (format nil "advtrains_~a" ,n))))
`(uiop:subpathname worldpath (format nil "advtrains_~a" ,n)))
(load-data ()
`(progn
(setf *ildb* (atil:import-data (savefilepath "interlocking.ls"))))))
(ywsw:safe-text-route
("/dumpser/:p" :method :get) (&path (p 'string))
(format nil "~s" (atsl:from-file (savefilepath p) :alist)))
(ywsw:safe-text-route
("/pretty_dump/interlocking" :method :get) ()
(format nil "~s" (atil:import-data (savefilepath "interlocking.ls"))))
(format nil "~s" *ildb*))
(ywsw:safe-json-route ("/tcbinfo" :method :get) () *ildb*)
(ywsw:safe-json-route
("/tcbinfo/:x/:y/:z" :method :get)
(&get
(ln :parameter-type 'string) (rc :parameter-type 'string)
(side :parameter-type 'string)
(next :parameter-type 'boolean)
&path (x 'integer) (y 'integer) (z 'integer))
(if (and x y z)
(let ((tcb (atil:find-tcb-at *ildb* (aux:make-v3d :x x :y y :z z))))
(cond
((not tcb) nil)
(side
(let ((tcbs (cond
((string-equal side "a") (atil:tcb-side-a tcb))
((string-equal side "b") (atil:tcb-side-b tcb))
(t nil))))
(cond
((not tcbs) nil)
((or ln rc)
(let ((route (atil:match-route (atil:tcbdata-routes tcbs)
(or ln "") (or rc ""))))
(cond
((not route) nil)
(next (atil:route-next route))
(t route))))
(t tcbs))))
(t tcb)))))
(hunchentoot:start server)
;; loop until an error occurs
(handler-case (loop (sleep most-positive-fixnum))
(handler-case (loop do
(load-data)
(sleep 20))
(t (c) (format t "~&~a~%" c)))
(ignore-errors
(hunchentoot:stop server)

View File

@ -29,6 +29,12 @@
(print-unreadable-object (obj stream)
(format stream "~a,~a,~a" x y z))))
(defmethod json:encode-json ((obj v3d) &optional json:*json-output*)
(with-accessors ((x v3d-x) (y v3d-y) (z v3d-z)) obj
(json:encode-json (list (cons "x" x)
(cons "y" y)
(cons "z" z)))))
(defmacro string-to-v3d (str)
(alexandria:with-gensyms (xs ys zs)
`(cl-ppcre:register-groups-bind
@ -45,3 +51,9 @@
`(make-v3d :x (gethash "x" ,ht)
:y (gethash "y" ,ht)
:z (gethash "z" ,ht))))
(defmacro v3d-to-string (obj)
(alexandria:with-gensyms (x y z)
`(with-accessors ((,x v3d-x) (,y v3d-y) (,z v3d-z))
,obj
(format nil "(~a,~a,~a)" ,x ,y ,z))))

View File

@ -1,7 +1,15 @@
(defpackage :advtrains-interlocking
(:use :cl)
(:nicknames :atil)
(:export :import-data))
(:export :import-data
:ars-rule :ars-rule-p :ars-rule-match-mode :ars-rule-match-string
:ars-rule-invert-match
:tcbside :tcbside-p :tcbside-pos :tcbside-side
:path-entry :path-entry-p :path-entry-next :path-entry-locks
:route :route-p :route-name :route-ars-rules :route-path
:tcbdata :tcbdata-p :tcbdata-signal-pos :tcbdata-signal-name :tcbdata-routes
:tcb :tcb-p :tcb-pos :tcb-side-a :tcb-side-b
:match-ars-p :match-route :route-next :find-tcb-at))
(in-package :atil)
(defstruct ars-rule
@ -21,6 +29,22 @@
(:ln (format t "ARS RULE~:[~; NOT~] MATCHING LINE ~s" neg match))
(:rc (format t "ARS RULE~:[~; NOT~] MATCHING RC ~s" neg match))))))
(defmethod json:encode-json ((obj ars-rule) &optional json:*json-output*)
(with-accessors ((mode ars-rule-match-mode)
(match ars-rule-match-string)
(neg ars-rule-invert-match))
obj
(json:encode-json (ecase mode
(:default (list (cons "mode" "default")))
(:comment (list (cons "mode" "comment")
(cons "comment" match)))
(:ln (list (cons "mode" "ln")
(cons "ln" match)
(cons "n" neg)))
(:rc (list (cons "mode" "rc")
(cons "rc" match)
(cons "n" neg)))))))
(defstruct tcbside
(:pos (error "No coordinates specified") :type aux:v3d)
(:side (error "No side specified") :type symbol))
@ -32,18 +56,32 @@
(print-unreadable-object (obj s)
(format s "SIDE ~a OF ~a" tside p)))))
(defmethod json:encode-json ((obj tcbside) &optional json:*json-output*)
(with-accessors ((p tcbside-pos) (side tcbside-side)) obj
(let ((tside (ecase side (:a "a") (:b "B"))))
(json:encode-json (list (cons "p" p) (cons "s" tside))))))
(defstruct path-entry
(next (error "No coordinates specified for path") :type tcbside)
(next nil :type (or null tcbside))
(locks (aux:adj-vector-of '(cons aux:v3d string)) :type (vector (cons aux:v3d string))))
(defmethod print-object ((obj path-entry) s)
(with-accessors ((next path-entry-next) (locks path-entry-locks))
obj
(print-unreadable-object (obj s)
(format s "TO ~a" next)
(format s "TO ~a" (or next "EOI"))
(loop for (pos . st) across locks do
(format s " ~_LOCKING ~a TO ~s" pos st)))))
(defmethod json:encode-json ((obj path-entry) &optional json:*json-output*)
(with-accessors ((next path-entry-next) (locks path-entry-locks)) obj
(json:encode-json-alist (list (cons "next" next)
(cons "locks" (loop with ht = (make-hash-table)
for i across locks
for (pos . st) = i
do (setf (gethash pos ht) st)
finally (return ht)))))))
(defstruct route
(name "" :type string)
(ars-rules (aux:adj-vector-of 'ars-rule) :type (vector ars-rule))
@ -59,6 +97,12 @@
(pprint-indent :block 1 s)
(loop for i across path do (format s " ~_~a" i))))))
(defmethod json:encode-json ((obj route) &optional json:*json-output*)
(with-accessors ((name route-name) (ars route-ars-rules) (path route-path)) obj
(json:encode-json (list (cons "name" name)
(cons "ars" ars)
(cons "path" path)))))
(defstruct tcbdata
(ts nil :type (or string null))
(signal-pos nil :type (or aux:v3d null))
@ -76,6 +120,15 @@
(pprint-indent :block 1 s)
(loop for i across routelist do (format s " ~_~a" i))))))
(defmethod json:encode-json ((obj tcbdata) &optional json:*json-output*)
(with-accessors ((ts tcbdata-ts) (spos tcbdata-signal-pos) (sname tcbdata-signal-name)
(routelist tcbdata-routes))
obj
(json:encode-json (list (cons "in_section" ts)
(cons "signal_pos" spos)
(cons "signal_name" sname)
(cons "routes" routelist)))))
(defstruct tcb
(pos (error "no position specified") :type aux:v3d)
(side-a (error "TCB has no side A") :type tcbdata)
@ -86,6 +139,58 @@
(print-unreadable-object (obj s)
(format s "TCB AT ~a ~_SIDE A ~a ~_SIDE B ~a" pos a b))))
(defmethod json:encode-json ((obj tcb) &optional json:*json-output*)
(with-accessors ((pos tcb-pos) (a tcb-side-a) (b tcb-side-b)) obj
(json:encode-json (list (cons "pos" pos)
(cons "a" a)
(cons "b" b)))))
(defmacro match-ars-p (ars-rules ln rc)
(alexandria:with-gensyms (i r s n)
(alexandria:once-only
(ars-rules ln rc)
`(loop
with ,r = nil
while (not ,r)
for ,i across ,ars-rules
for ,s = (ars-rule-match-string ,i)
for ,n = (ars-rule-invert-match ,i)
do (setf ,r (or ,r (ecase (ars-rule-match-mode ,i)
(:default t)
(:comment nil)
(:ln (if ,n (string/= ,s ,ln) (string= ,s ,ln)))
(:rc (if ,n (not (search ,rc ,s)) (search ,rc ,s))))))
finally (return ,r)))))
(defmacro match-route (routelist ln rc)
(alexandria:with-gensyms (i r)
(alexandria:once-only
(routelist ln rc)
`(loop
with ,r = nil
while (not ,r)
for ,i across ,routelist
when (match-ars-p (route-ars-rules ,i) ,ln ,rc)
do (setf ,r ,i)
finally (return ,r)))))
(defmacro route-next (route)
(alexandria:with-gensyms (p)
(alexandria:once-only
(route)
`(let ((,p (route-path ,route)))
(path-entry-next (aref ,p (length ,p)))))))
(defmacro find-tcb-at (data pos)
(alexandria:with-gensyms (i r)
(alexandria:once-only
(data pos)
`(loop with ,r = nil while (not ,r)
for ,i across ,data
when (equalp (tcb-pos ,i) ,pos)
do (setf ,r ,i)
finally (return ,r)))))
(defmacro parse-route-locks (locksht)
(alexandria:with-gensyms (ht locks k v pos st)
`(loop
@ -106,11 +211,10 @@
for ,ent = (gethash ,i ,ht)
while ,ent
for ,next = (gethash "next" ,ent)
while ,next
for ,p = (aux:hash-table-to-v3d (gethash "p" ,next))
and ,s = (ecase (gethash "s" ,next) (1 :a) (2 :b))
for ,p = (if ,next (aux:hash-table-to-v3d (gethash "p" ,next)) nil)
and ,s = (if ,next (ecase (gethash "s" ,next) (1 :a) (2 :b)) nil)
and ,locks = (parse-route-locks (gethash "locks" ,ent))
for ,tcbs = (make-tcbside :pos ,p :side ,s)
for ,tcbs = (if (and ,p ,s) (make-tcbside :pos ,p :side ,s) nil)
do (vector-push-extend (make-path-entry :next ,tcbs :locks ,locks)
,path)
finally (return ,path))))

View File

@ -1,7 +1,7 @@
(defpackage :ywatds-server-wrapper
(:use :cl)
(:nicknames :ywsw)
(:export :toxml :safe-text-route))
(:export :toxml :safe-text-route :safe-json-route))
(in-package :ywsw)
(defun toxml (l)
@ -38,7 +38,16 @@
(defmacro safe-text-route (options params &body body)
(let ((name (gensym)) (response (gensym)))
`(easy-routes:defroute ,name ,options ,params
(setf (hunchentoot:content-type*) "text/plain")
(let ((,response (safe-route ,@body)))
(if (stringp ,response) ,response nil)))))
`(easy-routes:defroute
,name ,options ,params
(setf (hunchentoot:content-type*) "text/plain")
(let ((,response (safe-route ,@body)))
(if (stringp ,response) ,response nil)))))
(defmacro safe-json-route (options params &body body)
(alexandria:with-gensyms (name response)
`(easy-routes:defroute
,name ,options ,params
(setf (hunchentoot:content-type*) "application/json")
(let ((,response (safe-route (json:encode-json-to-string (progn ,@body)))))
(if (stringp ,response) ,response "null")))))

View File

@ -4,7 +4,7 @@
:version "0.1"
:author "Y.W."
:license "GNU AGPL 3 or later"
:depends-on ("asdf" "easy-routes" "cl-ppcre" "parse-float" "alexandria")
:depends-on ("asdf" "easy-routes" "cl-ppcre" "parse-float" "alexandria" "cl-json")
:serial t
:components ((:file "helpers")
(:file "serialize-lib")