aboutsummaryrefslogtreecommitdiff
path: root/src/lux/reader.clj
blob: f0509ec194528eb15592da82ae2c8b8c6441ac44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
;;  Copyright (c) Eduardo Julian. All rights reserved.
;;  This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
;;  If a copy of the MPL was not distributed with this file,
;;  You can obtain one at http://mozilla.org/MPL/2.0/.

(ns lux.reader
  (:require [clojure.string :as string]
            clojure.core.match
            clojure.core.match.array
            [lux.base :as & :refer [defvariant |do return* return fail fail* |let |case]]))

;; [Tags]
(defvariant
  ("No" 1)
  ("Done" 1)
  ("Yes" 2))

;; [Utils]
(defn ^:private with-line [body]
  (fn [state]
    (|case (&/get$ &/$source state)
      (&/$Nil)
      (fail* "[Reader Error] EOF")

      (&/$Cons [[file-name line-num column-num] line]
               more)
      (|case (body file-name line-num column-num line)
        ($No msg)
        (fail* msg)

        ($Done output)
        (return* (&/set$ &/$source more state)
                 output)

        ($Yes output line*)
        (return* (&/set$ &/$source (&/$Cons line* more) state)
                 output))
      )))

(defn ^:private with-lines [body]
  (fn [state]
    (|case (body (&/get$ &/$source state))
      (&/$Right reader* match)
      (return* (&/set$ &/$source reader* state)
               match)

      (&/$Left msg)
      (fail* msg)
      )))

(defn ^:private re-find! [^java.util.regex.Pattern regex column ^String line]
  (let [matcher (doto (.matcher regex line)
                  (.region column (.length line))
                  (.useAnchoringBounds true))]
    (when (.find matcher)
      (.group matcher 0))))

;; [Exports]
(defn read-regex [regex]
  (with-line
    (fn [file-name line-num column-num ^String line]
      (if-let [^String match (re-find! regex column-num line)]
        (let [match-length (.length match)
              column-num* (+ column-num match-length)]
          (if (= column-num* (.length line))
            ($Done (&/T [(&/T [file-name line-num column-num]) true match]))
            ($Yes (&/T [(&/T [file-name line-num column-num]) false match])
                  (&/T [(&/T [file-name line-num column-num*]) line]))))
        ($No (str "[Reader Error] Pattern failed: " regex))))))

(defn read-regex+ [regex]
  (with-lines
    (fn [reader]
      (loop [prefix ""
             reader* reader]
        (|case reader*
          (&/$Nil)
          (&/$Left "[Reader Error] EOF")

          (&/$Cons [[file-name line-num column-num] ^String line]
                   reader**)
          (if-let [^String match (re-find! regex column-num line)]
            (let [match-length (.length match)
                  column-num* (+ column-num match-length)
                  prefix* (if (= 0 column-num)
                            (str prefix "\n" match)
                            (str prefix match))]
              (if (= column-num* (.length line))
                (recur prefix* reader**)
                (&/$Right (&/T [(&/$Cons (&/T [(&/T [file-name line-num column-num*]) line])
                                         reader**)
                                (&/T [(&/T [file-name line-num column-num]) prefix*])]))))
            (&/$Left (str "[Reader Error] Pattern failed: " regex))))))))

(defn read-text [^String text]
  (with-line
    (fn [file-name line-num column-num ^String line]
      (if (.startsWith line text column-num)
        (let [match-length (.length text)
              column-num* (+ column-num match-length)]
          (if (= column-num* (.length line))
            ($Done (&/T [(&/T [file-name line-num column-num]) true text]))
            ($Yes (&/T [(&/T [file-name line-num column-num]) false text])
                  (&/T [(&/T [file-name line-num column-num*]) line]))))
        ($No (str "[Reader Error] Text failed: " text))))))

(defn from [^String name ^String source-code]
  (let [lines (string/split-lines source-code)
        indexed-lines (map (fn [line line-num]
                             (&/T [(&/T [name (inc line-num) 0])
                                   line]))
                           lines
                           (range (count lines)))]
    (reduce (fn [tail head] (&/$Cons head tail))
            &/$Nil
            (reverse indexed-lines))))