├── .gitignore
├── README.md
├── clopad.png
├── pom.xml
├── samples
└── defpure.clj
└── src
├── main
└── java
│ ├── Advent.java
│ ├── ArrayCharSequence.java
│ ├── Clojure.java
│ ├── ClojureIndenter.java
│ ├── Console.java
│ ├── DocumentAdapter.java
│ ├── Flexer.java
│ ├── FreditorUI_symbol.java
│ ├── FreditorWriter.java
│ ├── ISeqSpliterator.java
│ ├── Java.java
│ ├── Main.java
│ ├── MainFrame.java
│ ├── NamespaceExplorer.java
│ ├── PrintFormToWriter.java
│ └── SpecialForm.java
└── test
└── java
└── ClojureIndenterTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | /.idea/
3 | /*.iml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## Background
4 |
5 | Do you struggle with setting up complicated Clojure development environments just to define your first function and evaluate some expressions?
6 | Welcome to Clopad, a minimalistic Clojure code editor that will support you through your first steps!
7 |
8 | ## How do I compile clopad into an executable jar?
9 | ```
10 | git clone https://github.com/fredoverflow/freditor
11 | cd freditor
12 | mvn install
13 | cd ..
14 | git clone https://github.com/fredoverflow/clopad
15 | cd clopad
16 | mvn package
17 | ```
18 | The executable `clopad-x.y.z-SNAPSHOT-jar-with-dependencies.jar` will be located inside the `target` folder.
19 |
--------------------------------------------------------------------------------
/clopad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredoverflow/clopad/83152b12a59af4ea2c3130df529091ab5afc9cbf/clopad.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | fredoverflow
6 | clopad
7 | 0.1.0-SNAPSHOT
8 |
9 |
10 | UTF-8
11 | 11
12 | 11
13 | 11
14 | Main
15 |
16 |
17 |
18 |
19 | fredoverflow
20 | freditor
21 | 0.1.0-SNAPSHOT
22 |
23 |
24 |
25 | org.clojure
26 | clojure
27 | 1.11.1
28 |
29 |
30 |
31 | junit
32 | junit
33 | 4.13.1
34 | test
35 |
36 |
37 |
38 |
39 |
40 |
41 | org.apache.maven.plugins
42 | maven-assembly-plugin
43 | 3.6.0
44 |
45 |
46 | make-assembly
47 | package
48 |
49 | single
50 |
51 |
52 |
53 |
54 | ${main.class}
55 |
56 |
57 |
58 | jar-with-dependencies
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/samples/defpure.clj:
--------------------------------------------------------------------------------
1 | (ns user
2 | (:require [clojure.string :as string]
3 | [clojure.test :refer [do-report run-tests]]))
4 |
5 |
6 |
7 | (defn- filter-stack-trace! [^Throwable throwable]
8 | (->> (for [^StackTraceElement element (. throwable getStackTrace)
9 | :when (. *source-path* equals (. element getFileName))]
10 | element)
11 | (into-array StackTraceElement)
12 | (. throwable setStackTrace))
13 | throwable)
14 |
15 | (defn- register-test [v, inputs->output-map, do-report]
16 | (alter-meta! v assoc :test
17 | #(doseq [[inputs output] inputs->output-map]
18 | (try
19 | (let [actual (apply @v inputs)]
20 | (do-report {:type (if (= output actual) :pass :fail)
21 | :message (str " inputs: " inputs)
22 | :expected output
23 | :actual actual}))
24 | (catch Throwable throwable
25 | (do-report {:type :error
26 | :message (str " inputs: " inputs)
27 | :expected output
28 | :actual (filter-stack-trace! throwable)}))))))
29 |
30 | (defmacro defpure
31 | "Defines a pure function, illustrated by an exemplary inputs->output map"
32 | [name, inputs->output-map & rest]
33 | `(do
34 | (defn ~name ~@rest)
35 | (register-test (var ~name) ~inputs->output-map #(do-report %))))
36 |
37 |
38 |
39 | (defpure square
40 | {[0] 0
41 | [2] 4
42 | [3] 9}
43 | "squares its input"
44 | [^Number x]
45 | (/ x x))
46 |
47 |
48 |
49 | (run-tests)
50 |
--------------------------------------------------------------------------------
/src/main/java/Advent.java:
--------------------------------------------------------------------------------
1 | import java.io.IOException;
2 | import java.net.HttpURLConnection;
3 | import java.net.URI;
4 | import java.net.http.HttpClient;
5 | import java.net.http.HttpRequest;
6 | import java.net.http.HttpResponse;
7 | import java.nio.file.Files;
8 | import java.nio.file.NoSuchFileException;
9 | import java.nio.file.Paths;
10 | import java.time.*;
11 | import java.time.temporal.ChronoUnit;
12 |
13 | public class Advent {
14 | public static final String CACHE_FOLDER = System.getProperty("user.home") + "/Downloads";
15 | public static final String CACHE_FORMAT = "%d_%02d.txt";
16 | public static final String REMOTE_FORMAT = "https://adventofcode.com/%d/day/%d/input";
17 | public static final String SESSION_COOKIE = "advent.txt";
18 | public static final ZoneId RELEASE_ZONE = ZoneId.of("US/Eastern");
19 |
20 | public static String get(int year, int day) throws IOException, InterruptedException {
21 | valiDate(year, day);
22 | String content;
23 |
24 | var cache = Paths.get(CACHE_FOLDER, String.format(CACHE_FORMAT, year, day));
25 | try {
26 | content = Files.readString(cache);
27 | } catch (NoSuchFileException notCachedYet) {
28 | content = downloadContent(year, day);
29 | Files.writeString(cache, content);
30 | }
31 | return content;
32 | }
33 |
34 | private static String downloadContent(int year, int day) throws IOException, InterruptedException {
35 | var client = HttpClient.newBuilder().build();
36 |
37 | var session = Files.readString(Paths.get(CACHE_FOLDER, SESSION_COOKIE)).trim();
38 |
39 | var request = HttpRequest.newBuilder()
40 | .uri(URI.create(String.format(REMOTE_FORMAT, year, day)))
41 | .header("Cookie", "session=" + session)
42 | .GET()
43 | .build();
44 |
45 | var response = client.send(request, HttpResponse.BodyHandlers.ofString());
46 |
47 | if (response.statusCode() == HttpURLConnection.HTTP_OK) {
48 | return response.body();
49 | } else {
50 | throw new IOException("HTTP status code " + response.statusCode() + ", session cookie length " + session.length());
51 | }
52 | }
53 |
54 | private static void valiDate(int year, int day) {
55 | if (!between(1, day, 25)) throw new IllegalArgumentException("illegal day " + day);
56 | var now = ZonedDateTime.now(RELEASE_ZONE);
57 | if (!between(2015, year, now.getYear())) throw new IllegalArgumentException("illegal year " + year);
58 |
59 | var desired = ZonedDateTime.of(LocalDate.of(year, Month.DECEMBER, day), LocalTime.MIDNIGHT, RELEASE_ZONE);
60 | var seconds = ChronoUnit.SECONDS.between(now, desired);
61 | if (seconds > 0) {
62 | var hours = ChronoUnit.HOURS.between(now, desired);
63 | if (hours > 0) {
64 | throw new IllegalArgumentException(hours + " hours until release...");
65 | }
66 | throw new IllegalArgumentException(seconds + " seconds until release...");
67 | }
68 | }
69 |
70 | private static boolean between(int min, int value, int max) {
71 | return min <= value && value <= max;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/ArrayCharSequence.java:
--------------------------------------------------------------------------------
1 | public class ArrayCharSequence implements CharSequence {
2 | private final char[] array;
3 | private final int offset;
4 | private final int length;
5 |
6 | public ArrayCharSequence(char[] array, int offset, int length) {
7 | this.array = array;
8 | this.offset = offset;
9 | this.length = length;
10 | }
11 |
12 | @Override
13 | public int length() {
14 | return length;
15 | }
16 |
17 | @Override
18 | public char charAt(int index) {
19 | return array[offset + index];
20 | }
21 |
22 | @Override
23 | public CharSequence subSequence(int start, int end) {
24 | return new ArrayCharSequence(array, offset + start, end - start);
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return new String(array, offset, length);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/Clojure.java:
--------------------------------------------------------------------------------
1 | import clojure.lang.Compiler;
2 | import clojure.lang.*;
3 |
4 | import java.io.IOException;
5 | import java.io.PushbackReader;
6 | import java.io.StringReader;
7 | import java.nio.file.Path;
8 | import java.util.function.Consumer;
9 |
10 | import static clojure.lang.Compiler.*;
11 |
12 | public class Clojure {
13 | public static final Keyword doc;
14 | public static final Symbol null_ns;
15 | public static final Symbol clojure_core_ns;
16 | public static final Var printLength;
17 | public static final Var warnOnReflection;
18 |
19 | public static final IFn macroexpand;
20 | public static final IFn macroexpandAll;
21 | public static final IFn nsInterns;
22 | public static final IFn pprint;
23 | public static final IFn sourceFn;
24 |
25 | static {
26 | IFn require = Var.find(Symbol.create("clojure.core", "require"));
27 | require.invoke(Symbol.create("clojure.pprint"));
28 | require.invoke(Symbol.create("clojure.repl"));
29 | require.invoke(Symbol.create("clojure.walk"));
30 |
31 | doc = Keyword.intern("doc");
32 | null_ns = Symbol.create(null, "ns");
33 | clojure_core_ns = Symbol.create("clojure.core", "ns");
34 | printLength = Var.find(Symbol.create("clojure.core", "*print-length*"));
35 | warnOnReflection = Var.find(Symbol.create("clojure.core", "*warn-on-reflection*"));
36 |
37 | macroexpand = Var.find(Symbol.create("clojure.core", "macroexpand"));
38 | macroexpandAll = Var.find(Symbol.create("clojure.walk", "macroexpand-all"));
39 | nsInterns = Var.find(Symbol.create("clojure.core", "ns-interns"));
40 | pprint = Var.find(Symbol.create("clojure.pprint", "pprint"));
41 | sourceFn = Var.find(Symbol.create("clojure.repl", "source-fn"));
42 | }
43 |
44 | public static String firstForm(String text) {
45 | LineNumberingPushbackReader reader = new LineNumberingPushbackReader(new StringReader(text));
46 | reader.captureString();
47 | LispReader.read(reader, false, null, false, null);
48 | return reader.getString();
49 | }
50 |
51 | public static void loadFromScratch(String text, Path file,
52 | Consumer