summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--java/.gitignore2
-rw-r--r--java/Abs.java26
-rw-r--r--java/App.java22
-rw-r--r--java/LCEvaluator.java59
-rw-r--r--java/LCEvaluatorTest.java74
-rw-r--r--java/Main.java49
-rw-r--r--java/Term.java5
-rw-r--r--java/TermTest.java38
-rw-r--r--java/Var.java21
-rw-r--r--java/lcgoji/.gitignore2
-rw-r--r--java/lcgoji/pom.xml65
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/LCEvaluator.java59
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/Main.java49
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Abs.java26
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/App.java22
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Term.java5
-rw-r--r--java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Var.java21
-rw-r--r--java/lcgoji/src/main/resources/log4j2.xml17
-rw-r--r--java/lcgoji/src/test/java/org/lambdanantes/lcgoji/LCEvaluatorTest.java74
-rw-r--r--java/lcgoji/src/test/java/org/lambdanantes/lcgoji/ast/TermTest.java38
-rw-r--r--java/log4j2.xml17
-rw-r--r--java/pom.xml65
23 files changed, 757 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 51e3dbd..cf0012f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*~
*.profraw
+.idea
diff --git a/java/.gitignore b/java/.gitignore
new file mode 100644
index 0000000..af515ad
--- /dev/null
+++ b/java/.gitignore
@@ -0,0 +1,2 @@
+.idea
+target/
diff --git a/java/Abs.java b/java/Abs.java
new file mode 100644
index 0000000..9d1c146
--- /dev/null
+++ b/java/Abs.java
@@ -0,0 +1,26 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class Abs implements Term {
+
+ public static final Abs IDENTITY = λ("x", var("x"));
+
+ public String arg;
+ public Term body;
+
+ public static Abs λ(String arg, Term body) {
+ return new Abs(arg, body);
+ }
+
+ @Override
+ public String toString() {
+ return "λ" + arg + "." + body;
+ }
+} \ No newline at end of file
diff --git a/java/App.java b/java/App.java
new file mode 100644
index 0000000..2f3e04d
--- /dev/null
+++ b/java/App.java
@@ -0,0 +1,22 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class App implements Term {
+
+ public Term left;
+ public Term right;
+
+ public static App apply(Term left, Term right) {
+ return new App(left, right);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + left + ") " + right;
+ }
+} \ No newline at end of file
diff --git a/java/LCEvaluator.java b/java/LCEvaluator.java
new file mode 100644
index 0000000..b0fe69b
--- /dev/null
+++ b/java/LCEvaluator.java
@@ -0,0 +1,59 @@
+package org.lambdanantes.lcgoji;
+
+import org.lambdanantes.lcgoji.ast.Abs;
+import org.lambdanantes.lcgoji.ast.App;
+import org.lambdanantes.lcgoji.ast.Term;
+import org.lambdanantes.lcgoji.ast.Var;
+
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+
+public class LCEvaluator {
+
+ public static Term evaluate(Term term) {
+ switch (term) {
+ case Var var -> {
+ return var;
+ }
+ case Abs abs -> {
+ return abs;
+ }
+ case App app -> {
+ switch (app.left) {
+ case Abs abs -> {
+ return substitute(abs.body, abs.arg, app.right);
+ }
+ case App _app -> {
+ return app;
+ }
+ case Var _var -> {
+ return app;
+ }
+ }
+ }
+ }
+ }
+
+ private static Term substitute(Term body, String arg, Term val) {
+ switch (body) {
+ case Var var -> {
+ if (var.name.equals(arg)) {
+ return val;
+ } else {
+ return body;
+ }
+ }
+ case App app -> {
+ return apply(substitute(app.left, arg, val), substitute(app.right, arg, val));
+ }
+ case Abs abs -> {
+ if (abs.arg.equals(arg)) {
+ // Pas de substitution des variables redéfinies
+ return abs;
+ } else {
+ return λ(abs.arg, substitute(abs.body, arg, val));
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/java/LCEvaluatorTest.java b/java/LCEvaluatorTest.java
new file mode 100644
index 0000000..ec2f027
--- /dev/null
+++ b/java/LCEvaluatorTest.java
@@ -0,0 +1,74 @@
+package org.lambdanantes.lcgoji;
+
+import org.junit.Test;
+import org.lambdanantes.lcgoji.ast.Term;
+import org.lambdanantes.lcgoji.ast.Var;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.lambdanantes.lcgoji.LCEvaluator.evaluate;
+import static org.lambdanantes.lcgoji.ast.Abs.IDENTITY;
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+public class LCEvaluatorTest {
+
+ Var x = var("x");
+ Var y = var("y");
+ Var z = var("z");
+
+ @Test
+ public void l_evaluation_d_une_variable_rend_la_meme_variable() {
+ // evaluate(x) == x
+ assertEvalTo(x, x);
+ }
+
+ @Test
+ public void l_evaluation_d_une_abstraction_rend_la_meme_abstraction() {
+ // evaluate(λx.x) == λx.x
+ assertEvalTo(IDENTITY, IDENTITY);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_de_deux_vars_rend_la_meme_application() {
+ // evaluate(x y) == x y
+ assertEvalTo(apply(x, y), apply(x, y));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_de_l_identite_sur_un_argument_rend_l_argument() {
+ // evaluate((λx.x) y) == y
+ assertEvalTo(apply(IDENTITY, y), y);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_substitue_son_argument() {
+ // evaluate((λx.x x) y ) == y y
+ assertEvalTo(apply(λ("x", apply(x, x)), y), apply(y, y));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_d_abstraction_substitue_recursivement_son_argument() {
+ // evaluate((λx.λy.x) z) == λy.z
+ assertEvalTo(apply(λ("x", λ("y", x)), z), λ("y", z));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_ne_substitue_pas_ses_variables_libres() {
+ // evaluate((λx.y) z) == y
+ assertEvalTo(apply(λ("x", y), z), y);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_d_abstraction_ne_substitue_pas_les_variables_redefinies() {
+ // evaluate((λx.λx.x) z) == λx.x
+ assertEvalTo(apply(λ("x", λ("x", x)), z), λ("x", x));
+ }
+
+ /////
+
+ private static void assertEvalTo(Term termToEvaluate, Term result) {
+ assertThat(evaluate(termToEvaluate), is(result));
+ }
+}
diff --git a/java/Main.java b/java/Main.java
new file mode 100644
index 0000000..76cb839
--- /dev/null
+++ b/java/Main.java
@@ -0,0 +1,49 @@
+package org.lambdanantes.lcgoji;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.fluent.Response;
+import org.apache.http.entity.StringEntity;
+
+import static spark.Spark.*;
+
+@Slf4j
+public class Main {
+
+ public static final String TEAM_NAME = "LCGOJI";
+ public static final int SELF_PORT = 8888;
+ public static final String SELF_URL = "http://127.0.0.1:" + SELF_PORT;
+ public static final String TESTER_URL = "http://127.0.0.1:8080";
+
+ public static void main(String[] args) throws Exception {
+ port(SELF_PORT);
+
+ before((request, response) -> log.info("Requête entrante : " + request.requestMethod() + " " + request.pathInfo() + ", query params : " + request.queryString()));
+
+ // API pour l'évaluation de λ-term
+ // Le body est une S-expression sous sa forme textuelle
+ post("/eval", (request, response) -> {
+ String body = request.body();
+ log.info("Demande d'évaluation de l'expression : " + body);
+
+ // TODO Parser, contruire l'AST, l'évaluer
+ String result = body; // Renvoie la s-expression à l'identique pour le moment
+
+ log.info("Réponse envoyée : " + body);
+
+ return result.getBytes();
+ });
+
+ init();
+
+ // Enregistrement de notre API auprès du tester d'API
+ String jsonBody = "{\"url\":\"" + SELF_URL + "/eval\", \"name\": \"" + TEAM_NAME + "\"}";
+ Response response = Request.Post(TESTER_URL + "/register")
+ .addHeader("Content-type", "application/json")
+ .body(new StringEntity(jsonBody))
+ .execute();
+
+ log.info("Résultat de l'enregistrement : "+response.returnContent().toString());
+ }
+
+} \ No newline at end of file
diff --git a/java/Term.java b/java/Term.java
new file mode 100644
index 0000000..2dcc9fe
--- /dev/null
+++ b/java/Term.java
@@ -0,0 +1,5 @@
+package org.lambdanantes.lcgoji.ast;
+
+public sealed interface Term permits Var, Abs, App {
+
+} \ No newline at end of file
diff --git a/java/TermTest.java b/java/TermTest.java
new file mode 100644
index 0000000..f11ca77
--- /dev/null
+++ b/java/TermTest.java
@@ -0,0 +1,38 @@
+package org.lambdanantes.lcgoji.ast;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+public class TermTest {
+
+ @Test
+ public void les_equals_des_termes_sont_corrects() {
+ // x == x
+ assertThat(var("x"), is(var("x")));
+ // x != y
+ assertThat(var("x"), is(not(var("y"))));
+ // λx.x == λx.x
+ assertThat(λ("x", var("x")), is(λ("x", var("x"))));
+ // λx.x != λy.x
+ assertThat(λ("x", var("x")), is(not(λ("y", var("x")))));
+ // x y == x y
+ assertThat(apply(var("x"), var("y")), is(apply(var("x"), var("y"))));
+ // x x != x y
+ assertThat(apply(var("x"), var("x")), is(not(apply(var("x"), var("y")))));
+ }
+
+ @Test
+ public void les_toString_des_termes_utilisent_la_notation_consacree() {
+ assertThat(var("x").toString(), is("x"));
+ assertThat(λ("x", var("x")).toString(), is("λx.x"));
+ assertThat(apply(λ("x", var("x")), var("x")).toString(), is("(λx.x) x"));
+ assertThat(apply(λ("x", λ("x", var("x"))), var("y")).toString(), is("(λx.λx.x) y"));
+ }
+
+} \ No newline at end of file
diff --git a/java/Var.java b/java/Var.java
new file mode 100644
index 0000000..ed710b6
--- /dev/null
+++ b/java/Var.java
@@ -0,0 +1,21 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class Var implements Term{
+
+ public String name;
+
+ public static Var var(String name) {
+ return new Var(name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+} \ No newline at end of file
diff --git a/java/lcgoji/.gitignore b/java/lcgoji/.gitignore
new file mode 100644
index 0000000..af515ad
--- /dev/null
+++ b/java/lcgoji/.gitignore
@@ -0,0 +1,2 @@
+.idea
+target/
diff --git a/java/lcgoji/pom.xml b/java/lcgoji/pom.xml
new file mode 100644
index 0000000..f4a4f30
--- /dev/null
+++ b/java/lcgoji/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.lambdanantes</groupId>
+ <artifactId>lcgoji</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.source>21</maven.compiler.source>
+ <maven.compiler.target>21</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <lombok.version>1.18.30</lombok.version>
+ <log4j.version>2.16.0</log4j.version>
+
+ <jetty-util.version>9.4.31.v20200723</jetty-util.version>
+ <spark-core.version>2.9.3</spark-core.version>
+ <httpclient.version>4.3.1</httpclient.version>
+
+ <junit.version>4.10</junit.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty-util.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sparkjava</groupId>
+ <artifactId>spark-core</artifactId>
+ <version>${spark-core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>fluent-hc</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ </dependencies>
+</project> \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/LCEvaluator.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/LCEvaluator.java
new file mode 100644
index 0000000..b0fe69b
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/LCEvaluator.java
@@ -0,0 +1,59 @@
+package org.lambdanantes.lcgoji;
+
+import org.lambdanantes.lcgoji.ast.Abs;
+import org.lambdanantes.lcgoji.ast.App;
+import org.lambdanantes.lcgoji.ast.Term;
+import org.lambdanantes.lcgoji.ast.Var;
+
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+
+public class LCEvaluator {
+
+ public static Term evaluate(Term term) {
+ switch (term) {
+ case Var var -> {
+ return var;
+ }
+ case Abs abs -> {
+ return abs;
+ }
+ case App app -> {
+ switch (app.left) {
+ case Abs abs -> {
+ return substitute(abs.body, abs.arg, app.right);
+ }
+ case App _app -> {
+ return app;
+ }
+ case Var _var -> {
+ return app;
+ }
+ }
+ }
+ }
+ }
+
+ private static Term substitute(Term body, String arg, Term val) {
+ switch (body) {
+ case Var var -> {
+ if (var.name.equals(arg)) {
+ return val;
+ } else {
+ return body;
+ }
+ }
+ case App app -> {
+ return apply(substitute(app.left, arg, val), substitute(app.right, arg, val));
+ }
+ case Abs abs -> {
+ if (abs.arg.equals(arg)) {
+ // Pas de substitution des variables redéfinies
+ return abs;
+ } else {
+ return λ(abs.arg, substitute(abs.body, arg, val));
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/Main.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/Main.java
new file mode 100644
index 0000000..76cb839
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/Main.java
@@ -0,0 +1,49 @@
+package org.lambdanantes.lcgoji;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.fluent.Response;
+import org.apache.http.entity.StringEntity;
+
+import static spark.Spark.*;
+
+@Slf4j
+public class Main {
+
+ public static final String TEAM_NAME = "LCGOJI";
+ public static final int SELF_PORT = 8888;
+ public static final String SELF_URL = "http://127.0.0.1:" + SELF_PORT;
+ public static final String TESTER_URL = "http://127.0.0.1:8080";
+
+ public static void main(String[] args) throws Exception {
+ port(SELF_PORT);
+
+ before((request, response) -> log.info("Requête entrante : " + request.requestMethod() + " " + request.pathInfo() + ", query params : " + request.queryString()));
+
+ // API pour l'évaluation de λ-term
+ // Le body est une S-expression sous sa forme textuelle
+ post("/eval", (request, response) -> {
+ String body = request.body();
+ log.info("Demande d'évaluation de l'expression : " + body);
+
+ // TODO Parser, contruire l'AST, l'évaluer
+ String result = body; // Renvoie la s-expression à l'identique pour le moment
+
+ log.info("Réponse envoyée : " + body);
+
+ return result.getBytes();
+ });
+
+ init();
+
+ // Enregistrement de notre API auprès du tester d'API
+ String jsonBody = "{\"url\":\"" + SELF_URL + "/eval\", \"name\": \"" + TEAM_NAME + "\"}";
+ Response response = Request.Post(TESTER_URL + "/register")
+ .addHeader("Content-type", "application/json")
+ .body(new StringEntity(jsonBody))
+ .execute();
+
+ log.info("Résultat de l'enregistrement : "+response.returnContent().toString());
+ }
+
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Abs.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Abs.java
new file mode 100644
index 0000000..9d1c146
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Abs.java
@@ -0,0 +1,26 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class Abs implements Term {
+
+ public static final Abs IDENTITY = λ("x", var("x"));
+
+ public String arg;
+ public Term body;
+
+ public static Abs λ(String arg, Term body) {
+ return new Abs(arg, body);
+ }
+
+ @Override
+ public String toString() {
+ return "λ" + arg + "." + body;
+ }
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/App.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/App.java
new file mode 100644
index 0000000..2f3e04d
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/App.java
@@ -0,0 +1,22 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class App implements Term {
+
+ public Term left;
+ public Term right;
+
+ public static App apply(Term left, Term right) {
+ return new App(left, right);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + left + ") " + right;
+ }
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Term.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Term.java
new file mode 100644
index 0000000..2dcc9fe
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Term.java
@@ -0,0 +1,5 @@
+package org.lambdanantes.lcgoji.ast;
+
+public sealed interface Term permits Var, Abs, App {
+
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Var.java b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Var.java
new file mode 100644
index 0000000..ed710b6
--- /dev/null
+++ b/java/lcgoji/src/main/java/org/lambdanantes/lcgoji/ast/Var.java
@@ -0,0 +1,21 @@
+package org.lambdanantes.lcgoji.ast;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+
+@AllArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+public final class Var implements Term{
+
+ public String name;
+
+ public static Var var(String name) {
+ return new Var(name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+} \ No newline at end of file
diff --git a/java/lcgoji/src/main/resources/log4j2.xml b/java/lcgoji/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..f938085
--- /dev/null
+++ b/java/lcgoji/src/main/resources/log4j2.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration monitorInterval="60">
+ <appenders>
+ <console name="console" target="SYSTEM_OUT">
+ <patternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
+ </console>
+ </appenders>
+
+ <loggers>
+ <root level="debug">
+ <appender-ref ref="console"/>
+ </root>
+ <logger name="org.eclipse.jetty" level="warn"/>
+ <logger name="spark" level="info"/>
+ </loggers>
+</configuration>
+
diff --git a/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/LCEvaluatorTest.java b/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/LCEvaluatorTest.java
new file mode 100644
index 0000000..ec2f027
--- /dev/null
+++ b/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/LCEvaluatorTest.java
@@ -0,0 +1,74 @@
+package org.lambdanantes.lcgoji;
+
+import org.junit.Test;
+import org.lambdanantes.lcgoji.ast.Term;
+import org.lambdanantes.lcgoji.ast.Var;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.lambdanantes.lcgoji.LCEvaluator.evaluate;
+import static org.lambdanantes.lcgoji.ast.Abs.IDENTITY;
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+public class LCEvaluatorTest {
+
+ Var x = var("x");
+ Var y = var("y");
+ Var z = var("z");
+
+ @Test
+ public void l_evaluation_d_une_variable_rend_la_meme_variable() {
+ // evaluate(x) == x
+ assertEvalTo(x, x);
+ }
+
+ @Test
+ public void l_evaluation_d_une_abstraction_rend_la_meme_abstraction() {
+ // evaluate(λx.x) == λx.x
+ assertEvalTo(IDENTITY, IDENTITY);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_de_deux_vars_rend_la_meme_application() {
+ // evaluate(x y) == x y
+ assertEvalTo(apply(x, y), apply(x, y));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_de_l_identite_sur_un_argument_rend_l_argument() {
+ // evaluate((λx.x) y) == y
+ assertEvalTo(apply(IDENTITY, y), y);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_substitue_son_argument() {
+ // evaluate((λx.x x) y ) == y y
+ assertEvalTo(apply(λ("x", apply(x, x)), y), apply(y, y));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_d_abstraction_substitue_recursivement_son_argument() {
+ // evaluate((λx.λy.x) z) == λy.z
+ assertEvalTo(apply(λ("x", λ("y", x)), z), λ("y", z));
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_ne_substitue_pas_ses_variables_libres() {
+ // evaluate((λx.y) z) == y
+ assertEvalTo(apply(λ("x", y), z), y);
+ }
+
+ @Test
+ public void l_evaluation_de_l_application_d_une_abstraction_d_abstraction_ne_substitue_pas_les_variables_redefinies() {
+ // evaluate((λx.λx.x) z) == λx.x
+ assertEvalTo(apply(λ("x", λ("x", x)), z), λ("x", x));
+ }
+
+ /////
+
+ private static void assertEvalTo(Term termToEvaluate, Term result) {
+ assertThat(evaluate(termToEvaluate), is(result));
+ }
+}
diff --git a/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/ast/TermTest.java b/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/ast/TermTest.java
new file mode 100644
index 0000000..f11ca77
--- /dev/null
+++ b/java/lcgoji/src/test/java/org/lambdanantes/lcgoji/ast/TermTest.java
@@ -0,0 +1,38 @@
+package org.lambdanantes.lcgoji.ast;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.lambdanantes.lcgoji.ast.Abs.λ;
+import static org.lambdanantes.lcgoji.ast.App.apply;
+import static org.lambdanantes.lcgoji.ast.Var.var;
+
+public class TermTest {
+
+ @Test
+ public void les_equals_des_termes_sont_corrects() {
+ // x == x
+ assertThat(var("x"), is(var("x")));
+ // x != y
+ assertThat(var("x"), is(not(var("y"))));
+ // λx.x == λx.x
+ assertThat(λ("x", var("x")), is(λ("x", var("x"))));
+ // λx.x != λy.x
+ assertThat(λ("x", var("x")), is(not(λ("y", var("x")))));
+ // x y == x y
+ assertThat(apply(var("x"), var("y")), is(apply(var("x"), var("y"))));
+ // x x != x y
+ assertThat(apply(var("x"), var("x")), is(not(apply(var("x"), var("y")))));
+ }
+
+ @Test
+ public void les_toString_des_termes_utilisent_la_notation_consacree() {
+ assertThat(var("x").toString(), is("x"));
+ assertThat(λ("x", var("x")).toString(), is("λx.x"));
+ assertThat(apply(λ("x", var("x")), var("x")).toString(), is("(λx.x) x"));
+ assertThat(apply(λ("x", λ("x", var("x"))), var("y")).toString(), is("(λx.λx.x) y"));
+ }
+
+} \ No newline at end of file
diff --git a/java/log4j2.xml b/java/log4j2.xml
new file mode 100644
index 0000000..f938085
--- /dev/null
+++ b/java/log4j2.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration monitorInterval="60">
+ <appenders>
+ <console name="console" target="SYSTEM_OUT">
+ <patternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
+ </console>
+ </appenders>
+
+ <loggers>
+ <root level="debug">
+ <appender-ref ref="console"/>
+ </root>
+ <logger name="org.eclipse.jetty" level="warn"/>
+ <logger name="spark" level="info"/>
+ </loggers>
+</configuration>
+
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 0000000..f4a4f30
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.lambdanantes</groupId>
+ <artifactId>lcgoji</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.source>21</maven.compiler.source>
+ <maven.compiler.target>21</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <lombok.version>1.18.30</lombok.version>
+ <log4j.version>2.16.0</log4j.version>
+
+ <jetty-util.version>9.4.31.v20200723</jetty-util.version>
+ <spark-core.version>2.9.3</spark-core.version>
+ <httpclient.version>4.3.1</httpclient.version>
+
+ <junit.version>4.10</junit.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty-util.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sparkjava</groupId>
+ <artifactId>spark-core</artifactId>
+ <version>${spark-core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>fluent-hc</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ </dependencies>
+</project> \ No newline at end of file