advent-of-code-2024

My solutions to AoC 2024
Log | Files | Refs

commit 35dfaacf7838e916e7bebf9a02c17488493785b6
parent c8859bce66424fc093ae4f999552d80c4bfc0989
Author: Lukas Henkel <lh@entf.net>
Date:   Tue,  3 Dec 2024 22:04:02 +0100

Use simple parser generator to generate a parser

This is quite a lot faster than using regex

Diffstat:
Msrc/day-3.lisp | 44+++++++++++++++++++++++++++-----------------
Msrc/utils.lisp | 55++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 81 insertions(+), 18 deletions(-)

diff --git a/src/day-3.lisp b/src/day-3.lisp @@ -1,25 +1,35 @@ (defpackage #:aoc/day-3 (:use #:cl #:aoc/utils) - (:import-from :cl-ppcre) (:export #:day-3)) (in-package #:aoc/day-3) -(defparameter *regex* "(mul\\((\\d+),(\\d+)\\))|((do|don't)\\(\\))") +(defun try-read-number (stream) + (loop while (digit-char-p (peek-char nil stream)) + collect (read-char stream) into bag + finally (return (if bag + (parse-integer (coerce bag 'string)) + nil)))) + +(define-parser day-3-parser (stream) + ((mul-enabled t) + (num-1 0) + (num-2 0) + (task-1 0) + (task-2 0)) + (#\d #\o #\( #\) (setf mul-enabled t)) + (#\d #\o #\n #\' #\t #\( #\) (setf mul-enabled nil)) + (#\m #\u #\l #\( + (setf num-1 (try-read-number stream)) + #\, + (setf num-2 (try-read-number stream)) + #\) + (let ((result (* num-1 num-2))) + (incf task-1 result) + (when mul-enabled + (incf task-2 result))))) (defun day-3 (input) - (let ((input-string (read-stream-content-into-string input)) - (mul-enabled t) - (task-1 0) - (task-2 0)) - (ppcre:do-scans (start end reg-starts reg-ends *regex* input-string) - (case (aref input-string start) - (#\d - (setf mul-enabled (char/= (aref input-string (+ start 2)) #\n))) - (#\m - (let* ((num-1 (parse-integer input-string :start (aref reg-starts 1) :end (aref reg-ends 1))) - (num-2 (parse-integer input-string :start (aref reg-starts 2) :end (aref reg-ends 2))) - (result (* num-1 num-2))) - (incf task-1 result) - (when mul-enabled - (incf task-2 result)))))) + (multiple-value-bind (mul-enabled num-1 num-2 task-1 task-2) + (day-3-parser input) + (declare (ignore mul-enabled num-1 num-2)) (values task-1 task-2))) diff --git a/src/utils.lisp b/src/utils.lisp @@ -24,7 +24,8 @@ #:manhattan-distance #:do-map-neighbours #:read-number-list - #:find-pattern)) + #:find-pattern + #:define-parser)) (in-package #:aoc/utils) (defun normalize-type (type) @@ -208,3 +209,55 @@ for c-2 = (elt list (+ i length)) always (= c-1 c-2)) do (return-from find-pattern length))) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defun transform-state-table (definitions) + (loop with table = (list nil nil) + with callbacks = nil + with current = nil + for definition in definitions + do (setf current table) + (loop for element in definition + do (etypecase element + (character + (setf current (or (assoc element (cddr current)) + (car (setf (cdr (last current)) + (list (list element nil))))))) + (list + (let ((sym (GENSYM "CALLBACK"))) + (setf (cadr current) sym) + (push (cons sym element) callbacks))))) + finally (return (values table + callbacks))))) + +(defun do-parse (stream table) + (loop with current = table + with callback = nil + with global = t + for char = (read-char stream nil) + until (null char) + do (setf current (assoc char (cddr current)) + callback (cadr current)) + (if (or (null current) + (and callback + (null (funcall (symbol-value callback))))) + (progn + (setf current table) + (unless global + (unread-char char stream)) + (setf global t)) + (setf global nil)))) + +(defmacro define-parser (name (stream) (&rest variable-bindings) &body parse-tables) + (multiple-value-bind (table callbacks) + (transform-state-table parse-tables) + (let ((table-var (gensym "TABLE")) + (callback-syms (mapcar #'car callbacks))) + `(defun ,name (,stream) + (let ((,table-var ',table) + ,@variable-bindings) + (let ,(loop for (sym . code) in callbacks + collect `(,sym (lambda () ,code))) + (declare (special ,@callback-syms)) + (do-parse stream ,table-var)) + (values ,@(mapcar #'car variable-bindings)))))))