diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index b9e3ed4..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 66b50af..a6b37b8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ notes # Don't commit the data directory that we'll # use to hold the data from # https://github.com/thelmuth/program-synthesis-benchmark-datasets -/data \ No newline at end of file +/data +**/.DS_Store diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index da17f2d..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/propeller/.DS_Store b/src/propeller/.DS_Store deleted file mode 100644 index 306b0a3..0000000 Binary files a/src/propeller/.DS_Store and /dev/null differ diff --git a/src/propeller/push/instructions/string.cljc b/src/propeller/push/instructions/string.cljc index 91d1a5a..d602430 100755 --- a/src/propeller/push/instructions/string.cljc +++ b/src/propeller/push/instructions/string.cljc @@ -37,7 +37,7 @@ :string_contains ^{:stacks #{:boolean :string}} (fn [state] - (make-instruction state string/includes? [:string :string] :boolean))) + (make-instruction state #(string/includes? %2 %1) [:string :string] :boolean))) ;; Pushes TRUE if the top CHAR is contained in the top STRING, and FALSE ;; otherwise @@ -67,7 +67,9 @@ :string_first ^{:stacks #{:char :string}} (fn [state] - (make-instruction state first [:string] :char))) + (make-instruction state + #(if (empty? %) :ignore-instruction (first %)) + [:string] :char))) ;; Pushes the STRING version of the top BOOLEAN, e.g. "true" (def-instruction @@ -103,7 +105,11 @@ :string_indexof_char ^{:stacks #{:char :integer :string}} (fn [state] - (make-instruction state string/index-of [:string :char] :integer))) + (make-instruction state + #(let [index (string/index-of %1 %2)] + (if index index :ignore-instruction)) + [:string :char] + :integer))) ;; Iterates over the top STRING using code on the EXEC stack (def-instruction @@ -129,12 +135,13 @@ (state/push-to-stack :exec (state/peek-stack state :exec)) (state/push-to-stack :char (first top-item)))))))) -;; Pushes the last CHAR of the top STRING +;; Pushes the last CHAR of the top STRING. +;; If the string is empty, do nothing (def-instruction :string_last ^{:stacks #{:char :string}} (fn [state] - (make-instruction state last [:string] :char))) + (make-instruction state #(if (empty? %) :ignore-instruction (last %)) [:string] :char))) ;; Pushes the length of the top STRING onto the INTEGER stack (def-instruction @@ -150,7 +157,13 @@ :string_nth ^{:stacks #{:char :integer :string}} (fn [state] - (make-instruction state #(nth %2 (mod %1 (count %2))) [:integer :string] :char))) + (make-instruction state + #(let [str-length (count %2)] + (if (= 0 str-length) + :ignore-instruction + (nth %2 (mod %1 str-length)))) + [:integer :string] + :char))) ;; Pushes the number of times the top CHAR occurs in the top STRING onto the ;; INTEGER stack @@ -254,10 +267,12 @@ ^{:stacks #{:char :integer :string}} (fn [state] (make-instruction state - #(let [index (mod %2 (count %3)) + #(if (empty? %3) + :ignore-instruction + (let [index (mod %2 (count %3)) beginning (take index %3) end (drop (inc index) %3)] - (apply str (concat beginning (list %1) end))) + (apply str (concat beginning (list %1) end)))) [:char :integer :string] :string))) diff --git a/src/propeller/push/utils/globals.cljc b/src/propeller/push/utils/globals.cljc index 26c37f1..aad494b 100644 --- a/src/propeller/push/utils/globals.cljc +++ b/src/propeller/push/utils/globals.cljc @@ -8,4 +8,23 @@ ;; Limits the number of items that can be duplicated onto a stack at once. ;; We might want to extend this to limit all the different that things may be ;; placed on a stack. -(def max-stack-items 100) \ No newline at end of file +(def max-stack-items 100) + + + +;; ============================================================================= +;; Values used by the Push instructions to keep computed values within +;; reasonable size limits. +;; ============================================================================= + +;; Used by keep-number-reasonable as the maximum magnitude of any integer/float +(def max-number-magnitude 1.0E12) + +;; Used by keep-number-reasonable as the minimum magnitude of any float +(def min-number-magnitude 1.0E-10) + +;; Used by reasonable-string-length? to ensure that strings don't get too large +(def max-string-length 1000) + +;; Used by keep-vector-reasonable to ensure that vectors don't get too large +(def max-vector-length 1000) \ No newline at end of file diff --git a/src/propeller/push/utils/helpers.cljc b/src/propeller/push/utils/helpers.cljc index 4eccfb6..748f9eb 100755 --- a/src/propeller/push/utils/helpers.cljc +++ b/src/propeller/push/utils/helpers.cljc @@ -2,9 +2,43 @@ (:require [clojure.set] [propeller.push.core :as push] [propeller.push.state :as state] + [propeller.push.utils.globals :as globals] #?(:cljs [goog.string :as gstring]) #?(:cljs [goog.string.format]))) +;; Returns a version of the number n that is within reasonable size bounds +(defn keep-number-reasonable + [n] + (cond + (integer? n) + (cond + (> n globals/max-number-magnitude) (long globals/max-number-magnitude) + (< n (- globals/max-number-magnitude)) (long (- globals/max-number-magnitude)) + :else n) + :else + (cond + (#?(:clj Double/isNaN + :cljs js/isNaN) n) 0.0 + (or (= n #?(:clj Double/POSITIVE_INFINITY + :cljs js/Infinity)) + (> n globals/max-number-magnitude)) globals/max-number-magnitude + (or (= n #?(:clj Double/NEGATIVE_INFINITY + :cljs js/-Infinity)) + (< n (- globals/max-number-magnitude))) (- globals/max-number-magnitude) + (< (- globals/min-number-magnitude) n globals/min-number-magnitude) 0.0 + :else n))) + +;; Returns true if the string is of a reasonable size +(defn reasonable-string-length? + [string] + (let [length (count string)] + (<= length globals/max-string-length))) + +;; Returns true if the vector is of a reasonable size +(defn reasonable-vector-length? + [vector] + (let [length (count vector)] + (<= length globals/max-vector-length))) ;; Takes a state and a collection of stacks to take args from. If there are ;; enough args on each of the desired stacks, returns a map with keys @@ -40,9 +74,23 @@ state (let [result (apply function (:args popped-args)) new-state (:state popped-args)] - (if (= result :ignore-instruction) + (cond + (number? result) + (state/push-to-stack new-state return-stack (keep-number-reasonable result)) + ;; + (and (string? result) + (not (reasonable-string-length? result))) state - (state/push-to-stack new-state return-stack result)))))) + ;; + (and (vector? result) + (not (reasonable-vector-length? result))) + state + ;; + (= result :ignore-instruction) + state + ;; + :else + (state/push-to-stack new-state return-stack result)))))) ;; Given a set of stacks, returns all instructions that operate on those stacks ;; only. Won't include random instructions unless :random is in the set as well diff --git a/test/propeller/push/instructions/string_spec.clj b/test/propeller/push/instructions/string_spec.clj new file mode 100644 index 0000000..d0b00fb --- /dev/null +++ b/test/propeller/push/instructions/string_spec.clj @@ -0,0 +1,560 @@ +(ns propeller.push.instructions.string-spec + (:require + [clojure.string :as string] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop] + [clojure.test.check.clojure-test :as ct :refer [defspec]] + [propeller.push.state :as state] + [propeller.push.core :as core] + [propeller.push.instructions.string :as string-instructions] + [propeller.push.interpreter :as interpreter])) + + +;; string/butlast + +(defn check-butlast + [value] + (let [start-state (state/push-to-stack state/empty-state + :string + value) + end-state ((:string_butlast @core/instruction-table) start-state) + expected-result (apply str (butlast value))] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec butlast-spec 100 + (prop/for-all [str gen/string] + (check-butlast str))) + + +;; string/concat + +(defn check-concat + [value1 value2] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value1) + (state/push-to-stack :string value2)) + end-state ((:string_concat @core/instruction-table) start-state) + expected-result (str value1 value2)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec concat-spec 100 + (prop/for-all [str1 gen/string + str2 gen/string] + (check-concat str1 str2))) + + +;; string/conj-char + +(defn check-conj-char + [value char] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char)) + end-state ((:string_conj_char @core/instruction-table) start-state) + expected-result (str value char)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec conj-char-spec 100 + (prop/for-all [str gen/string + char gen/char] + (check-conj-char str char))) + + +;; string/contains + +(defn check-contains + [value1 value2] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value1) + (state/push-to-stack :string value2)) + end-state ((:string_contains @core/instruction-table) start-state) + expected-result (string/includes? value2 value1)] + (= expected-result + (state/peek-stack end-state :boolean)))) + +(defspec contains-spec 100 + (prop/for-all [str1 gen/string + str2 gen/string] + (check-contains str1 str2))) + + +;; string/contains-char + +(defn check-contains-char + [value char] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char)) + end-state ((:string_contains_char @core/instruction-table) start-state) + expected-result (string/includes? value (str char))] + (= expected-result + (state/peek-stack end-state :boolean)))) + +(defspec contains-char-spec 100 + (prop/for-all [str gen/string + char gen/char] + (check-contains-char str char))) + + +;; string/drop + +(defn check-drop + [value n] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :integer n)) + end-state ((:string_drop @core/instruction-table) start-state) + expected-result (apply str (drop n value))] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec drop-spec 100 + (prop/for-all [str gen/string + int gen/small-integer] + (check-drop str int))) + + +;; string/empty-string + +(defn check-empty-string + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_empty_string @core/instruction-table) start-state) + expected-result (empty? value)] + (= expected-result + (state/peek-stack end-state :boolean)))) + +(defspec empty-string-spec 100 + (prop/for-all [str gen/string] + (check-empty-string str))) + + +;; string/first + +(defn check-first + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_first @core/instruction-table) start-state) + expected-result (first value)] + (or + (and (empty? value) + (= (state/peek-stack end-state :string) value) + (state/empty-stack? end-state :char)) + (and (= expected-result + (state/peek-stack end-state :char)) + (state/empty-stack? end-state :string))))) + +(defspec first-spec 100 + (prop/for-all [str gen/string] + (check-first str))) + + +;; string/from-boolean + +(defn check-from-boolean + [value] + (let [start-state (state/push-to-stack state/empty-state :boolean value) + end-state ((:string_from_boolean @core/instruction-table) start-state) + expected-result (str value)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec from-boolean-spec 10 + (prop/for-all [bool gen/boolean] + (check-from-boolean bool))) + + +;; string/from-char + +(defn check-from-char + [value] + (let [start-state (state/push-to-stack state/empty-state :char value) + end-state ((:string_from_char @core/instruction-table) start-state) + expected-result (str value)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec from-char-spec 100 + (prop/for-all [char gen/char] + (check-from-char char))) + + +;; string/from-float + +(defn check-from-float + [value] + (let [start-state (state/push-to-stack state/empty-state :float value) + end-state ((:string_from_float @core/instruction-table) start-state) + expected-result (str value)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec from-float-spec 100 + (prop/for-all [float gen/double] + (check-from-float float))) + + +;; string/from-integer + +(defn check-from-integer + [value] + (let [start-state (state/push-to-stack state/empty-state :integer value) + end-state ((:string_from_integer @core/instruction-table) start-state) + expected-result (str value)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec from-integer-spec 100 + (prop/for-all [int gen/small-integer] + (check-from-integer int))) + + +;; string/indexof-char + +(defn check-indexof-char + [value char] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char)) + end-state ((:string_indexof_char @core/instruction-table) start-state) + expected-result (string/index-of value char)] + (or + (and (not expected-result) + (= (state/peek-stack end-state :string) value) + (= (state/peek-stack end-state :char) char) + (state/empty-stack? end-state :integer)) + (= expected-result + (state/peek-stack end-state :integer))))) + +(defspec indexof-char-spec 100 + (prop/for-all [str gen/string + char gen/char] + (check-indexof-char str char))) + + +;; string/iterate + +(defn check-iterate + [value] + (let [print-instr (keyword "char_print") + iter-instr (keyword "string_iterate") + program [iter-instr print-instr] + start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :output "")) + ; 4 times the string length should be enough for this iteration, perhaps even + ; more than we strictly need. + end-state (interpreter/interpret-program program start-state (* 4 (count value)))] + (= value + (state/peek-stack end-state :output)))) + +(defspec iterate-spec 100 + (prop/for-all [value gen/string] + (check-iterate value))) + +;; string/last + +(defn check-last + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_last @core/instruction-table) start-state) + expected-result (last value)] + (or + (and (empty? value) + (state/empty-stack? end-state :char) + (= value (state/peek-stack end-state :string))) + (and (state/empty-stack? end-state :string) + (= expected-result + (state/peek-stack end-state :char)))))) + +(defspec last-spec 100 + (prop/for-all [str gen/string] + (check-last str))) + + +;; string/length + +(defn check-length + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_length @core/instruction-table) start-state) + expected-result (count value)] + (= expected-result + (state/peek-stack end-state :integer)))) + +(defspec length-spec 100 + (prop/for-all [str gen/string] + (check-length str))) + + +;; string/nth + +(defn check-nth + [value n] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :integer n)) + end-state ((:string_nth @core/instruction-table) start-state)] + (or + (and (empty? value) + (state/empty-stack? end-state :char) + (= value (state/peek-stack end-state :string)) + (= n (state/peek-stack end-state :integer))) + (= (nth value (mod n (count value))) + (state/peek-stack end-state :char))))) + +(defspec nth-spec 100 + (prop/for-all [str gen/string + int gen/small-integer] + (check-nth str int))) + + +;; string/occurencesof_char + +(defn check-occurencesof-char + [value char] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char)) + end-state ((:string_occurencesof_char @core/instruction-table) start-state) + expected-result (count (filter #(= char %) value))] + (= expected-result + (state/peek-stack end-state :integer)))) + +(defspec occurencesof-char-spec 100 + (prop/for-all [str gen/string + char gen/char] + (check-occurencesof-char str char))) + + +;; string/parse-to-chars + +(defn check-parse-to-chars + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_parse_to_chars @core/instruction-table) start-state) + ;; Since split will return the empty string when given the empty string + string-length (if (= 0 (count value)) 1 (count value)) + expected-result (string/split value #"")] + (and + (= expected-result + (state/peek-stack-many end-state :string string-length)) + (-> end-state + (state/pop-stack-many :string string-length) + (state/empty-stack? :string))))) + +(defspec parse-to-chars-spec 100 + (prop/for-all [str gen/string] + (check-parse-to-chars str))) + + +;; string/remove-char + +(defn check-remove-char + [value char] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char)) + end-state ((:string_remove_char @core/instruction-table) start-state) + expected-result (apply str (filter #(not= char %) value))] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec remove-char-spec 100 + (prop/for-all [str gen/string + char gen/char] + (check-remove-char str char))) + + +;; string/replace + +(defn check-replace + [value1 value2 value3] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value1) + (state/push-to-stack :string value2) + (state/push-to-stack :string value3)) + end-state ((:string_replace @core/instruction-table) start-state) + expected-result (string/replace value1 value2 value3)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec replace-spec 100 + (prop/for-all [str1 gen/string + str2 gen/string + str3 gen/string] + (check-replace str1 str2 str3))) + + +;; string/replace-char + +(defn check-replace-char + [value char1 char2] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char1) + (state/push-to-stack :char char2)) + end-state ((:string_replace_char @core/instruction-table) start-state) + expected-result (string/replace value char1 char2)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec replace-char-spec 100 + (prop/for-all [str gen/string + char1 gen/char + char2 gen/char] + (check-replace-char str char1 char2))) + + +;; string/replace-first + +(defn check-replace-first + [value1 value2 value3] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value1) + (state/push-to-stack :string value2) + (state/push-to-stack :string value3)) + end-state ((:string_replace_first @core/instruction-table) start-state) + expected-result (string/replace-first value1 value2 value3)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec replace-first-spec 100 + (prop/for-all [str1 gen/string + str2 gen/string + str3 gen/string] + (check-replace-first str1 str2 str3))) + + +;; string/replace-first-char + +(defn check-replace-first-char + [value char1 char2] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char1) + (state/push-to-stack :char char2)) + end-state ((:string_replace_first_char @core/instruction-table) start-state) + expected-result (string/replace-first value char1 char2)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec replace-first-char-spec 100 + (prop/for-all [str gen/string + char1 gen/char + char2 gen/char] + (check-replace-first-char str char1 char2))) + + +;; string/rest + +(defn check-rest + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_rest @core/instruction-table) start-state) + expected-result (apply str (rest value))] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec rest-spec 100 + (prop/for-all [str gen/string] + (check-rest str))) + + +;; string/reverse + +(defn check-reverse + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_reverse @core/instruction-table) start-state) + expected-result (apply str (reverse value))] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec reverse-spec 100 + (prop/for-all [str gen/string] + (check-reverse str))) + + +;; string/set-char + +(defn check-set-char + [value char n] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :char char) + (state/push-to-stack :integer n)) + end-state ((:string_set_char @core/instruction-table) start-state)] + (or + (and + (empty? value) + (= (state/peek-stack end-state :string) value) + (= (state/peek-stack end-state :char) char) + (= (state/peek-stack end-state :integer) n)) + (= + (let [index (mod n (count value)) + start (subs value 0 index) + end (subs value (+ index 1))] + (str start char end)) + (state/peek-stack end-state :string))))) + +(defspec set-char-spec 100 + (prop/for-all [str gen/string + char gen/char + int gen/small-integer] + (check-set-char str char int))) + + +;; string/split + +(defn check-split + [value] + (let [start-state (state/push-to-stack state/empty-state :string value) + end-state ((:string_split @core/instruction-table) start-state) + our-split (string/split (string/trim value) #"\s+") + num-items (count our-split)] + (and + (= (state/stack-size end-state :string) num-items) + (every? identity + (map = + our-split + (state/peek-stack-many end-state :string num-items)))))) + +(defspec split-spec 100 + (prop/for-all [str gen/string] + (check-split str))) + + +;; string/substr + +(defn check-substr + [instruction value start end] + (let [start-state (-> state/empty-state + (state/push-to-stack :string value) + (state/push-to-stack :integer start) + (state/push-to-stack :integer end)) + end-state ((instruction @core/instruction-table) start-state) + str-len (count value) + small (min str-len (max 0 start)) + big (min str-len (max 0 small end)) + expected-result (subs value small big)] + (= expected-result + (state/peek-stack end-state :string)))) + +(defspec substr-spec 100 + (prop/for-all + [tuple (gen/let [str gen/string + int1 (gen/large-integer* {:min -5 :max (+ 5 (count str))}) + int2 (gen/large-integer* {:min -5 :max (+ 5 (count str))})] + [:string_substr, str, int1, int2])] + (apply check-substr tuple))) + +(defspec take-spec 100 + (prop/for-all + [tuple (gen/let [str gen/string + int (gen/large-integer* {:min -5 :max (+ 5 (count str))})] + [:string_take, str, 0, int])] + (apply check-substr tuple))) \ No newline at end of file