diff options
| -rw-r--r-- | src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java | 61 | ||||
| -rw-r--r-- | src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserNamespaceTests.java | 43 |
2 files changed, 103 insertions, 1 deletions
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 dc35fa0..2a51257 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/parser/Parser.java @@ -32,6 +32,12 @@ public class Parser { ) { var start = tokens.startPosition(); + var imports = parseMany( + () -> !tokens.isNext(TokenType.KEYWORD_IMPORT), + () -> parseImport(tokens), + () -> true + ); + var body = parseMany( () -> tokens.isNext(TokenType.END), () -> parseNamespaceStatement(tokens), @@ -43,12 +49,32 @@ public class Parser { return new UntypedNamespaceNode( namespaceName, - List.of(), + imports, body, source ); } + private static UntypedImportNode parseImport(TokenIterator tokens) { + tokens.skip(TokenType.KEYWORD_IMPORT); + tokens.skip(TokenType.SYMBOL_BRACE_OPEN); + + var importedNames = parseMany( + () -> tokens.isNext(TokenType.SYMBOL_BRACE_CLOSE), + () -> parseIdentifier(tokens), + () -> tokens.trySkip(TokenType.SYMBOL_COMMA) + ); + + tokens.skip(TokenType.SYMBOL_BRACE_CLOSE); + tokens.skip(TokenType.KEYWORD_FROM); + + var namespaceName = parseRelativeNamespaceName(tokens); + + tokens.skip(TokenType.SYMBOL_SEMICOLON); + + return new UntypedImportNode(importedNames, namespaceName); + } + static UntypedNamespaceStatementNode parseNamespaceStatement( TokenIterator tokens ) { @@ -281,6 +307,39 @@ public class Parser { return new NamespaceName(namespaceParts); } + private static NamespaceName parseRelativeNamespaceName(TokenIterator tokens) { + var isSibling = tokens.trySkip(TokenType.SYMBOL_DOT); + + List<String> parentParts; + if (isSibling) { + tokens.skip(TokenType.SYMBOL_FORWARD_SLASH); + parentParts = List.of("."); + } else if (tokens.isNext(TokenType.SYMBOL_DOT_DOT)) { + parentParts = new ArrayList<>(); + while (tokens.trySkip(TokenType.SYMBOL_DOT_DOT)) { + parentParts.add(".."); + tokens.skip(TokenType.SYMBOL_FORWARD_SLASH); + } + } else { + parentParts = List.of(parseIdentifier(tokens)); + } + + var childParts = parseMany( + () -> !tokens.isNext(TokenType.IDENTIFIER), + () -> { + tokens.trySkip(TokenType.SYMBOL_FORWARD_SLASH); + return parseIdentifier(tokens); + }, + () -> true + ); + + var namespaceParts = new ArrayList<String>(); + namespaceParts.addAll(parentParts); + namespaceParts.addAll(childParts); + + return new NamespaceName(namespaceParts); + } + private static String parseIdentifier(TokenIterator tokens) { return tokens.next(TokenType.IDENTIFIER).charSequence().toString(); } diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserNamespaceTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserNamespaceTests.java index 22878dc..4df8b95 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserNamespaceTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/parser/ParserNamespaceTests.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedStructDefinitionNode; import org.zwobble.hobgoblin.compiler.types.NamespaceName; +import static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedImportNodeMatcher.isUntypedImportNode; import static org.zwobble.hobgoblin.compiler.ast.untyped.UntypedNamespaceNodeMatcher.isUntypedNamespaceNode; import static org.zwobble.hobgoblin.compiler.parser.ParserTesting.parseString; import static org.zwobble.precisely.AssertThat.assertThat; @@ -11,6 +12,48 @@ import static org.zwobble.precisely.Matchers.*; public class ParserNamespaceTests { @Test + public void emptyNamespaceHasNoImports() { + var source = ""; + + var node = parseString( + source, + tokens -> Parser.parseNamespace(tokens, NamespaceName.of("Example", "Project")) + ); + + assertThat(node, isUntypedNamespaceNode() + .withImports(isSequence()) + ); + } + + @Test + public void importsAreParsed() { + var source = """ + import {Point} from point; + import {Square, Rectangle} from ./shapes; + import {Line} from ../../line; + """; + + var node = parseString( + source, + tokens -> Parser.parseNamespace(tokens, NamespaceName.of("Example", "Project")) + ); + + assertThat(node, isUntypedNamespaceNode() + .withImports(isSequence( + isUntypedImportNode() + .withImportedNames(isSequence(equalTo("Point"))) + .withNamespaceName(equalTo(NamespaceName.of("point"))), + isUntypedImportNode() + .withImportedNames(isSequence(equalTo("Square"), equalTo("Rectangle"))) + .withNamespaceName(equalTo(NamespaceName.of(".", "shapes"))), + isUntypedImportNode() + .withImportedNames(isSequence(equalTo("Line"))) + .withNamespaceName(equalTo(NamespaceName.of("..", "..", "line"))) + )) + ); + } + + @Test public void emptyNamespaceHasEmptyBody() { var source = ""; |
