summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-10-25 10:11:50 +0000
committerMichael Williamson <mike@zwobble.org>2026-10-25 10:11:50 +0000
commit6d211928a40953bbebbee574fa5e8928a3ea687b (patch)
treeed81c613a56ec8063af14566997a794a72ef81c9 /src
parent577abae87d7a73a72d432311a604124bed0a1f4c (diff)
Parse function expressions
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedExpressionNode.java2
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNode.java60
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNode.java38
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java50
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/parser/TokenType.java2
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/parser/Tokenizer.java9
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java53
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNodeMatcher.java47
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserFunctionExpressionTests.java55
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/parser/TokenizerTests.java22
10 files changed, 337 insertions, 1 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedExpressionNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedExpressionNode.java
index 11f6b67..a5c54a0 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedExpressionNode.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedExpressionNode.java
@@ -5,7 +5,7 @@ package org.zwobble.hobgoblin.compiler.ast.untyped;
// Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode imports
// Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode imports
-public sealed interface UntypedExpressionNode permits org.zwobble.hobgoblin.compiler.ast.untyped.UntypedCallNode, org.zwobble.hobgoblin.compiler.ast.untyped.UntypedMemberAccessNode, org.zwobble.hobgoblin.compiler.ast.untyped.UntypedReferenceNode {
+public sealed interface UntypedExpressionNode permits org.zwobble.hobgoblin.compiler.ast.untyped.UntypedCallNode, org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode, org.zwobble.hobgoblin.compiler.ast.untyped.UntypedMemberAccessNode, org.zwobble.hobgoblin.compiler.ast.untyped.UntypedReferenceNode {
public interface Builder {
public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode build();
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNode.java
new file mode 100644
index 0000000..4e410f3
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNode.java
@@ -0,0 +1,60 @@
+// Generated by hobgoblin.
+
+package org.zwobble.hobgoblin.compiler.ast.untyped;
+
+// Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode imports
+// Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode imports
+
+public record UntypedFunctionExpressionNode(
+ java.util.List<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode> params,
+ org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode body,
+ org.zwobble.hobgoblin.compiler.sources.Source source
+) implements org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode {
+ public static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder arbitrary() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(java.util.List.of(), org.zwobble.hobgoblin.compiler.ast.untyped.UntypedReferenceNode.arbitrary().build(), org.zwobble.hobgoblin.compiler.sources.HobgoblinNativeSources.arbitrarySource());
+ }
+
+ public record Builder(
+ java.util.List<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode> params,
+ org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode body,
+ org.zwobble.hobgoblin.compiler.sources.Source source
+ ) implements org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode.Builder {
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode build() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode(params, body, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder withParams(java.util.List<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode> params) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder addParam(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode param) {
+ var params = new java.util.ArrayList<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>(this.params);
+ params.add(param);
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder addParam(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder param) {
+ var params = new java.util.ArrayList<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>(this.params);
+ params.add(param.build());
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder withBody(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode body) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder withBody(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode.Builder body) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body.build(), source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder withSource(org.zwobble.hobgoblin.compiler.sources.Source source) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder(params, body, source);
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.Builder body
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode body
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNode.java
new file mode 100644
index 0000000..068097f
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNode.java
@@ -0,0 +1,38 @@
+// Generated by hobgoblin.
+
+package org.zwobble.hobgoblin.compiler.ast.untyped;
+
+// Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode imports
+// Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode imports
+
+public record UntypedParamNode(
+ String name,
+ org.zwobble.hobgoblin.compiler.sources.Source source
+) {
+ public static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder arbitrary() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder("", org.zwobble.hobgoblin.compiler.sources.HobgoblinNativeSources.arbitrarySource());
+ }
+
+ public record Builder(
+ String name,
+ org.zwobble.hobgoblin.compiler.sources.Source source
+ ) {
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode build() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode(name, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder withName(String name) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder(name, source);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder withSource(org.zwobble.hobgoblin.compiler.sources.Source source) {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder(name, source);
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.Builder body
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode body
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java b/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java
index 976ab75..cdeeedd 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java
@@ -457,6 +457,19 @@ public class Parser {
return Optional.of(typeParams);
}
+ // == Params ==
+
+ private static UntypedParamNode parseParam(TokenIterator tokens) {
+ var start = tokens.startPosition();
+
+ var name = parseIdentifier(tokens);
+
+ var end = tokens.endPosition();
+ var source = createSource(start, end);
+
+ return new UntypedParamNode(name, source);
+ }
+
// == Type-level expressions ==
static UntypedTypeLevelExpressionNode parseTypeLevelExpression(
@@ -545,6 +558,43 @@ public class Parser {
private static UntypedExpressionNode parsePrimaryExpression(
TokenIterator tokens
) {
+ if (tokens.isNext(TokenType.SYMBOL_DOLLAR)) {
+ return parseFunctionExpression(tokens);
+ } else if (tokens.isNext(TokenType.IDENTIFIER)) {
+ return parseReference(tokens);
+ } else {
+ throw new UnsupportedOperationException("TODO");
+ }
+ }
+
+ private static UntypedFunctionExpressionNode parseFunctionExpression(TokenIterator tokens) {
+ var start = tokens.startPosition();
+
+ tokens.skip(TokenType.SYMBOL_DOLLAR);
+ tokens.skip(TokenType.SYMBOL_PAREN_OPEN);
+
+ var params = parseMany(
+ () -> tokens.isNext(TokenType.SYMBOL_PAREN_CLOSE),
+ () -> parseParam(tokens),
+ () -> tokens.trySkip(TokenType.SYMBOL_COMMA)
+ );
+
+ tokens.skip(TokenType.SYMBOL_PAREN_CLOSE);
+ tokens.skip(TokenType.SYMBOL_FAT_ARROW);
+
+ var body = parseTopLevelExpression(tokens);
+
+ var end = tokens.endPosition();
+ var source = createSource(start, end);
+
+ return new UntypedFunctionExpressionNode(
+ params,
+ body,
+ source
+ );
+ }
+
+ private static UntypedReferenceNode parseReference(TokenIterator tokens) {
var start = tokens.startPosition();
var name = parseIdentifier(tokens);
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/parser/TokenType.java b/src/main/java/org/zwobble/hobgoblin/compiler/parser/TokenType.java
index 2a85072..1181fca 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/parser/TokenType.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/parser/TokenType.java
@@ -19,8 +19,10 @@ enum TokenType {
SYMBOL_BRACE_CLOSE,
SYMBOL_COLON,
SYMBOL_COMMA,
+ SYMBOL_DOLLAR,
SYMBOL_DOT,
SYMBOL_DOT_DOT,
+ SYMBOL_FAT_ARROW,
SYMBOL_FORWARD_SLASH,
SYMBOL_PAREN_OPEN,
SYMBOL_PAREN_CLOSE,
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/parser/Tokenizer.java b/src/main/java/org/zwobble/hobgoblin/compiler/parser/Tokenizer.java
index 1a775b0..56c2123 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/parser/Tokenizer.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/parser/Tokenizer.java
@@ -154,6 +154,10 @@ class Tokenizer {
tokenType = TokenType.SYMBOL_DOT_DOT;
iterator.skip();
iterator.skip();
+ } else if ("=>".contentEquals(iterator.peekSequence(2))) {
+ tokenType = TokenType.SYMBOL_FAT_ARROW;
+ iterator.skip();
+ iterator.skip();
} else {
switch (iterator.peek()) {
case '{':
@@ -176,6 +180,11 @@ class Tokenizer {
iterator.skip();
break;
+ case '$':
+ tokenType = TokenType.SYMBOL_DOLLAR;
+ iterator.skip();
+ break;
+
case '.':
tokenType = TokenType.SYMBOL_DOT;
iterator.skip();
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java
new file mode 100644
index 0000000..71356e8
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedFunctionExpressionNodeMatcher.java
@@ -0,0 +1,53 @@
+// Generated by hobgoblin.
+
+package org.zwobble.hobgoblin.compiler.ast.untyped;
+
+// Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher imports
+// Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher imports
+
+public final class UntypedFunctionExpressionNodeMatcher implements org.zwobble.precisely.Matcher<java.lang.Object> {
+ public static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher isUntypedFunctionExpressionNode() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher(java.util.List.of());
+ }
+
+ private final java.util.List<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode>> submatchers;
+
+ private UntypedFunctionExpressionNodeMatcher(
+ java.util.List<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode>> submatchers
+ ) {
+ this.submatchers = submatchers;
+ }
+
+ public org.zwobble.precisely.MatchResult match(java.lang.Object actual) {
+ return this.toMatcher().match(actual);
+ }
+
+ public org.zwobble.precisely.TextTree describe() {
+ return this.toMatcher().describe();
+ }
+
+ private org.zwobble.precisely.Matcher<java.lang.Object> toMatcher() {
+ return org.zwobble.precisely.Matchers.instanceOf(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode.class, this.submatchers);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher withParams(org.zwobble.precisely.Matcher<? super java.util.List<org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>> params) {
+ var submatchers = new java.util.ArrayList<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode>>(this.submatchers);
+ submatchers.add(org.zwobble.precisely.Matchers.has("params", org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode::params, (org.zwobble.precisely.Matcher) params));
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher(submatchers);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher withBody(org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedExpressionNode> body) {
+ var submatchers = new java.util.ArrayList<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode>>(this.submatchers);
+ submatchers.add(org.zwobble.precisely.Matchers.has("body", org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode::body, (org.zwobble.precisely.Matcher) body));
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher(submatchers);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher withSource(org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.sources.Source> source) {
+ var submatchers = new java.util.ArrayList<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode>>(this.submatchers);
+ submatchers.add(org.zwobble.precisely.Matchers.has("source", org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNode::source, (org.zwobble.precisely.Matcher) source));
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher(submatchers);
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher body
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNodeMatcher.java b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNodeMatcher.java
new file mode 100644
index 0000000..c140fb2
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedParamNodeMatcher.java
@@ -0,0 +1,47 @@
+// Generated by hobgoblin.
+
+package org.zwobble.hobgoblin.compiler.ast.untyped;
+
+// Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher imports
+// Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher imports
+
+public final class UntypedParamNodeMatcher implements org.zwobble.precisely.Matcher<java.lang.Object> {
+ public static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher isUntypedParamNode() {
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher(java.util.List.of());
+ }
+
+ private final java.util.List<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>> submatchers;
+
+ private UntypedParamNodeMatcher(
+ java.util.List<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>> submatchers
+ ) {
+ this.submatchers = submatchers;
+ }
+
+ public org.zwobble.precisely.MatchResult match(java.lang.Object actual) {
+ return this.toMatcher().match(actual);
+ }
+
+ public org.zwobble.precisely.TextTree describe() {
+ return this.toMatcher().describe();
+ }
+
+ private org.zwobble.precisely.Matcher<java.lang.Object> toMatcher() {
+ return org.zwobble.precisely.Matchers.instanceOf(org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode.class, this.submatchers);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher withName(org.zwobble.precisely.Matcher<? super String> name) {
+ var submatchers = new java.util.ArrayList<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>>(this.submatchers);
+ submatchers.add(org.zwobble.precisely.Matchers.has("name", org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode::name, (org.zwobble.precisely.Matcher) name));
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher(submatchers);
+ }
+
+ public org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher withSource(org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.sources.Source> source) {
+ var submatchers = new java.util.ArrayList<org.zwobble.precisely.Matcher<? super org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode>>(this.submatchers);
+ submatchers.add(org.zwobble.precisely.Matchers.has("source", org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNode::source, (org.zwobble.precisely.Matcher) source));
+ return new org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher(submatchers);
+ }
+
+ // Custom area start: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher body
+ // Custom area end: org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher body
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserFunctionExpressionTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserFunctionExpressionTests.java
new file mode 100644
index 0000000..99cf3c0
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserFunctionExpressionTests.java
@@ -0,0 +1,55 @@
+package org.zwobble.hobgoblin.compiler.parser;
+
+import org.junit.jupiter.api.Test;
+
+import static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedFunctionExpressionNodeMatcher.isUntypedFunctionExpressionNode;
+import static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedParamNodeMatcher.isUntypedParamNode;
+import static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedReferenceNodeMatcher.isUntypedReferenceNode;
+import static org.zwobble.hobgoblin.compiler.parser.ParserTesting.parseString;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.equalTo;
+import static org.zwobble.precisely.Matchers.isSequence;
+
+public class ParserFunctionExpressionTests {
+ @Test
+ public void canParseFunctionExpressionWithNoParams() {
+ var source = "$() => x";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(node, isUntypedFunctionExpressionNode()
+ .withParams(isSequence())
+ .withBody(isUntypedReferenceNode().withName(equalTo("x")))
+ );
+ }
+
+ @Test
+ public void canParseFunctionExpressionWithOneParam() {
+ var source = "$(a) => x";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(node, isUntypedFunctionExpressionNode()
+ .withParams(isSequence(
+ isUntypedParamNode().withName(equalTo("a"))
+ ))
+ .withBody(isUntypedReferenceNode().withName(equalTo("x")))
+ );
+ }
+ @Test
+ public void canParseFunctionExpressionWithMultipleParams() {
+ var source = "$(a, b, c) => x";
+
+ var node = parseString(source, Parser::parseTopLevelExpression);
+
+ assertThat(node, isUntypedFunctionExpressionNode()
+ .withParams(isSequence(
+ isUntypedParamNode().withName(equalTo("a")),
+ isUntypedParamNode().withName(equalTo("b")),
+ isUntypedParamNode().withName(equalTo("c"))
+ ))
+ .withBody(isUntypedReferenceNode().withName(equalTo("x")))
+ );
+ }
+}
+
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/parser/TokenizerTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/parser/TokenizerTests.java
index 39aded5..d5b7347 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/parser/TokenizerTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/parser/TokenizerTests.java
@@ -173,6 +173,17 @@ public class TokenizerTests {
}
@Test
+ public void symbolDollarIsTokenized() {
+ var sourceText = SourceText.fromString("<string>", "$");
+
+ var tokens = Tokenizer.tokenize(sourceText);
+
+ assertThat(tokens.tokens(), isSequence(
+ equalTo(new Token(TokenType.SYMBOL_DOLLAR, sourceRange(sourceText, 0, 1)))
+ ));
+ }
+
+ @Test
public void symbolDotIsTokenized() {
var sourceText = SourceText.fromString("<string>", ".");
@@ -195,6 +206,17 @@ public class TokenizerTests {
}
@Test
+ public void symbolFatArrowIsTokenized() {
+ var sourceText = SourceText.fromString("<string>", "=>");
+
+ var tokens = Tokenizer.tokenize(sourceText);
+
+ assertThat(tokens.tokens(), isSequence(
+ equalTo(new Token(TokenType.SYMBOL_FAT_ARROW, sourceRange(sourceText, 0, 2)))
+ ));
+ }
+
+ @Test
public void symbolForwardSlashIsTokenized() {
var sourceText = SourceText.fromString("<string>", "/");