Skip to content

Commit

Permalink
Add Clojure (#357)
Browse files Browse the repository at this point in the history
* Add Clojure

* New emitter

* Fix formatting

* Improvements, bugfixes

* Add bit count op

* Starts/ends with, tests
  • Loading branch information
Steffan153 authored Mar 8, 2024
1 parent c849d1f commit 833a8b3
Show file tree
Hide file tree
Showing 6 changed files with 534 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
src/frontend/grammar.ts
*.test.md.ts
.DS_Store
191 changes: 191 additions & 0 deletions src/languages/clojure/clojure.test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Clojure

## Printing

```polygolf
$a <- 1;
$b <- "x";
println $a;
print $a;
println $b;
print $b;
```

```clj nogolf
(def a 1)(def b"x")(prn a)(pr a)(println b)(print b)
```

## Ops emit

```polygolf
$a:-100..100 <- 0;
$b:Text <- "xy";
$c <- (0==0);
$d <- (list "q" "r" "s");
% Boolean
and $c $c;
or $c $c;
not $c;
% Unary Arithmetic
~ $a;
- $a;
abs $a;
% Binary Arithmetic
$a + 2;
$a - 2;
$a * 2;
$a div 2;
$a ^ 2;
$a rem 2;
$a mod 2;
$a & 2;
$a | 2;
$a ~ 2;
max $a 2;
min $a 2;
$a << 2;
$a >> 2;
bit_count 123;
% Comparison
$a < 2;
$a <= 2;
$a == 2;
$a != 2;
$a >= 2;
$a > 2;
% Parity
is_even $a;
is_odd $a;
% Text Encoding
at[codepoint] "abc" 1;
ord[codepoint] "g";
ord_at[codepoint] "ijk" 1;
char[codepoint] 99;
slice[codepoint] "abcdefg" 2 3;
slice[codepoint] "abcdefg" 0 3;
"a" == "b";
"a" != "b";
% Other
at[Table] (table (1 => 2)) 1;
at[List] $d 1;
at_back[List] $d -2;
at_back[List] $d -1;
size[List] $d;
join $d "_";
sorted $d;
concat[Text] $b "xyz";
int_to_dec 5;
int_to_hex 7;
dec_to_int "5";
split "xyz" "y";
reversed[codepoint] $b;
reversed[List] $d;
repeat $b 3;
right_align "he" 7;
int_to_hex_aligned 31 7;
replace "abcbd" "b" "e";
starts_with "abcd" "ab";
ends_with "abcd" "cd";
```

```clj nogolf
(def a 0)(def b"xy")(def c(= 0 0))(def d["q""r""s"])(and c c)(or c c)(not c)(bit-not a)(- a)(abs a)(+ 2 a)(- a 2)(* 2 a)(quot a 2)(int(Math/pow a 2))(rem a 2)(mod a 2)(bit-and 2 a)(bit-or 2 a)(bit-xor 2 a)(max 2 a)(min 2 a)(bit-shift-left a 2)(bit-shift-right a 2)(Long/bitCount 123)(< a 2)(<= a 2)(= a 2)(not= a 2)(>= a 2)(> a 2)(even? a)(odd? a)(str(nth"abc"1))(int(nth"g"0))(int(nth"ijk"1))(str(char 99))(subs"abcdefg"2 5)(subs"abcdefg"0 3)(="a""b")(not="a""b")({1 2}1)(nth d 1)(nth d(-(count d)2))(last d)(count d)(clojure.string/join"_"d)(sort d)(str b"xyz")(str 5)(format"%x"7)(read-string"5")(.split"xyz""y")(clojure.string/reverse b)(reverse d)(apply str(repeat 3 b))(format"%7s""he")(format"%07x"31)(clojure.string/replace"abcbd""b""e")(clojure.string/starts-with?"abcd""ab")(clojure.string/ends-with?"abcd""cd")
```

## Looping

```polygolf
for $i 0 31 {
println ((1 + $i) + ($i * $i));
};
```

```clj nogolf
(dotimes[i 31](prn(+ 1 i(* i i))))
```

```polygolf
$a:-10..10 <- -4;
for $i $a ($a+6) {
println $i;
};
```

```clj nogolf
(def a -4)(doseq[i(range a(+ 6 a))](prn i))
```

```polygolf
for 5 {
print "x";
};
```

```clj nogolf
(dotimes[_ 5](print"x"))
```

```polygolf
while (> 1 0) {
println 5;
$x <- 1;
};
```

```clj nogolf
(while(> 1 0)(prn 5)(def x 1))
```

## Argv

```polygolf
println (at[argv] 5);
```

```clj nogolf
(println(nth *command-line-args* 5))
```

```polygolf
for_argv $x 100 {
println $x;
};
```

```clj nogolf
(doseq[x *command-line-args*](println x))
```

## Block in if statement

```polygolf
if (> 1 0) {
$x <- 1;
print $x;
} {
$y <- 2;
println $y;
};
```

```clj nogolf
(if(> 1 0)(do(def x 1)(pr x))(do(def y 2)(prn y)))
```

## implicitlyConvertConcatArg

```polygolf
(.. "he" (int_to_dec 11) "o");
```

```clj implicitlyConvertConcatArg
(str"he"11"o")
```
124 changes: 124 additions & 0 deletions src/languages/clojure/emit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
EmitError,
emitIntLiteral,
emitTextFactory,
getIfChain,
} from "../../common/emit";
import {
isInt,
isForEachRange,
type Node,
type If,
type ConditionalOp,
} from "../../IR";
import {
defaultDetokenizer,
VisitorEmitter,
type EmitterVisitResult,
} from "../../common/Language";
import { type Spine } from "../../common/Spine";
import type { CompilationContext } from "../../common/compile";
import { $ } from "../../common/fragments";

const emitClojureText = emitTextFactory({
'"TEXT"': { "\\": `\\\\`, "\r": `\\r`, '"': `\\"` },
});

export class ClojureEmitter extends VisitorEmitter {
detokenize = defaultDetokenizer(
(a, b) =>
a !== "" &&
b !== "" &&
/[^(){}[\]"]/.test(a[a.length - 1]) &&
/[^(){}[\]"]/.test(b[0]),
);

visit(n: Node, spine: Spine<Node>, context: CompilationContext) {
function list(...args: EmitterVisitResult[]) {
return ["(", ...args, ")"];
}

if (n === undefined) return "_";
const prop = spine.pathFragment?.prop;

switch (n.kind) {
case "Block": {
return prop === "consequent" || prop === "alternate"
? list("do", $.children.join())
: $.children.join();
}
case "While":
return list("while", $.condition, $.body);
case "ForEach":
if (isForEachRange(n)) {
const [low, high, step] = n.collection.args.map((x, i) =>
spine.getChild($.collection, $.args.at(i)),
);
const stepIsOne = isInt(1n)(n.collection.args[2]);
return stepIsOne && isInt(0n)(n.collection.args[0])
? list("dotimes", "[", $.variable, high, "]", $.body)
: list(
"doseq",
"[",
$.variable,
list("range", low, high, stepIsOne ? [] : step),
"]",
$.body,
);
}
return list(
"doseq",
"[",
n.variable === undefined ? "_" : $.variable,
$.collection,
"]",
$.body,
);
case "If": {
const { ifs, alternate } = getIfChain(spine as Spine<If>);
return list(
ifs.length > 1 ? "cond" : "if",
ifs.map((x) => [x.condition, x.consequent]),
ifs.length > 1 ? '""' : [],
alternate ?? [],
);
}
case "Assignment":
return list("def", $.variable, $.expr);
case "Identifier":
return n.name;
case "Text":
return emitClojureText(n.value);
case "Integer":
return emitIntLiteral(n, {
10: ["", ""],
16: ["0x", ""],
36: ["36r", ""],
});
case "FunctionCall":
return list($.func, $.args.join());
case "RangeIndexCall":
if (!isInt(1n)(n.step)) throw new EmitError(n, "step not equal one");
return isInt(0n)(n.low)
? list("take", $.high, $.collection)
: list("subvec", list("vec", $.collection), $.low, $.high);
case "ConditionalOp": {
const { ifs, alternate } = getIfChain(spine as Spine<ConditionalOp>);
return list(
ifs.length > 1 ? "cond" : "if",
ifs.map((x) => [x.condition, x.consequent]),
ifs.length > 1 ? '""' : [],
alternate!,
);
}
case "List":
return ["[", $.value.join(), "]"];
case "Table":
return ["{", $.value.join(), "}"];
case "KeyValue":
return [$.key, $.value];
default:
throw new EmitError(n);
}
}
}
Loading

0 comments on commit 833a8b3

Please sign in to comment.