Common Lisp Solution to While Language
I have written a solution to Hackerrank's problem While Language in Common Lisp, solely for fun. Several years ago, I have solved it using OCaml.
Where does 'fun' come from? In Lisp, it's really easy to construct ASTs. In a
lot of scenarios, you can effortlessly transpile source code into Lisp forms
and then ask your Lisp implementation to analyse, optimise and execute it. For
example, after lexing and parsing, I have written a function gen-lisp-code
to
expose the transpiled Lisp code:
(pprint (gen-lisp-code "fact := 1 ; val := 10000 ; cur := val ; mod := 1000000007 ; while ( cur > 1 ) do { fact := fact * cur ; fact := fact - fact / mod * mod ; cur := cur - 1 } ; cur := 0")) #| (LAMBDA () (LET* ((|cur| 0) (|fact| 0) (|mod| 0) (|val| 0)) (DECLARE (OPTIMIZE SPEED (SPACE 0) (SAFETY 1)) (TYPE (INTEGER 0 2000000000000000000) |cur| |fact| |mod| |val|)) (SETF |fact| 1) (SETF |val| 10000) (SETF |cur| |val|) (SETF |mod| 1000000007) (DO () ((NOT (> |cur| 1))) (SETF |fact| (* |fact| |cur|)) (SETF |fact| (- |fact| (* (FLOOR (/ |fact| |mod|)) |mod|))) (SETF |cur| (- |cur| 1))) (SETF |cur| 0) (FORMAT T "~a ~d~%" "cur" |cur|) (FORMAT T "~a ~d~%" "fact" |fact|) (FORMAT T "~a ~d~%" "mod" |mod|) (FORMAT T "~a ~d~%" "val" |val|))) |#
Afterwards, the lambda definition can be sent to Lisp's compile
function and
generate a Lisp function which can be excuted to print the desired results:
(funcall (compile nil (gen-lisp-code "fact := 1 ; val := 10000 ; cur := val ; mod := 1000000007 ; while ( cur > 1 ) do { fact := fact * cur ; fact := fact - fact / mod * mod ; cur := cur - 1 } ; cur := 0"))) ;; cur 0 ;; fact 531950728 ;; mod 1000000007 ;; val 10000 ;; NIL
Therefore, my solution is actually not an interpreter but a compiler that transpiles While language code into Lisp forms. In other languages, writing a compiler arguably requires more code; but in Lisp, with its comprehensive run-time system, it becomes much easier.
The full solution is shown below (or from this Github Gist):
;;;; while.lisp ;;;; A solution to Hackerrank's While Language problem -- ;;;; https://www.hackerrank.com/challenges/while-language-fp/problem ;;;; Author: Tianyu Gu ([email protected]) (in-package #:cl-user) (defpackage #:while (:use #:cl) (:nicknames #:while-lang) (:export #:gen-lisp-code #:run-program)) (in-package #:while) ;;; Part I. Lex ;;; Variable, Numeral, ;;; AOp: Plus, Minus, Mul, Div, Assign ;;; BOp: And, Or, ;;; ROp('<' | '>'): Gt, Lt, ;;; Other Keywords: True, False, Lparen, Rparen, If, Then, Else, ;;; --------------- Semicolon, While, Do, Lbracket, Rbracket (defstruct token type val) (defmethod print-object ((obj token) stream) (with-slots (type val) obj (if val (format stream "~A(~A)~%" type val) (format stream "~A~%" type)))) (defun make-var (var) (make-token :type :var :val var)) (defun make-num (num) (make-token :type :num :val num)) (defconstant +keywords-table+ (loop with table = (make-hash-table :test 'equal) for (k . v) in '((#\+ . :plus) (#\- . :minus) (#\* . :mul) (#\/ . :div) (":=" . :assign) ("and" . :and) ("or" . :or) (#\> . :gt) (#\< . :lt) ("true" . :true) ("false" . :false) (#\( . :lparen) (#\) . :rparen) ("if" . :if) ("then" . :then) ("else" . :else) (#\; . :semicolon) ("while" . :while) ("do" . :do) (#\{ . :lbracket) (#\} . :rbracket)) do (setf (gethash k table) v) finally (return table))) (defun make-keyword (part) (make-token :type (gethash part +keywords-table+))) (defstruct token-stream tokens len ptr) (defmethod print-object ((obj token-stream) stream) (with-slots (len ptr) obj (format stream "<~d tokens in total, ~d consumed, ~d left.>~%" len ptr (- len ptr)))) (defun next-token (tokens) (with-slots (tokens len ptr) tokens (unless (= ptr len) (let ((rt (aref tokens ptr))) (incf ptr) rt)))) (defun peek-token (tokens) (with-slots (tokens len ptr) tokens (unless (= ptr len) (aref tokens ptr)))) (defun expect-token (tokens expect) (let ((next (next-token tokens))) (unless (eq (token-type next) expect) (error "Parsing error: unexpected token ~A" next)))) (defun token-stream-empty-p (tokens) (with-slots (len ptr) tokens (= len ptr))) (defun lex-by-pred (in pred) (declare (type stream in)) (with-output-to-string (o) (loop for c = (peek-char nil in nil nil) while (and c (funcall pred c)) do (write-char (read-char in) o)))) (defun lex-str (in) (declare (type stream in)) (lex-by-pred in (lambda (c) (char<= #\a c #\z)))) (defun lex-num (in) (declare (type stream in)) (let ((str (lex-by-pred in (lambda (c) (char<= #\0 c #\9))))) (parse-integer str :junk-allowed nil))) (defun lex (in) (declare (type stream in)) (let ((tokens (make-array 0 :element-type 'token :adjustable t :fill-pointer 0))) (loop for c = (peek-char t in nil nil) while c do (cond (;; keywords (find c #(#\+ #\- #\* #\/ #\> #\< #\( #\) #\; #\{ #\}) :test 'char=) (vector-push-extend (make-keyword (read-char in)) tokens)) (;; Assign (char= c #\:) (read-char in) ;; #\: (read-char in) ;; #\= (vector-push-extend (make-keyword ":=") tokens)) (;; a var or a keyword (char<= #\a c #\z) (let ((str (lex-str in))) (if (find str #("and" "or" "true" "false" "if" "then" "else" "while" "do") :test 'string=) (vector-push-extend (make-keyword str) tokens) (vector-push-extend (make-var str) tokens)))) (;; a numeral (char<= #\0 c #\z) (vector-push-extend (make-num (lex-num in)) tokens))) finally (return (make-token-stream :tokens tokens :len (length tokens) :ptr 0))))) ;;; Part II. Parse (recursive descent) (defun parse (tokens) (let (stmts) (tagbody start (push (parse-statement tokens) stmts) (if (and (peek-token tokens) (eq :semicolon (token-type (peek-token tokens)))) (progn (next-token tokens) (go start)) (return-from parse (nreverse stmts)))))) (defun parse-statement (tokens) (let ((token (peek-token tokens))) (case (token-type token) (:var (parse-assign tokens)) (:if (next-token tokens) (parse-if tokens)) (:while (next-token tokens) (parse-while tokens)) (t (error "Parsing error."))))) (defun parse-assign (tokens) (let (var val) (setq var (intern (token-val (next-token tokens)) :while)) (expect-token tokens :assign) (setq val (parse-aexpr tokens)) `(setf ,var ,val))) (defun parse-aexpr (tokens) (labels ((parse-factor (tokens) (let ((token (next-token tokens))) (case (token-type token) (:var (intern (token-val token) :while)) (:num (token-val token)) (:lparen (let ((arith (parse-aexpr tokens))) (expect-token tokens :rparen) arith)) (t (error "Parsing error"))))) (parse-term (tokens) (let ((left (parse-factor tokens))) (tagbody start (cond ((and (peek-token tokens) (eq :mul (token-type (peek-token tokens)))) (next-token tokens) (setq left `(* ,left ,(parse-factor tokens))) (go start)) ((and (peek-token tokens) (eq :div (token-type (peek-token tokens)))) (next-token tokens) (setq left `(floor (/ ,left ,(parse-factor tokens)))) (go start)) (t (return-from parse-term left))))))) (let ((left (parse-term tokens))) (tagbody start (cond ((and (peek-token tokens) (eq :plus (token-type (peek-token tokens)))) (next-token tokens) (setq left `(+ ,left ,(parse-term tokens))) (go start)) ((and (peek-token tokens) (eq :minus (token-type (peek-token tokens)))) (next-token tokens) (setq left `(- ,left ,(parse-term tokens))) (go start)) (t (return-from parse-aexpr left))))))) (defun parse-if (tokens) (let (test then else) (setq test (parse-bexpr tokens)) (expect-token tokens :then) (expect-token tokens :lbracket) (setq then (parse tokens)) (expect-token tokens :rbracket) (expect-token tokens :else) (expect-token tokens :lbracket) (setq else (parse tokens)) (expect-token tokens :rbracket) `(if ,test (progn ,@then) (progn ,@else)))) (defun parse-while (tokens) (let (test body) (setq test (parse-bexpr tokens)) (expect-token tokens :do) (expect-token tokens :lbracket) (setq body (parse tokens)) (expect-token tokens :rbracket) `(do () ((not ,test)) ,@body))) (defun parse-bexpr (tokens) (labels ((parse-bexpr/1 () (case (token-type (peek-token tokens)) (:true (next-token tokens) t) (:false (next-token tokens) nil) (:lparen (next-token tokens) (let ((bexpr (parse-bexpr tokens))) (expect-token tokens :rparen) bexpr)) (t (let ((left (parse-aexpr tokens))) (case (token-type (next-token tokens)) (:gt `(> ,left ,(parse-aexpr tokens))) (:lt `(< ,left ,(parse-aexpr tokens))) (t (error "Parsing error")))))))) (let ((left (parse-bexpr/1))) (tagbody start (case (token-type (peek-token tokens)) (:and (next-token tokens) (setq left `(and ,left ,(parse-bexpr/1))) (go start)) (:or (next-token tokens) (setq left `(or ,left ,(parse-bexpr/1))) (go start)) (t (return-from parse-bexpr left))))))) ;;; Part III: Compile (transpile, actually) (defun generate-symbol-table (stmts) (when stmts (loop with syms = (list) for stmt in stmts do (case (car stmt) (setf (pushnew (second stmt) syms :test 'eq)) (do (let ((res (generate-symbol-table (cdddr stmt)))) (when res (setf syms (concatenate 'list syms res))))) (if (let ((res1 (generate-symbol-table (cdr (third stmt)))) (res2 (generate-symbol-table (cdr (fourth stmt))))) (setf syms (concatenate 'list syms res1 res2))))) finally (return (sort (delete-duplicates syms :test 'eq) 'string< :key 'symbol-name))))) (defun gen-lisp-code (program) (let* ((stmts (parse (with-input-from-string (in program) (lex in)))) (syms (generate-symbol-table stmts))) `(lambda () (let* ,(mapcar (lambda (sym) `(,sym 0)) syms) (declare (optimize speed (space 0) (safety 1)) (type (integer 0 #.(* 2 (expt 10 18))) ,@syms)) ,@stmts ,@(mapcar (lambda (sym) `(format t "~a ~d~%" ,(symbol-name sym) ,sym)) syms))))) (defun run-program (program) (let ((fn (compile 'nil (gen-lisp-code program)))) (funcall fn))) ;;; Entry point (in-package #:cl-user) (defun main () (while:run-program (with-output-to-string (o) (loop for line = (read-line t nil nil) while line do (write-line line o))))) ;;; uncomment this line if you wanto to submit it to Hackerrank ;; (main) ;;; test case (defvar *test-0* "base := 2 ; power := 100 ; prime := 1000000007 ; res := 1 ; while ( power > 0 ) do { parity := power - ( power / 2 * 2 ) ; if ( power - power / 2 * 2 > 0 ) then { res := res * base ; res := res - res / prime * prime } else { res := res } ; base := base * base ; base := base - base / prime * prime ; power := power / 2 }") (defvar *test-1* "fact := 1 ; val := 10000 ; cur := val ; mod := 1000000007 ; while ( cur > 1 ) do { fact := fact * cur ; fact := fact - fact / mod * mod ; cur := cur - 1 } ; cur := 0") (defvar *test-2* "a := 267815000 ; b := 556456000 ; while ( b > 0 ) do { t := b ; b := a - ( a / b ) * b ; a := t } ; res := a") (defvar *test-3* "a := 10 ; b := 100 ; c := 1000 ; if ( a > b and a > c ) then { largest := a } else { if ( b > a and b > c ) then { largest := b } else { largest := c } } ; if ( a > b and a < c ) then { middle := a } else { if ( b > a and b < c ) then { middle := b } else { middle := c } } ; if ( a < b and a < c ) then { smallest := a } else { if ( b < a and b < c ) then { smallest := b } else { smallest := c } }") (defvar *test-4* "sum := 0 ; cur := 0 ; while ( cur < 10000 ) do { cur := cur + 1 ; sum := sum + cur } ; p := 1000000007 ; cur := 0 ; prod := 1 ; while ( cur < 10000 ) do { cur := cur + 1 ; prod := prod * cur ; prod := prod - prod / p * p }") (defvar *test-5* "a := 1000 ; b := 2000 ; c := b ; b := a ; a := c ; c := 0") (defvar *test-6* "a := 10 ; b := 100 ; if ( a < b ) then { min := a ; max := b } else { min := b ; max := a }") (defvar *test-7* "i := 0 ; oddsum := 0 ; evensum := 0 ; while ( i < 100 ) do { j := 0 ; while ( j < i ) do { if ( j - j / 2 * 2 > 0 ) then { oddsum := oddsum + j } else { evensum := evensum + j } ; j := j + 1 } ; i := i + 1 }")