Skip to content

Instantly share code, notes, and snippets.

@nhusher
Created December 9, 2015 15:30
Show Gist options
  • Save nhusher/50868fcfa8594785855c to your computer and use it in GitHub Desktop.
Save nhusher/50868fcfa8594785855c to your computer and use it in GitHub Desktop.
(ns timeline.roller
(:require [instaparse.core :as insta])
(:use [clojure.string :only [join]]))
;
; -- Parsing code -----------------------------------------------------------
;
; Known limitations
; - no negative numbers
; - no functions
; - no floating point numbers
(def roll-parser
(insta/parser
"expr = sum-expr
<sum-expr> = prod-expr | add | subtract
<prod-expr> = roll-expr | multiply | divide
<roll-expr> = unary-expr | roll
<unary-expr> = integer | scope
(* binary operators - +, -, /, *, d *)
multiply = prod-expr ws* <'*'> ws* prod-expr
divide = prod-expr ws* <'/'> ws* prod-expr
add = sum-expr ws* <'+'> ws* sum-expr
subtract = sum-expr ws* <'-'> ws* sum-expr
roll = unary-expr ws* <'d'> ws* unary-expr | <'d'> unary-expr
(* statements that act like unary operators *)
scope = <'('> ws* sum-expr ws* <')'>
(* different kinds of value tokens *)
integer = #'\\d+'
<ws> = <#'[\\s,]+'>"))
(defn- parse [s]
(let [result (insta/parse roll-parser s)]
; TODO: pass along the error object for loggin and display
(if (insta/failure? result) [:error s result] result)))
(defn dice-parse [s]
(parse s))
;
; -- Evaluating code -----------------------------------------------------------
;
(defn- unwrap [result]
"Unwraps a result object so we can do simple math on it. The run fn will rewrap"
(cond
(map? result) (unwrap (:result result))
(seq? result) (reduce + result)
:else result))
(defn- run [f n c]
"Executed the provided function f and generates a result object with name n and children c"
(let [result (apply f (map unwrap c))]
{ :name n :result result :raw c }))
(defn- run-scope [c] { :name :scope, :result (:result (first c)), :raw c })
(defn- run-expr [c]
{ :name :expr :result (unwrap (first c)) :raw c })
(defn- roll
"Rolls some dice."
([sides] (roll 1 sides))
([number sides] (sort > (repeatedly (max (unwrap number) 1) #(+ (rand-int (max (unwrap sides) 2)) 1)))))
(defn- parse-int [s] (Integer. (re-find #"\d+" s)))
(defn- convert-error [e] (.-index e))
(defn- eval-expr [s]
(let [[n & args] s
continue (fn [] (map eval-expr args))]
(case n
:multiply (run * n (continue))
:divide (run / n (continue))
:add (run + n (continue))
:subtract (run - n (continue))
:expr (run-expr (continue))
:error {:name :error :result (second s) :raw (convert-error (last s)) }
:roll (run roll n (continue))
:scope (run-scope (continue))
:integer (parse-int (first args)))))
(defn dice-eval [p]
"Evaluates a dice roll expression."
(if (string? p)
(dice-eval (dice-parse p))
(eval-expr p)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment