refactored, changed module names, untangled dependencies between kernel language and register names. Also removed 'repl'

This commit is contained in:
Richard Harrington 2013-07-31 20:55:15 -04:00
parent d59467d2d2
commit 08dcf38214
9 changed files with 109 additions and 110 deletions

View File

@ -1,6 +1,6 @@
(ns robotwar.core
(:use [clojure.pprint]
(robotwar create exec)))
(robotwar foundry robot world game-lexicon)))
(def src-code1 " START
0 TO A
@ -17,7 +17,7 @@
(def src-code2 "WAIT GOTO WAIT")
(def src-code3 "500 TO RANDOM RANDOM RANDOM RANDOM")
(def world (init-world 30 30 (map assemble [src-code1 src-code2 src-code3])))
(def world (init-world 30 30 (map #(assemble reg-names %) [src-code1 src-code2 src-code3])))
(def step (fn [initial-state n]
(nth (iterate tick-robot initial-state) n)))

View File

@ -1,8 +1,8 @@
(ns robotwar.create
(ns robotwar.foundry
(:use (clojure [string :only [split join]]
[pprint :only [pprint]])
[clojure.core.match :only [match]]
[robotwar.lexicon :only [registers op-commands commands]]))
[robotwar.kernel-lexicon]))
(defn re-seq-with-pos
"Returns a lazy sequence of successive matches of pattern in string with position.
@ -54,33 +54,32 @@
(def return-err (constantly "Invalid word or symbol"))
(def parser-priority
[[(set registers) :register]
[(set commands) :command]
[str->int :number]
[valid-word :label]
[return-err :error]])
(defn parse-token
"removes the token-str field and adds two new fields:
:val and :type"
[{token-str :token-str :as token}]
(loop [[[parser token-type] & tail] parser-priority]
(if-let [token-val (parser token-str)]
(dissoc (into token {:val token-val, :type token-type})
:token-str)
(recur tail))))
:val and :type, based on sending the :token-str value
through a series of parsing functions until a match is found."
[reg-names token]
(let [parser-priority [[(set reg-names) :register]
[(set commands) :command]
[str->int :number]
[valid-word :label]
[return-err :error]]]
(loop [[[parser token-type] & tail] parser-priority]
(if-let [token-val (parser (:token-str token))]
(dissoc (into token {:val token-val, :type token-type})
:token-str)
(recur tail)))))
(defn parse
"take the tokens and convert them to structured source code ready for compiling.
if there's an error, returns a different type: just the token,
outside of any sequence."
[initial-tokens]
[reg-names initial-tokens]
(loop [[token & tail :as tokens] initial-tokens
parsed-tokens []]
(if (empty? tokens)
parsed-tokens
(let [parsed-token (parse-token token)]
(let [parsed-token (parse-token reg-names token)]
(if (= (:type parsed-token) :error)
parsed-token
(recur tail (conj parsed-tokens parsed-token)))))))
@ -138,25 +137,17 @@
(recur tail (assoc-in result [:labels (command :val)] next-instr-num))
(recur tail (assoc-in result [:instrs next-instr-num] instr)))))))
(defn assemble [string]
(defn assemble [reg-names string]
"compiles robotwar code, with error-checking beginning after the lexing
step. All functions that return errors will return a map with the keyword
:error, and then a token with a :val field containing the error string,
and metadata containing :pos and :line fields containing the location.
So far only parse implements error-checking."
(let [lexed (-> string split-lines strip-comments lex)]
(let [parse-with-reg-names (partial parse reg-names)
lexed (-> string split-lines strip-comments lex)]
(reduce (fn [result step]
(if (= (:type result) :error)
result
(step result)))
lexed
[parse disambiguate-minus-signs make-instr-pairs map-labels])))
(defn repl
"make it so"
[]
(loop [input (read-line)]
(when (not= input "exit")
(-> input assemble pprint)
(recur (read-line)))))
[parse-with-reg-names disambiguate-minus-signs make-instr-pairs map-labels])))

View File

@ -0,0 +1,6 @@
(ns robotwar.game-lexicon)
(def reg-names [ "DATA"
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
"N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
"AIM" "SHOT" "RADAR" "DAMAGE" "SPEEDX" "SPEEDY" "RANDOM" "INDEX" ])

View File

@ -0,0 +1,7 @@
(ns robotwar.kernel-lexicon)
(def op-commands [ "-" "+" "*" "/" "=" "#" "<" ">" ])
(def word-commands [ "TO" "IF" "GOTO" "GOSUB" "ENDSUB" ])
(def commands (concat op-commands word-commands))

View File

@ -1,12 +0,0 @@
(ns robotwar.lexicon)
(def registers [ "DATA"
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
"N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
"AIM" "SHOT" "RADAR" "DAMAGE" "SPEEDX" "SPEEDY" "RANDOM" "INDEX" "DATA" ])
(def op-commands [ "-" "+" "*" "/" "=" "#" "<" ">" ])
(def word-commands [ "TO" "IF" "GOTO" "GOSUB" "ENDSUB" ])
(def commands (concat op-commands word-commands))

View File

@ -1,19 +1,22 @@
(ns robotwar.exec
(:require (robotwar [lexicon :as lexicon]))
(:use [clojure.string :only [join]]))
(ns robotwar.robot
(:use [clojure.string :only [join]]
(robotwar kernel-lexicon game-lexicon)))
(def op-map (zipmap lexicon/op-commands
; TODO: remove the game-lexicon dependency above, when it's no longer needed
; (i.e. when we've moved the resolve-register logic out of this module)
(def op-map (zipmap op-commands
(map (fn [op]
(case op
"/" #(int (Math/round (float (/ %1 %2))))
"#" not=
(-> op read-string eval)))
lexicon/op-commands)))
op-commands)))
(defn resolve-register [registers reg]
(case reg
"RANDOM" (rand-int (registers reg))
"DATA" (registers (lexicon/registers (registers "INDEX")))
"DATA" (registers (reg-names (registers "INDEX")))
(registers reg)))
(defn resolve-arg [{arg-val :val arg-type :type} registers labels]
@ -68,48 +71,12 @@
(defn init-robot-state
"initialize all the state variables that go along
with the robot program when it's running."
[program register-vals]
with the robot program when it's running.
(Optionally, pass in a hash-map of register names and values)."
[program reg-names & [registers]]
{:program program
:acc 0
:instr-ptr 0
:registers (into (zipmap lexicon/registers (repeat 0))
register-vals)
:registers (into (zipmap reg-names (repeat 0))
registers)
:call-stack []})
(defn init-world
"initialize all the variables for a robot world"
[width height programs]
{:width width
:height height
:shells []
:robots (map-indexed (fn [idx program]
{:internal-state (init-robot-state program
{"X" (rand-int width)
"Y" (rand-int height)})
:external-state {:icon (str idx)}})
programs)})
(defn tick-world
"TODO"
[world-state])
(defn arena-text-grid
"outputs the arena, with borders"
[{:keys [width height robots]}]
(let [horiz-border-char "-"
vert-border-char "+"
header-footer (apply str (repeat (+ width 2) horiz-border-char))
field (for [y (range height), x (range width)]
(some (fn [{{{robot-x "X" robot-y "Y"} :registers} :internal-state
{icon :icon} :external-state}]
(if (= [x y] [robot-x robot-y])
icon
" "))
robots))]
(str header-footer
"\n"
(join "\n" (map #(join (apply str %) (repeat 2 vert-border-char))
(partition width field)))
"\n"
header-footer)))

40
src/robotwar/world.clj Normal file
View File

@ -0,0 +1,40 @@
(ns robotwar.world
(:use [clojure.string :only [join]]
(robotwar foundry robot)))
(defn init-world
"initialize all the variables for a robot world"
[width height programs]
{:width width
:height height
:shells []
:robots (map-indexed (fn [idx program]
{:internal-state (init-robot-state program
{"X" (rand-int width)
"Y" (rand-int height)})
:external-state {:icon (str idx)}})
programs)})
(defn tick-world
"TODO"
[world-state])
(defn arena-text-grid
"outputs the arena, with borders"
[{:keys [width height robots]}]
(let [horiz-border-char "-"
vert-border-char "+"
header-footer (apply str (repeat (+ width 2) horiz-border-char))
field (for [y (range height), x (range width)]
(some (fn [{{{robot-x "X" robot-y "Y"} :registers} :internal-state
{icon :icon} :external-state}]
(if (= [x y] [robot-x robot-y])
icon
" "))
robots))]
(str header-footer
"\n"
(join "\n" (map #(join (apply str %) (repeat 2 vert-border-char))
(partition width field)))
"\n"
header-footer)))

View File

@ -1,6 +1,7 @@
(ns robotwar.create-test
(ns robotwar.foundry-test
(:require [clojure.test :refer :all]
[robotwar.create :refer :all])
[robotwar.foundry :refer :all]
[robotwar.game-lexicon :refer :all])
(:use [clojure.string :only [join]]))
(def line1 "IF DAMAGE # D GOTO MOVE ; comment or something")
@ -211,47 +212,47 @@
(deftest parse-token-register
(testing "parsing register token"
(is (= (parse-token {:token-str "AIM"})
(is (= (parse-token reg-names {:token-str "AIM"})
{:val "AIM", :type :register}))))
(deftest parse-token-command-word
(testing "parsing command token (word)"
(is (= (parse-token {:token-str "GOTO"})
(is (= (parse-token reg-names {:token-str "GOTO"})
{:val "GOTO", :type :command}))))
(deftest parse-token-command-operator
(testing "parsing command token (operator)"
(is (= (parse-token {:token-str "#"})
(is (= (parse-token reg-names {:token-str "#"})
{:val "#", :type :command}))))
(deftest parse-token-number
(testing "parsing number token"
(is (= (parse-token {:token-str "-17"}))
(is (= (parse-token reg-names {:token-str "-17"}))
{:val -17, :type :number})))
(deftest parse-token-label
(testing "parsing label token"
(is (= (parse-token {:token-str "SCAN"})
(is (= (parse-token reg-names {:token-str "SCAN"})
{:val "SCAN", :type :label}))))
(deftest parse-token-error
(testing "parsing error token"
(is (= (parse-token {:token-str "-GOTO"})
(is (= (parse-token reg-names {:token-str "-GOTO"})
{:val "Invalid word or symbol", :type :error}))))
(deftest parse-tokens-minus-sign
(testing "parsing tokens with a binary minus sign"
(is (= (parse lexed-tokens2)
(is (= (parse reg-names lexed-tokens2)
parsed-tokens2))))
(deftest parse-tokens-negative-sign
(testing "parsing tokens with a unary negative sign"
(is (= (parse lexed-tokens3)
(is (= (parse reg-names lexed-tokens3)
parsed-tokens3))))
(deftest parse-tokens-error
(testing "parsing tokens with an invalid operator"
(is (= (parse lexed-tokens4)
(is (= (parse reg-names lexed-tokens4)
parsed-tokens4))))
(def minus-sign-disambiguated-tokens2 parsed-tokens2)
@ -293,17 +294,17 @@
(deftest assemble-test-success
(testing "compiling successfully"
(is (= (assemble (join "\n" [line1 line2 line3]))
(is (= (assemble reg-names (join "\n" [line1 line2 line3]))
multi-line-assembled))))
(deftest assemble-test-failure
(testing "assemble results in error"
(is (= (assemble (join "\n" [line1 line2 line3 line4]))
(is (= (assemble reg-names (join "\n" [line1 line2 line3 line4]))
multi-line-assembled-error))))
(deftest preserving-line-and-pos-metadata-test
(testing "line and pos metadata preserved through assembly process"
(is (= (meta (get-in (assemble (join "\n" [line1 line2 line3]))
(is (= (meta (get-in (assemble reg-names (join "\n" [line1 line2 line3]))
[:instrs 8 1]))
{:line 3, :pos 14}))))

View File

@ -1,7 +1,6 @@
(ns robotwar.exec-test
(:require [clojure.test :refer :all]
(robotwar [create :refer :all]
[exec :refer :all])))
(ns robotwar.robot-test
(:use [clojure.test]
(robotwar foundry robot game-lexicon)))
(def src-codes [ ; program 0: multi-use program
" START
@ -34,7 +33,7 @@
1 TO INDEX
DATA " ])
(def robot-history #(iterate tick-robot (init-robot-state (assemble %) {})))
(def robot-history #(iterate tick-robot (init-robot-state (assemble reg-names %) {})))
(def robot-histories (map robot-history src-codes))
(def multi-use-history (nth robot-histories 0))
(def random-check-history (nth robot-histories 1))