diff --git a/nobot/botscript/lexer/lexer-nodes.lisp b/nobot/botscript/lexer/lexer-nodes.lisp index ddc7fbd..ab7b039 100644 --- a/nobot/botscript/lexer/lexer-nodes.lisp +++ b/nobot/botscript/lexer/lexer-nodes.lisp @@ -110,12 +110,16 @@ (defmethod update-pos (ch (obj from-source-code-node)) (if (eq ch #\newline) - (incf (get-position-y obj)) + (progn + (incf (get-position-y obj)) + (setf (get-position-x obj) 0)) (incf (get-position-x obj)))) (defmethod undo-update-pos (ch (obj from-source-code-node)) (if (eq ch #\newline) - (decf (get-position-y obj)) + (progn + (decf (get-position-y obj)) + (setf (get-position-x obj) 0)) (decf (get-position-x obj)))) (defmethod push-char-to-buffer (ch (obj from-source-code-node)) diff --git a/nobot/botscript/lexer/lexer-utils.lisp b/nobot/botscript/lexer/lexer-utils.lisp index bd37457..e58790f 100644 --- a/nobot/botscript/lexer/lexer-utils.lisp +++ b/nobot/botscript/lexer/lexer-utils.lisp @@ -167,7 +167,7 @@ (case on-error (:on-char (assert val) - (format nil "unknown symbol \"~a\"" val)) + (format nil "unknown symbol '~a'" val)) (:on-close-comment "comment closing expected") (:on-string @@ -175,7 +175,7 @@ (t (error "unknown type of arg `on-error`: ~a, see fun doc" on-error))) - " at position: line - ~a, column - ~a~a") + " at position [~a:~a]~a") (if on-fixed-pos (cdr fix-pos) (get-position-y *source*)) diff --git a/nobot/botscript/parser/acacia/parser-generator.lisp b/nobot/botscript/parser/acacia/parser-generator.lisp index 629bd1c..a25d946 100644 --- a/nobot/botscript/parser/acacia/parser-generator.lisp +++ b/nobot/botscript/parser/acacia/parser-generator.lisp @@ -19,22 +19,23 @@ (:import-from :nobot/utils #:reintern #:to-symbol) + (:import-from :nobot/botscript/types + #:type->keyword) (:import-from :nobot/botscript/lexer/token #:token-typep #:token-value-equal-to #:value-of-token #:convert-token #:get-token-type - #:get-position - ) + #:get-position) (:export #:define-rule #:rule->)) (in-package :nobot/botscript/parser/acacia/parser-generator) -(defgeneric rule-> (rule-name &key first-fail-no-error) - (:method (rule-name &key first-fail-no-error) - (declare (ignore first-fail-no-error)) +(defgeneric rule-> (rule-name &key first-fail-no-error not-first) + (:method (rule-name &key first-fail-no-error not-first) + (declare (ignore first-fail-no-error not-first)) (error 'acacia-undefined-rule :rule rule-name))) @@ -44,22 +45,24 @@ (defmacro define-rule (rule-name () body) (let ((rule-name (intern (string rule-name) :keyword))) - `(defmethod rule-> ((rule-name (eql ,rule-name)) &key first-fail-no-error) - (declare (ignorable first-fail-no-error)) + `(defmethod rule-> ((rule-name (eql ,rule-name)) &key first-fail-no-error not-first) + (declare (ignorable first-fail-no-error not-first)) ,(build-rule-body `(,body) `,rule-name)))) (defun build-rule-body (quote-body-tree rule-name) (labels ((%build (body-tree &key first-fail-no-error - merge-sub-trees) + merge-sub-trees + not-first) (let ((root (car body-tree))) (case root ((:rule :rule*) (destructuring-bind (rule-name) (cdr body-tree) `(awhen (rule-> ,(to-kword rule-name) - :first-fail-no-error (or ,first-fail-no-error - first-fail-no-error)) + :first-fail-no-error ,(or first-fail-no-error + 'first-fail-no-error) + :not-first ,not-first) (remove nil ,(if (eq root :rule*) '(cdr it) '(list it)))))) @@ -70,7 +73,8 @@ (error 'acacia-empty-body-of-rule :rule :and)) `(awhen ,(%build first-rule - :first-fail-no-error first-fail-no-error + :first-fail-no-error (or first-fail-no-error + 'first-fail-no-error) :merge-sub-trees t) ;;TODO: no need using this remove function (remove @@ -80,7 +84,9 @@ (list ($conf-rule->term-sym ,rule-name))) it ,@ (mapcar - (rcurry #'%build :merge-sub-trees t) + (rcurry #'%build + :merge-sub-trees t + :not-first t) (cdr sub-rules))))))) (:or (let* ((sub-rules (cdr body-tree)) @@ -89,36 +95,54 @@ (error 'acacia-empty-body-of-rule :rule :or)) ;;TODO: see issue #4 - `(awhen - (append - (unless ,merge-sub-trees - (list ($conf-rule->term-sym ,rule-name))) - (aif (or - ,@ (mapcar (rcurry #'%build - :first-fail-no-error t - :merge-sub-trees t) - (butlast sub-rules)) - ,(if (is-empty-rule last-rule) - t - (%build last-rule - :first-fail-no-error t - :merge-sub-trees t))) - it - (unless first-fail-no-error - (raise-bs-parser-error - "error on rule ~a" ,rule-name)))) - (cond - ((eq (cdr it) t) - (list (car it))) - ((cdr it) (remove nil it)) - (t nil))))) - (:terminal - (destructuring-bind (sym &optional val exclude-from-tree) + (with-gensyms (pos-list) + `(awhen + (append + (unless ,merge-sub-trees + (list ($conf-rule->term-sym ,rule-name))) + (aif (or + ,@ (mapcar (rcurry #'%build + :first-fail-no-error t + :merge-sub-trees t) + (butlast sub-rules)) + ,(if (is-empty-rule last-rule) + t + (%build last-rule + :first-fail-no-error t + :merge-sub-trees t))) + it + (unless (and (not (or ,not-first + not-first)) + first-fail-no-error) + (with-next-token () + (let ((,pos-list + (if next + (get-position next) + (awhen (get-position ($conf-prev-token)) + (cons (car it) + (1+ (cdr it))))))) + (raise-bs-parser-error + "expected ~a, but got ~a, at position [~a:~a]~a" + ($conf-rule->description ,rule-name) + (if next + ($conf-terminal->description + (type->keyword (get-token-type next)) + (value-of-token next)) + "end of source") + (cdr ,pos-list) + (car ,pos-list) + (if (eq ($conf-get-source-type) :file) + (format nil ", file: ~a" + ($conf-get-source)) + ""))))))) + (cond + ((eq (cdr it) t) + (list (car it))) + ((cdr it) (remove nil it)) + (t nil)))))) + ((:terminal :terminal*) + (destructuring-bind (sym &optional val) (cdr body-tree) - (unless (or (null exclude-from-tree) - (eq exclude-from-tree :exclude-from-tree)) - (error 'acacia-unknown-argument-of-rule - :unknown-arg exclude-from-tree)) (with-gensyms (converted-sym converted-val pos-list) `(with-next-token () (let ((,converted-sym ($conf-token-rule->token-sym ',sym)) @@ -132,12 +156,10 @@ ,(if val `(token-value-equal-to next ,converted-val) t)) - (if ,exclude-from-tree + (if ,(eq root :terminal*) (list nil) (list (convert-token next :with-pos nil))) - (if ,(if first-fail-no-error - t - 'first-fail-no-error) + (if ,(and (not not-first) first-fail-no-error) (progn ($conf-mv-ptr-to-prev-token) nil) @@ -148,14 +170,14 @@ (cons (car it) (1+ (cdr it))))))) (raise-bs-parser-error - "expected get: ~a, but got: ~a, at position line - ~a, column - ~a~a" + "expected ~a, but got ~a, at position [~a:~a]~a" ,(if val `($conf-terminal->description ',sym ,val) `($conf-token-rule->description ',sym)) (if next ,(if val `($conf-terminal->description - ',sym + (type->keyword (get-token-type next)) (value-of-token next)) `($conf-token-rule->description (get-token-type next))) @@ -163,9 +185,9 @@ (cdr ,pos-list) (car ,pos-list) (if (eq ($conf-get-source-type) :file) - (format nil ", file: ~a." + (format nil ", file: ~a" ($conf-get-source)) - ".")))))))))) + "")))))))))) (t (error 'acacia-unknown-parser-rule :unknown-rule root)))))) (let* ((body-tree (car quote-body-tree)) diff --git a/nobot/botscript/parser/parser-impl.lisp b/nobot/botscript/parser/parser-impl.lisp index d394fff..1d9cc7c 100644 --- a/nobot/botscript/parser/parser-impl.lisp +++ b/nobot/botscript/parser/parser-impl.lisp @@ -69,7 +69,7 @@ (:and (:rule compiler-option-name) (:rule string-or-number) - (:terminal delimiter ";" :exclude-from-tree))) + (:terminal* delimiter ";"))) (define-rule compiler-option-name () (:or @@ -79,21 +79,21 @@ (define-rule bot-declaration () (:and - (:terminal keyword "bot" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "bot") + (:terminal* delimiter "{") (:rule bot-options) (:rule var-declarations) (:rule start-from-stmt) (:rule state-points-declarations) (:rule state-actions-declarations) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule bot-options () (:and - (:terminal keyword "options" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "options") + (:terminal* delimiter "{") (:rule* bot-options-list) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule bot-options-list () (:or @@ -105,16 +105,16 @@ (define-rule bot-option () (:and (:terminal id) - (:terminal delimiter ":" :exclude-from-tree) + (:terminal* delimiter ":") (:rule string-or-number) - (:terminal delimiter ";" :exclude-from-tree))) + (:terminal* delimiter ";"))) (define-rule var-declarations () (:and - (:terminal keyword "vars" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "vars") + (:terminal* delimiter "{") (:rule* var-decls-list) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule var-decls-list () (:or @@ -126,23 +126,23 @@ (define-rule var-decl () (:and (:terminal id) - (:terminal delimiter ":" :exclude-from-tree) + (:terminal* delimiter ":") (:rule* literal) - (:terminal delimiter ";" :exclude-from-tree))) + (:terminal* delimiter ";"))) (define-rule start-from-stmt () (:and - (:terminal keyword "start" :exclude-from-tree) - (:terminal keyword "from" :exclude-from-tree) + (:terminal* keyword "start") + (:terminal* keyword "from") (:terminal id) - (:terminal delimiter ";" :exclude-from-tree))) + (:terminal* delimiter ";"))) (define-rule state-points-declarations () (:and - (:terminal keyword "state-points" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "state-points") + (:terminal* delimiter "{") (:rule* state-points-decls) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule state-points-decls () (:or @@ -154,10 +154,10 @@ (define-rule state-point-decl () (:and (:terminal id) - (:terminal delimiter ":" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* delimiter ":") + (:terminal* delimiter "{") (:rule state-point-options) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule state-point-options () (:or @@ -169,16 +169,16 @@ (define-rule state-point-option () (:and (:terminal id) - (:terminal delimiter ":" :exclude-from-tree) + (:terminal* delimiter ":") (:terminal id) - (:terminal delimiter ";" :exclude-from-tree))) + (:terminal* delimiter ";"))) (define-rule state-actions-declarations () (:and - (:terminal keyword "state-actions" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "state-actions") + (:terminal* delimiter "{") (:rule* state-actions-decls) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule state-actions-decls () (:or @@ -190,10 +190,10 @@ (define-rule state-decl () (:and (:terminal id) - (:terminal delimiter ":" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* delimiter ":") + (:terminal* delimiter "{") (:rule stmt-list) - (:terminal delimiter "}" :exclude-from-tree))) + (:terminal* delimiter "}"))) (define-rule stmt-list () (:or @@ -207,7 +207,7 @@ (:rule if-stmt) (:and (:rule expr) - (:terminal delimiter ";" :exclude-from-tree)))) + (:terminal* delimiter ";")))) (define-rule expr () (:or @@ -217,7 +217,7 @@ (define-rule gotov-expr () (:and - (:terminal keyword "gotov" :exclude-from-tree) + (:terminal* keyword "gotov") (:rule* gotov-arg))) (define-rule gotov-arg () @@ -227,7 +227,7 @@ (define-rule say-expr () (:and - (:terminal keyword "say" :exclude-from-tree) + (:terminal* keyword "say") (:rule* say-expr-args))) (define-rule say-expr-args () @@ -251,43 +251,43 @@ (define-rule save-to-expr () (:and - (:terminal keyword "save" :exclude-from-tree) + (:terminal* keyword "save") (:rule* literal-or-id-or-input) - (:terminal keyword "to" :exclude-from-tree) + (:terminal* keyword "to") (:terminal id))) (define-rule if-stmt () (:and - (:terminal keyword "if" :exclude-from-tree) + (:terminal* keyword "if") (:rule cond-expr) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* delimiter "{") (:rule stmt-list) - (:terminal delimiter "}" :exclude-from-tree) + (:terminal* delimiter "}") (:rule else-block))) (define-rule else-block () (:or (:and - (:terminal keyword "else" :exclude-from-tree) - (:terminal delimiter "{" :exclude-from-tree) + (:terminal* keyword "else") + (:terminal* delimiter "{") (:rule stmt-list) - (:terminal delimiter "}" :exclude-from-tree)) + (:terminal* delimiter "}")) (:empty))) - ;;TODO: redundant AND rule fir single RULE rule + ;;TODO: redundant AND rule for single RULE rule (define-rule cond-expr () (:and (:rule logic-expr))) (define-rule logic-expr () - (:and + (:or (:rule equal-expr) (:rule in-expr))) (define-rule equal-expr () (:and (:rule eq-sub-expr) - (:terminal delimiter "==" :exclude-from-tree) + (:terminal* delimiter "==") (:rule eq-sub-expr))) (define-rule eq-sub-expr () @@ -300,7 +300,7 @@ (define-rule in-expr () (:and (:rule* left-in-expr) - (:terminal keyword "in" :exclude-from-tree) + (:terminal* keyword "in") (:rule* right-in-expr))) (define-rule left-in-expr () @@ -336,9 +336,9 @@ (define-rule item-list () (:and - (:terminal delimiter "[" :exclude-from-tree) + (:terminal* delimiter "[") (:rule* literal-list) - (:terminal delimiter "]" :exclude-from-tree))) + (:terminal* delimiter "]"))) (define-rule literal-list () (:or @@ -350,7 +350,7 @@ (define-rule rest-literal-list () (:or (:and - (:terminal delimiter "," :exclude-from-tree) + (:terminal* delimiter ",") (:rule literal) (:rule* rest-literal-list)) (:empty))) diff --git a/nobot/botscript/types/parser-types.lisp b/nobot/botscript/types/parser-types.lisp index c12bec4..1c17e2b 100644 --- a/nobot/botscript/types/parser-types.lisp +++ b/nobot/botscript/types/parser-types.lisp @@ -26,7 +26,7 @@ (:value "var-declarations" :description "var declarations") (:value "var-decls-list" :description "var declarations list") (:value "var-decl" :description "var declaration") - (:value "start-from-stmt" :description "start from statement") + (:value "start-from-stmt" :description "'start from' statement") (:value "state-points-declarations" :description "state points declarations") (:value "state-points-decls" :description "state points declaration list") (:value "state-point-decl" :description "state point declaration") @@ -38,28 +38,28 @@ (:value "stmt-list" :description "statement list") (:value "stmt" :description "statement") (:value "expr" :description "expression") - (:value "gotov-expr" :description "go to vertex expression") - (:value "say-expr" :description "say expression") - (:value "say-expr-args" :description "arguments of say expression") - (:value "say-expr-arg" :description "argument of say expression") - (:value "rest-say-expr-args" :description "rest of arguments of say expression") - (:value "save-to-expr" :description "expression save to") - (:value "if-stmt" :description "if statement") - (:value "else-block" :description "else block") + (:value "gotov-expr" :description "'go to' vertex expression") + (:value "say-expr" :description "'say' expression") + (:value "say-expr-args" :description "arguments of 'say' expression") + (:value "say-expr-arg" :description "argument of 'say' expression") + (:value "rest-say-expr-args" :description "rest of arguments of 'say' expression") + (:value "save-to-expr" :description "'save to' expression") + (:value "if-stmt" :description "'if' statement") + (:value "else-block" :description "'else' block") (:value "cond-expr" :description "condition expression") (:value "logic-expr" :description "logical expression") - (:value "equal-expr" :description "equal expression") - (:value "eq-sub-expr" :description "equal sub expression") - (:value "literal-or-id-or-input" :description "literal or id or ?input") + (:value "equal-expr" :description "'equal' expression") + (:value "eq-sub-expr" :description "'equal' sub expression") + (:value "literal-or-id-or-input" :description "literal, id or keyword ?input") (:value "string-or-number" :description "string or number") (:value "literal" :description "literal") (:value "item-list" :description "list of items") (:value "literal-list" :description "list of literals") (:value "rest-literal-list" :description "rest of list of literals") - (:value "in-expr" :description "in expression") - (:value "left-in-expr" :description "left part of in-expression") - (:value "right-in-expr" :description "right part of in-expression") - (:value "gotov-arg" :description "gotov arg: id or keyword self") + (:value "in-expr" :description "'in' expression") + (:value "left-in-expr" :description "left part of 'in' expression") + (:value "right-in-expr" :description "right part of 'in' expression") + (:value "gotov-arg" :description "'go to' expression arg: id or keyword self") )) (defmethod get-from-type (type (class-type (eql :sort)) what-need) diff --git a/nobot/codegen/js/core.lisp b/nobot/codegen/js/core.lisp index ad9e357..2aac48f 100644 --- a/nobot/codegen/js/core.lisp +++ b/nobot/codegen/js/core.lisp @@ -185,7 +185,7 @@ (translate :js (first sub-tree) sub-tree))) (defmethod translate ((lang (eql :js)) (sort (eql :item-list)) tree) - (mapcar (curry #'translate :js :literal) (cdr tree))) + `(:list ,@(mapcar (curry #'translate :js :literal) (cdr tree)))) (defmethod translate ((lang (eql :js)) (sort (eql :id)) tree) (second tree)) diff --git a/nobot/toplevel/context.lisp b/nobot/toplevel/context.lisp index 8ca2bf3..aabadf7 100644 --- a/nobot/toplevel/context.lisp +++ b/nobot/toplevel/context.lisp @@ -6,7 +6,12 @@ (:use :cl) (:import-from :nobot/logger #:log-info) + (:import-from :alexandria + #:with-gensyms) + (:import-from :nobot/utils + #:with-time) (:export #:*context* + #:with-total-time #:with-translator-context #:add-info-log #:add-debug-log @@ -62,24 +67,37 @@ :initform nil :accessor get-code-gen-result ))) + (defun regist (option value) (case option (:parser - (progn - (log-info "parse botscript...") - (setf (get-parser-result *context*) (funcall value)))) + (setf (get-parser-result *context*) + (multiple-value-bind (bench-time res) + (with-time () + (funcall value)) + (log-info "parsed botscript... (~f s)" bench-time) + res))) (:post-processing - (progn - (log-info "botscript post processing...") - (setf (get-post-processing-result *context*) (funcall value)))) + (setf (get-post-processing-result *context*) + (multiple-value-bind (bench-time res) + (with-time () + (funcall value)) + (log-info "botscript post processed... (~f s)" bench-time) + res))) (:project-generation - (progn - (log-info "project generation...") - (setf (get-projectgen-result *context*) (funcall value)))) - (:code-generation - (progn - (log-info "code generation...") - (setf (get-code-gen-result *context*) (funcall value)))))) + (setf (get-projectgen-result *context*) + (multiple-value-bind (bench-time res) + (with-time () + (funcall value)) + (log-info "project generated... (~f s)" bench-time) + res))) + (:code-generation + (setf (get-code-gen-result *context*) + (multiple-value-bind (bench-time res) + (with-time () + (funcall value)) + (log-info "code generated... (~f s)" bench-time) + res))))) (defmacro with-translator-context ((&key source-type source) &body body) `(let ((*context* @@ -137,3 +155,11 @@ (eq source-type :string)) t (error "Unknown source type: ~a" source-type))) + +(defmacro with-total-time (() &body body) + (with-gensyms (bench-time res) + `(multiple-value-bind (,bench-time ,res) + (with-time () + ,@body) + (log-info "total time: ~f s" ,bench-time) + ,res))) diff --git a/nobot/toplevel/error-handling.lisp b/nobot/toplevel/error-handling.lisp index f147e59..f3ff544 100644 --- a/nobot/toplevel/error-handling.lisp +++ b/nobot/toplevel/error-handling.lisp @@ -7,7 +7,8 @@ (:import-from :alexandria #:with-gensyms) (:import-from :nobot/logger - #:log-error) + #:log-error + #:log-warn) (:export ;; Level 1 errors #:raise-bs-lexer-error @@ -80,4 +81,8 @@ nil) (projectgen-error (c) (declare (ignore c)) + nil) + (sb-sys::interactive-interrupt (c) + (declare (ignore c)) + (log-warn "NOBOT interrupted!") nil))) diff --git a/nobot/toplevel/translator.lisp b/nobot/toplevel/translator.lisp index 8537eb3..3fccbb3 100644 --- a/nobot/toplevel/translator.lisp +++ b/nobot/toplevel/translator.lisp @@ -6,7 +6,8 @@ (:use :cl) (:import-from :nobot/toplevel/context #:with-translator-context - #:regist) + #:regist + #:with-total-time) (:import-from :nobot/botscript #:parse-file #:botscript-post-process) @@ -30,15 +31,17 @@ (with-translator-context (:source-type :file :source file) (with-logger ((configure-logger)) - (toplevel-error-handler - ;; Level 1: parse source file and get instance with tree of code - (regist :parser (lambda () (parse-file file :return-instance t))) ;; remove passing args, use context - ;; Level 2: post parsing processing - (regist :post-processing (lambda () (botscript-post-process))) - ;; Level 3: generate project - (regist :project-generation (lambda () (generate-project))) - ;; Level 4: generate code - (regist :code-generation (lambda () (generate-code))))))) + (with-total-time () + (toplevel-error-handler + ;; Level 1: parse source file and get instance with tree of code + ;;TODO: ;; remove passing args, use context + (regist :parser (lambda () (parse-file file :return-instance t))) + ;; Level 2: post parsing processing + (regist :post-processing (lambda () (botscript-post-process))) + ;; Level 3: generate project + (regist :project-generation (lambda () (generate-project))) + ;; Level 4: generate code + (regist :code-generation (lambda () (generate-code)))))))) (defun *run-and-burn-in-runtime* () ;; WIP diff --git a/nobot/utils/program-utils.lisp b/nobot/utils/program-utils.lisp index 3e0d805..c8a2bfe 100644 --- a/nobot/utils/program-utils.lisp +++ b/nobot/utils/program-utils.lisp @@ -7,6 +7,8 @@ (:import-from :cl-fad #:pathname-directory-pathname) (:import-from :unix-opts) + (:import-from :alexandria + #:with-gensyms) (:import-from :cl-ppcre #:scan) (:export #:is-valid-file-format-? @@ -14,7 +16,8 @@ #:when-option #:it-opt #:get-pwd - #:get-root-dir)) + #:get-root-dir + #:with-time)) (in-package :nobot/utils/program-utils) @@ -38,3 +41,12 @@ (defun get-root-dir () (pathname-directory-pathname sb-ext:*core-pathname*)) + +(defmacro with-time (() &body body) + (with-gensyms (start-time res) + `(let ((,start-time (get-internal-real-time)) + (,res (progn ,@body))) + (values + (/ (- (get-internal-real-time) ,start-time) + internal-time-units-per-second) + ,res))))