diff --git a/CHANGES.md b/CHANGES.md index 414ce9b2..33900be1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,23 @@ Changes ======= -0.5.5 (unreleased) +0.6.0 (2022-09-12) + +- Drop UnaryOperator, replaced by the proper method calls +- Minor refactoring of CLI options +- Improve MagikIndexer to determine whether a method really returns something and setting the resulting type of the method accordingly (undefined result when no new-method-doc is available, or an empty result) +- Don't overwrite already known methods in JsonTypeKeeperReader +- Add JsonTypeKeeperWriter +- Support methods returning a parameter with the `_parameter(..)` type/ParameterReferenceType +- Fix MagikGrammer better support EOLs in certain cases +- Fix showing procedure doc on hover +- Extend hover provider, now supports packages, conditions +- Support conditions +- Changes to MagikGrammar +- Rewrite parts of references to types in TypeKeeper/types. Fixes mem leaks, references to invalid/old types. MagikPreIndexer can now also be removed and methods without a type definition can be indexed +- Methods support recording used globals, called methods, used slots, used conditions. This allows for finding references and possibly method renaming in the future +- Various bug fixes +- Various new features 0.5.4 (2022-11-07) diff --git a/magik-checks/pom.xml b/magik-checks/pom.xml index 8eae0dd7..ee95963b 100644 --- a/magik-checks/pom.xml +++ b/magik-checks/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-checks diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java index 85bbd7de..df2b3adb 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/CheckList.java @@ -52,8 +52,8 @@ private CheckList() { } /** - * Get the list of {{MagikCheck}}s. - * @return List of with {{MagikCheck}}s + * Get the list of {@link MagikCheck}s. + * @return List of {@link MagikCheck}s. */ public static List> getChecks() { return List.of( @@ -91,8 +91,8 @@ public static List> getChecks() { } /** - * Get {{MagikCheck}}s which are disabled by default. - * @return List of {{MagikCheck}}s. + * Get {@link MagikCheck}s which are disabled by default. + * @return List of {@link MagikCheck}s. */ public static List> getDisabledByDefaultChecks() { return getChecks().stream() @@ -101,8 +101,8 @@ public static List> getDisabledByDefaultChecks() { } /** - * Get {{MagikCheck}}s which are templated. - * @return List of {{MagikCheck}}s. + * Get {@link MagikCheck}s which are templated. + * @return List of {@link MagikCheck}s. */ public static List> getTemplatedChecks() { return getChecks().stream() diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/MagikIssue.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/MagikIssue.java index 19bc83b3..ed4d5185 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/MagikIssue.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/MagikIssue.java @@ -25,8 +25,8 @@ public MagikIssue(final Location location, final String message, final MagikChec } /** - * Get the {{Location}} of the issue. - * @return {{Location}}. + * Get the {@link Location} of the issue. + * @return {@link Location}. */ public Location location() { return this.location; @@ -77,8 +77,8 @@ public String message() { } /** - * Get the {{MagikCheck}} giving the issue. - * @return {{MagikCheck}} giving the issue. + * Get the {@link MagikCheck} giving the issue. + * @return {@link MagikCheck} giving the issue. */ public MagikCheck check() { return this.check; diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/CommentedCodeCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/CommentedCodeCheck.java index fd49f8cf..0083166d 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/CommentedCodeCheck.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/CommentedCodeCheck.java @@ -78,8 +78,9 @@ private Map> extractCommentBlocks(final AstNode node) { if (startToken == null) { startToken = token; - } else if (lastToken.getLine() != token.getLine() - 1 - || lastToken.getColumn() != token.getColumn()) { + } else if (lastToken != null + && (lastToken.getLine() != token.getLine() - 1 + || lastToken.getColumn() != token.getColumn())) { // Block broken, either due to line not connecting or indent changed. // Save current block. diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FormattingCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FormattingCheck.java index 3f71eb4f..41f9c364 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FormattingCheck.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/FormattingCheck.java @@ -5,7 +5,6 @@ import com.sonar.sslr.api.Token; import java.util.Set; import nl.ramsolutions.sw.magik.MagikFile; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import nl.ramsolutions.sw.magik.checks.MagikCheck; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; @@ -112,10 +111,6 @@ protected void walkPostMagik(final AstNode node) { @Override public void walkToken(final Token token) { - if (token.getType() == ExtendedTokenType.SYNTAX_ERROR) { - return; - } - this.previousToken = this.currentToken; this.currentToken = this.nextToken; this.nextToken = token; diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheck.java index f1c6ff32..9b01e123 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheck.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheck.java @@ -49,4 +49,5 @@ private void checkDefinition(final AstNode node) { this.addIssue(node, message); } } + } diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/SwMethodDocCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/SwMethodDocCheck.java index c317e16b..70d27e5b 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/SwMethodDocCheck.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/SwMethodDocCheck.java @@ -8,7 +8,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.annotation.CheckForNull; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.checks.DisabledByDefault; import nl.ramsolutions.sw.magik.checks.MagikCheck; @@ -18,7 +17,7 @@ /** * Check if method docs are valid, according to SW style. */ -@DisabledByDefault // This conflicts with {{MethodDocCheck}}! +@DisabledByDefault @Rule(key = SwMethodDocCheck.CHECK_KEY) public class SwMethodDocCheck extends MagikCheck { @@ -31,7 +30,7 @@ public class SwMethodDocCheck extends MagikCheck { @Override protected void walkPreMethodDefinition(final AstNode node) { final String methodDoc = this.extractDoc(node); - if (methodDoc == null) { + if (methodDoc.isBlank()) { final String message = String.format(MESSAGE, "all"); this.addIssue(node, message); return; @@ -73,7 +72,6 @@ private Set getMethodParameters(final AstNode node) { return parameters; } - @CheckForNull private String extractDoc(final AstNode node) { return MagikCommentExtractor.extractDocComments(node) .map(Token::getValue) diff --git a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/UndefinedVariableCheck.java b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/UndefinedVariableCheck.java index 213cb6db..1c189b8d 100644 --- a/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/UndefinedVariableCheck.java +++ b/magik-checks/src/main/java/nl/ramsolutions/sw/magik/checks/checks/UndefinedVariableCheck.java @@ -19,7 +19,8 @@ public class UndefinedVariableCheck extends MagikCheck { @Override protected void walkPostMagik(final AstNode node) { final GlobalScope globalScope = this.getMagikFile().getGlobalScope(); - globalScope.getScopeEntriesInScope().stream() + globalScope.getSelfAndDescendantScopes().stream() + .flatMap(scope -> scope.getScopeEntriesInScope().stream()) .filter(scopeEntry -> scopeEntry.isType(ScopeEntry.Type.GLOBAL)) .filter(scopeEntry -> this.isPrefixed(scopeEntry.getIdentifier())) .forEach(scopeEntry -> { diff --git a/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MagikCheckTestBase.java b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MagikCheckTestBase.java index 36015208..52b006e0 100644 --- a/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MagikCheckTestBase.java +++ b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MagikCheckTestBase.java @@ -20,7 +20,7 @@ class MagikCheckTestBase { /** * VSCode runs from module directory, mvn runs from project directory. * - * @return Proper {{Path}} to file. + * @return Proper {@link Path} to file. */ protected Path getPath(final Path relativePath) { final Path path = Path.of(".").toAbsolutePath().getParent(); diff --git a/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheckTest.java b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheckTest.java index 3a73d59e..6635c0af 100644 --- a/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheckTest.java +++ b/magik-checks/src/test/java/nl/ramsolutions/sw/magik/checks/checks/MethodComplexityCheckTest.java @@ -45,8 +45,8 @@ void testNotTooComplex() { final MagikCheck check = new MethodComplexityCheck(); final String code = "" + "_method a.b\n" - + " _if a" - + " _then" + + " _if a\n" + + " _then\n" + " _endif\n" + "_endmethod\n"; final List issues = this.runCheck(code, check); diff --git a/magik-debug-adapter/pom.xml b/magik-debug-adapter/pom.xml index 83aa5235..de3a760b 100644 --- a/magik-debug-adapter/pom.xml +++ b/magik-debug-adapter/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-debug-adapter diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManager.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManager.java index 7022e7c4..a8428c6b 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManager.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManager.java @@ -215,7 +215,7 @@ MagikBreakpoint addBreakpoint(final Source source, final SourceBreakpoint source if (methodNode == null) { method = ""; } else { - method = helper.getPakkageExemplarMethodName(); + method = helper.getFullExemplarMethodName(); methodLine = methodNode.getTokenLine(); if (methodLine == line) { line = 0; @@ -259,7 +259,7 @@ void clearFunctionBreakpoints() throws IOException, InterruptedException, Execut * Add multiple function breakpoints. * * @param functionBreakpoints Breakpoints to be set. - * @return List of {{MagikBreakpoint}}s. + * @return List of {@link MagikBreakpoint}s. * @throws ExecutionException - * @throws InterruptedException - * @throws IOException - @@ -277,8 +277,8 @@ List addFunctionBreakpoints(final FunctionBreakpoint[] function /** * Add a function breakpoint. * - * @param functionBreakpoint {{FunctionBreakpoint}} to add. - * @return Created {{MagikBreakpoint}}. + * @param functionBreakpoint {@link FunctionBreakpoint} to add. + * @return Created {@link MagikBreakpoint}. * @throws IOException - * @throws ExecutionException - * @throws InterruptedException - @@ -366,7 +366,7 @@ MagikBreakpoint getBreakpoint(final long breakpointId) { // region: Event handling /** - * Handle a {{BreakpointEvent}}. + * Handle a {@link BreakpointEvent}. * @param breakpointEvent event. */ void handleBreakpointEvent(final BreakpointEvent breakpointEvent) { diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Lsp4jConversion.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Lsp4jConversion.java index ab221cd2..331b20d6 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Lsp4jConversion.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Lsp4jConversion.java @@ -24,10 +24,10 @@ private Lsp4jConversion() { } /** - * Convert a slap {{ThreadInfoResponse}} to a lsp4j {{Thread}}. + * Convert a slap {@link ThreadInfoResponse} to a lsp4j {@link Thread}. * @param threadId ID of the thread. * @param threadInfo Slap thread info. - * @return lsp4j {{Thread}}. + * @return lsp4j {@link Thread}. */ public static Thread toLsp4j(final long threadId, final ThreadInfoResponse threadInfo) { final Thread thread = new Thread(); @@ -37,9 +37,9 @@ public static Thread toLsp4j(final long threadId, final ThreadInfoResponse threa } /** - * Convert a slap {{BreakpointSetResponse}} to a lsp4j {{Breakpoint}}. + * Convert a slap {@link BreakpointSetResponse} to a lsp4j {@link Breakpoint}. * @param breakpointSet Slap breakpoint set. - * @return lsp4j {{Breakpoint}}. + * @return lsp4j {@link Breakpoint}. */ public static Breakpoint toLsp4j(final BreakpointSetResponse breakpointSet, final Source source) { final Breakpoint breakpoint = new Breakpoint(); @@ -50,11 +50,11 @@ public static Breakpoint toLsp4j(final BreakpointSetResponse breakpointSet, fina } /** - * Convert a slap {{ThreadStackResponse.StackElement}} to lsp4j {{StackFrame}}. + * Convert a slap {@link ThreadStackResponse.StackElement} to lsp4j {@link StackFrame}. * @param threadId Thread ID. * @param stackElement Stack element. * @param path Path. - * @return lsp4j {{StackFrame}}. + * @return lsp4j {@link StackFrame}. */ public static StackFrame toLsp4j( final long threadId, final ThreadStackResponse.StackElement stackElement, final @Nullable Path path) { @@ -73,10 +73,10 @@ public static StackFrame toLsp4j( } /** - * Convert {{MagikBreakpoint}}s to LSP4j {{Breakpoint}}s. + * Convert {@link MagikBreakpoint}s to LSP4j {@link Breakpoint}s. * @param source Source of file. * @param magikBreakpoints MagikBreakpoints. - * @return Array of converted LSP4j {{Breakpoint}}s. + * @return Array of converted LSP4j {@link Breakpoint}s. */ public static Breakpoint[] toLsp4j(final Source source, final List magikBreakpoints) { return magikBreakpoints.stream() @@ -92,9 +92,9 @@ public static Breakpoint[] toLsp4j(final Source source, final List magikVariables) { return magikVariables.stream() diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/MagikDebugAdapter.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/MagikDebugAdapter.java index 21142f40..d87deb1e 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/MagikDebugAdapter.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/MagikDebugAdapter.java @@ -1,12 +1,16 @@ package nl.ramsolutions.sw.magik.debugadapter; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.debugadapter.BreakpointManager.MagikBreakpoint; import nl.ramsolutions.sw.magik.debugadapter.VariableManager.MagikVariable; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapEvent; @@ -20,6 +24,7 @@ import nl.ramsolutions.sw.magik.debugadapter.slap.events.ThreadStartedEvent; import org.eclipse.lsp4j.debug.Breakpoint; import org.eclipse.lsp4j.debug.Capabilities; +import org.eclipse.lsp4j.debug.ConfigurationDoneArguments; import org.eclipse.lsp4j.debug.ContinueArguments; import org.eclipse.lsp4j.debug.ContinueResponse; import org.eclipse.lsp4j.debug.DisconnectArguments; @@ -37,10 +42,13 @@ import org.eclipse.lsp4j.debug.SetBreakpointsArguments; import org.eclipse.lsp4j.debug.SetBreakpointsResponse; import org.eclipse.lsp4j.debug.SetExceptionBreakpointsArguments; +import org.eclipse.lsp4j.debug.SetExceptionBreakpointsResponse; import org.eclipse.lsp4j.debug.SetFunctionBreakpointsArguments; import org.eclipse.lsp4j.debug.SetFunctionBreakpointsResponse; import org.eclipse.lsp4j.debug.Source; +import org.eclipse.lsp4j.debug.SourceArguments; import org.eclipse.lsp4j.debug.SourceBreakpoint; +import org.eclipse.lsp4j.debug.SourceResponse; import org.eclipse.lsp4j.debug.StackFrame; import org.eclipse.lsp4j.debug.StackTraceArguments; import org.eclipse.lsp4j.debug.StackTraceResponse; @@ -71,6 +79,7 @@ public class MagikDebugAdapter implements IDebugProtocolServer, SlapEventListene private ThreadManager threadManager; private VariableManager variableManager; private BreakpointManager breakpointManager; + private PathMapper pathMapper; /** * Connect to the debug client. @@ -90,28 +99,36 @@ public CompletableFuture initialize(final InitializeRequestArgumen } @Override - @SuppressWarnings("unchecked") + public CompletableFuture configurationDone(final ConfigurationDoneArguments args) { + return new CompletableFuture<>(); + } + + @Override public CompletableFuture attach(final Map args) { - final Map connect = (Map) args.get("connect"); - if (connect == null) { + final String host = this.getHost(args); + final Integer port = this.getPort(args); + if (host == null || port == null) { + // Inform user. final OutputEventArguments outputArgs = new OutputEventArguments(); outputArgs.setCategory(OutputEventArgumentsCategory.STDERR); - outputArgs.setOutput("No \"connect\" object in configuration, aborting."); + outputArgs.setOutput("No \"host\" and/or \"port\" found in configuration, aborting."); this.debugClient.output(outputArgs); - final Throwable exception = new Exception("No \"connect\" object in configuration, aborting."); + // Crash. + final Throwable exception = new Exception("No \"host\" and/or \"port\" found in configuration, aborting."); final CompletableFuture future = new CompletableFuture<>(); future.completeExceptionally(exception); return future; } - final String host = (String) connect.get("host"); - final int port = (int) Math.floor((Double) connect.get("port")); + final Map pathMapping = this.getPathMapping(args); + this.pathMapper = new PathMapper(pathMapping); return CompletableFuture.runAsync(() -> { try { // Connect to session. this.slapProtocol = new SlapProtocol(host, port, this); + this.slapProtocol.connect(); // Inform client we're initialized. this.debugClient.initialized(); @@ -120,11 +137,63 @@ public CompletableFuture attach(final Map args) { } this.breakpointManager = new BreakpointManager(this.slapProtocol, this.debugClient); - this.threadManager = new ThreadManager(this.slapProtocol, this.debugClient); + this.threadManager = new ThreadManager(this.slapProtocol, this.debugClient, this.pathMapper); this.variableManager = new VariableManager(this.slapProtocol); }); } + @SuppressWarnings("unchecked") + private String getHost(final Map args) { + try { + final Map connect = (Map) args.get("connect"); + return (String) connect.get("host"); + } catch (ClassCastException ex) { + return null; + } + } + + @SuppressWarnings("unchecked") + private Integer getPort(final Map args) { + try { + final Map connect = (Map) args.get("connect"); + final Object portObj = connect.get("port"); + if (portObj == null) { + return null; + } + + final Double portDouble = (Double) portObj; + return (int) Math.floor(portDouble); + } catch (ClassCastException ex) { + return null; + } + } + + @SuppressWarnings("unchecked") + private Map getPathMapping(final Map args) { + try { + final Map connect = (Map) args.get("connect"); + if (!connect.containsKey("path_mapping")) { + return Collections.emptyMap(); + } + + final ArrayList> pathMappings = + (ArrayList>) connect.get("path_mapping"); + return pathMappings.stream() + .map(mapping -> { + final String fromStr = mapping.get("from"); + final String toStr = mapping.get("to"); + final Path from = Path.of(fromStr); + final Path to = Path.of(toStr); + return Map.entry(from, to); + }) + .collect(Collectors.toUnmodifiableMap( + entry -> entry.getKey(), + entry -> entry.getValue())); + } catch (ClassCastException ex) { + return Collections.emptyMap(); + } + } + @Override public CompletableFuture disconnect(final DisconnectArguments args) { return CompletableFuture.runAsync(() -> { @@ -374,13 +443,21 @@ public CompletableFuture setFunctionBreakpoints( } @Override - public CompletableFuture setExceptionBreakpoints(final SetExceptionBreakpointsArguments args) { + public CompletableFuture + setExceptionBreakpoints(final SetExceptionBreakpointsArguments args) { LOGGER.trace("setExceptionBreakpoints"); - return CompletableFuture.runAsync(() -> { + return CompletableFuture.supplyAsync(() -> { final String[] filters = args.getFilters(); try { // Set (or update) condition breakpoint. - this.breakpointManager.setConditionBreakpoint(filters); + final MagikBreakpoint magikBreakpoint = this.breakpointManager.setConditionBreakpoint(filters); + + final SetExceptionBreakpointsResponse response = new SetExceptionBreakpointsResponse(); + if (magikBreakpoint != null) { + final Breakpoint[] breakpoints = Lsp4jConversion.toLsp4j(null, List.of(magikBreakpoint)); + response.setBreakpoints(breakpoints); + } + return response; } catch (InterruptedException exception) { java.lang.Thread.currentThread().interrupt(); throw new CompletionException(exception.getMessage(), exception); @@ -415,6 +492,34 @@ public CompletableFuture evaluate(final EvaluateArguments args }); } + @Override + public CompletableFuture source(final SourceArguments args) { + LOGGER.trace( + "source, source: {}", + args.getSource()); + return CompletableFuture.supplyAsync(() -> { + final Source source = args.getSource(); + final String pathStr = source.getPath(); + final Path path = Path.of(pathStr); + final Path mappedPath = this.pathMapper.applyMapping(path); + + final SourceResponse sourceResponse = new SourceResponse(); + if (Files.exists(mappedPath)) { + try { + final String content = Files.readString(mappedPath); + sourceResponse.setContent(content); + } catch (IOException ex) { + ex.printStackTrace(); + + sourceResponse.setContent("Error reading file: " + pathStr); + } + } else { + sourceResponse.setContent("File not found: " + pathStr); + } + return sourceResponse; + }); + } + @Override public void handleEvent(final ISlapEvent event) { LOGGER.trace("Got event: {}", event); diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Main.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Main.java index bd4006e3..feafb739 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Main.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/Main.java @@ -19,14 +19,14 @@ public final class Main { private static final Options OPTIONS; - private static final String OPTION_DEBUG = "debug"; + private static final Option OPTION_DEBUG = Option.builder() + .longOpt("debug") + .desc("Show debug messages") + .build(); static { OPTIONS = new Options(); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_DEBUG) - .desc("Show debug messages") - .build()); + OPTIONS.addOption(OPTION_DEBUG); } private Main() { @@ -71,7 +71,7 @@ private static CommandLine parseCommandline(final String[] args) throws ParseExc * @throws ParseException - */ public static void main(final String[] args) throws IOException, ParseException { - final CommandLine commandLine = parseCommandline(args); + final CommandLine commandLine = Main.parseCommandline(args); if (commandLine.hasOption(OPTION_DEBUG)) { Main.initDebugLogger(); } else { diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/PathMapper.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/PathMapper.java new file mode 100644 index 00000000..943e29e7 --- /dev/null +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/PathMapper.java @@ -0,0 +1,41 @@ +package nl.ramsolutions.sw.magik.debugadapter; + +import java.nio.file.Path; +import java.util.Map; + +/** + * Path mapper, maps paths if a mapping is found. + */ +public final class PathMapper { + + private final Map mapping; + + /** + * Constructor. + * @param mapping Path mappings. + */ + public PathMapper(final Map mapping) { + this.mapping = mapping; + } + + /** + * Apply mapping, if a mapping is found. Otherwise return path itself. + * @param path Path to map. + * @return Mapped path, or original if no mapping was found. + */ + public Path applyMapping(final Path path) { + final Map.Entry pathMap = this.mapping.entrySet().stream() + .filter(entry -> path.startsWith(entry.getKey())) + .findAny() + .orElse(null); + if (pathMap == null) { + return path; + } + + final Path from = pathMap.getKey(); + final Path to = pathMap.getValue(); + final Path relative = from.relativize(path); + return to.resolve(relative); + } + +} diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManager.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManager.java index 48066b96..29d5263d 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManager.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManager.java @@ -43,10 +43,11 @@ class ThreadManager { private static final String LOOPBODY = ""; private static final String UNNAMED_PROC = "()"; private static final String EVAL_EXEMPLAR_PACKAGE = - "_self.method(%s).owner.meta_at(:exemplar_global).package.name.write_string"; + "_self.define_method_target.meta_at(:exemplar_global).package.association_at(%s).package.name.write_string"; private final ISlapProtocol slapProtocol; private final IDebugProtocolClient debugClient; + private final PathMapper pathMapper; private boolean stepCompletedEventReceived; private BreakpointEvent breakpointEvent; @@ -54,15 +55,20 @@ class ThreadManager { * Constructor. * @param slapProtocol SLapProtocol. * @param debugClient LSP4j DebugClient. + * @param pathMapper Path mapping. */ - ThreadManager(final ISlapProtocol slapProtocol, final IDebugProtocolClient debugClient) { + ThreadManager( + final ISlapProtocol slapProtocol, + final IDebugProtocolClient debugClient, + final PathMapper pathMapper) { this.slapProtocol = slapProtocol; this.debugClient = debugClient; + this.pathMapper = pathMapper; } /** * Get the Thread currently active. - * @return {{Thread}}s + * @return {@link Thread}s * @throws IOException - * @throws InterruptedException - * @throws ExecutionException - @@ -112,6 +118,10 @@ List stackTrace(final long threadId) throws IOException, Interrupted // instead of Lsp4jConversion. final List stackFrames = new ArrayList<>(); for (final ThreadStackResponse.StackElement stackElement : threadStack.getStackFrames()) { + LOGGER.trace( + "Stack element, level: {}, language: {}, name: '{}', offset: {}", + stackElement.getLevel(), stackElement.getLanguage(), stackElement.getName(), stackElement.getOffset()); + if (!stackElement.getLanguage().equals(LANGUAGE_MAGIK)) { continue; } @@ -130,9 +140,9 @@ List stackTrace(final long threadId) throws IOException, Interrupted // Do some extra work to determine package. final int level = stackElement.getLevel(); - final String methodName = ":|" + method.substring(index) + "|"; - final String expr = String.format(EVAL_EXEMPLAR_PACKAGE, methodName); - LOGGER.debug("Eval expression: {}", expr); + final String exemplarName = ":|" + method.substring(0, index - 1) + "|"; + final String expr = String.format(EVAL_EXEMPLAR_PACKAGE, exemplarName); + LOGGER.debug("Eval expression: '{}'", expr); final EvalResponse eval = (EvalResponse) this.slapProtocol.evaluate(threadId, level, expr).get(); method = eval.getResult() + ":" + method; @@ -148,8 +158,8 @@ List stackTrace(final long threadId) throws IOException, Interrupted final CompletableFuture sourceFileFuture = this.slapProtocol.getSourceFile(method); final SourceFileResponse sourceFile = (SourceFileResponse) sourceFileFuture.get(); final String filename = sourceFile.getFilename(); - - path = Path.of(filename); + final Path daPath = Path.of(filename); + path = this.pathMapper.applyMapping(daPath); } } catch (final ExecutionException exception) { final Throwable cause = exception.getCause(); @@ -245,16 +255,19 @@ void stepOut(final long threadId) throws IOException, InterruptedException, Exec */ String evaluate(final @Nullable Integer frameId, final String expression) throws IOException, InterruptedException, ExecutionException { - // TODO: Which threadId to choose here? Or do we abort? - final long threadId = frameId != null ? Lsp4jConversion.frameIdToThreadId(frameId) : 0; - final int level = frameId != null ? Lsp4jConversion.frameIdToLevel(frameId) : 0; + if (frameId == null) { + throw new IllegalArgumentException("Missing frame ID"); + } + + final long threadId = Lsp4jConversion.frameIdToThreadId(frameId); + final int level = Lsp4jConversion.frameIdToLevel(frameId); final CompletableFuture evaluateFuture = this.slapProtocol.evaluate(threadId, level, expression); final EvalResponse response = (EvalResponse) evaluateFuture.get(); return response.getResult(); } /** - * Handle a {{ThreadStartedEvent}}. + * Handle a {@link ThreadStartedEvent}. * @param event Event. */ void handleThreadStartedEvent(final ThreadStartedEvent event) { @@ -265,7 +278,7 @@ void handleThreadStartedEvent(final ThreadStartedEvent event) { } /** - * Handle a {{ThreadEndedEvent}}. + * Handle a {@link ThreadEndedEvent}. * @param event Event. */ void handleThreadEndedEvent(final ThreadEndedEvent event) { @@ -276,7 +289,7 @@ void handleThreadEndedEvent(final ThreadEndedEvent event) { } /** - * Handle a {{StepCompletedEvent}}. + * Handle a {@link StepCompletedEvent}. * @param event Event. */ void handleStepCompletedEvent(final StepCompletedEvent event) { diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/VariableManager.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/VariableManager.java index 6062f5b6..69f9c69b 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/VariableManager.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/VariableManager.java @@ -3,10 +3,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapProtocol; import nl.ramsolutions.sw.magik.debugadapter.slap.events.BreakpointEvent; import nl.ramsolutions.sw.magik.debugadapter.slap.events.StepCompletedEvent; @@ -121,10 +123,10 @@ int getFrameId(final int id) { // region: Scopes /** - * Get the {{Scope}}s for frame ID. + * Get the {@link Scope}s for frame ID. * Currently only returns locals scope. * @param frameId Frame ID. - * @return {{Scope}}s for frame ID. + * @return {@link Scope}s for frame ID. */ Scope[] getScopes(final int frameId) { Scope localsScope = this.getScope(frameId); @@ -183,7 +185,7 @@ private MagikVariable addVariable( } /** - * Add a new variable from a {{StackFrameLocalsResponse.Local}}. + * Add a new variable from a {@link StackFrameLocalsResponse.Local}. * @param frameId Frame ID. * @param local Local to convert. * @return New variable. @@ -195,7 +197,7 @@ private MagikVariable addVariable(final int frameId, final Local local) { } /** - * Add a new variable from another {{MagikVariable}}. + * Add a new variable from another {@link MagikVariable}. * @param variable Parent variable. * @param name Name of variable. * @param value Value of variable. @@ -287,7 +289,11 @@ List getVariables(final int reference) throws IOException, Interr magikVariables.addAll(subVariables); } - return magikVariables; + // Sort variables. + final Comparator byName = Comparator.comparing(MagikVariable::getName); + return magikVariables.stream() + .sorted(byName) + .collect(Collectors.toList()); } // endregion @@ -367,7 +373,7 @@ private List variablesFromEnumerated(final MagikVariable variable } /** - * Handle a {{BreakpointEvent}}. + * Handle a {@link BreakpointEvent}. * * @param event Event. */ @@ -376,7 +382,7 @@ void handleBreakpointEvent(final BreakpointEvent event) { } /** - * Handle a {{StepCompletedEvent}}. + * Handle a {@link StepCompletedEvent}. * @param event Event. */ void handleStepCompletedEvent(final StepCompletedEvent event) { diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtils.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferHelper.java similarity index 84% rename from magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtils.java rename to magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferHelper.java index 0f7d6922..0bad8452 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtils.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferHelper.java @@ -8,11 +8,11 @@ * Byte buffer utils. */ @SuppressWarnings("checkstyle:MagicNumber") -public final class ByteBufferUtils { +public final class ByteBufferHelper { private static final int ELEMENTS_PER_LINE = 16; - private ByteBufferUtils() { + private ByteBufferHelper() { } /** @@ -66,7 +66,19 @@ public static long readUInt32(final ByteBuffer buffer, final int position) { final ByteBuffer roBuffer = buffer.asReadOnlyBuffer(); roBuffer.order(buffer.order()); roBuffer.position(position); - return ByteBufferUtils.readUInt32(roBuffer); + return ByteBufferHelper.readUInt32(roBuffer); + } + + /** + * Peek a uint32 value from the buffers current position. Position is not updated. + * @param buffer ByteBuffer to read from. + * @return Uint32 value. + */ + public static long peekUInt32(final ByteBuffer buffer) { + final int position = buffer.position(); + final long val = ByteBufferHelper.readUInt32(buffer); + buffer.position(position); + return val; } /** @@ -95,7 +107,7 @@ public static void writeUInt32(final ByteBuffer buffer, final long value) { * @return String value. */ public static String readString(final ByteBuffer buffer) { - final int length = (int) ByteBufferUtils.readUInt32(buffer); + final int length = (int) ByteBufferHelper.readUInt32(buffer); final byte[] encoded = new byte[length]; buffer.get(encoded); return new String(encoded, StandardCharsets.UTF_8); @@ -111,7 +123,7 @@ public static String readString(final ByteBuffer buffer, final int position) { final ByteBuffer roBuffer = buffer.asReadOnlyBuffer(); roBuffer.order(buffer.order()); roBuffer.position(position); - return ByteBufferUtils.readString(roBuffer); + return ByteBufferHelper.readString(roBuffer); } /** @@ -121,7 +133,7 @@ public static String readString(final ByteBuffer buffer, final int position) { */ public static void writeString(final ByteBuffer buffer, final String value) { final byte[] bytes = value.getBytes(StandardCharsets.UTF_8); - ByteBufferUtils.writeUInt32(buffer, bytes.length); + ByteBufferHelper.writeUInt32(buffer, bytes.length); buffer.put(bytes); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ErrorMessage.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ErrorMessage.java index 341ba394..5d6ce353 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ErrorMessage.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ErrorMessage.java @@ -30,7 +30,7 @@ public int getVal() { } /** - * Get the {{ErrorMessage}} from an interger value. + * Get the {@link ErrorMessage} from an interger value. * @param value Integer value. * @return ErrorMessage */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/EventType.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/EventType.java index 4f1925f7..2dce19a2 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/EventType.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/EventType.java @@ -22,7 +22,7 @@ public int getVal() { } /** - * Get the {{EventType}} from an interger value. + * Get the {@link EventType} from an interger value. * @param value Integer value. * @return EventType */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapEvent.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapEvent.java index 51159f32..d665e07f 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapEvent.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapEvent.java @@ -16,7 +16,7 @@ public interface ISlapEvent { int OFFSET_EVENT_TYPE = 8; /** - * Get the {{EventType}} from this event. + * Get the {@link EventType} from this event. * @return EventType. */ EventType getEventType(); diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapProtocol.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapProtocol.java index 127c7aea..aa2e4a21 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapProtocol.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapProtocol.java @@ -19,10 +19,17 @@ public interface ISlapProtocol { */ long getVersion(); + /** + * Connect to the running session. + * @throws IOException - + * @throws SlapErrorException - + */ + void connect() throws IOException, SlapException; + /** * Close the connection to the debuggee. * - * @throws IOException - + * @throws IOException - */ void close() throws IOException; @@ -129,7 +136,7 @@ public interface ISlapProtocol { CompletableFuture step(long threadId, StepType stepType, int count) throws IOException; /** - * Evaluate {{code}}. + * Evaluate {@code expression}. * @param threadId Thread to evaluate in. * @param level Stack level to evaluate in. * @param expression Code to evaluate. diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapResponse.java index 45a5c515..29d348c6 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ISlapResponse.java @@ -21,7 +21,7 @@ public interface ISlapResponse { int INT_SIZE_BYTES = 4; /** - * Get the {{RequestType}} from this response. + * Get the {@link RequestType} from this response. * @return RequestType. */ RequestType getRequestType(); diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ModifyBreakpoint.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ModifyBreakpoint.java index 6b56ef3a..68f6a206 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ModifyBreakpoint.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ModifyBreakpoint.java @@ -21,7 +21,7 @@ public int getVal() { } /** - * Get the {{ModifyBreakpoint}} from an interger value. + * Get the {@link ModifyBreakpoint} from an interger value. * @param value Integer value. * @return ModifyBreakpoint */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/RequestType.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/RequestType.java index 90c48a74..8adeb94d 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/RequestType.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/RequestType.java @@ -31,7 +31,7 @@ public int getVal() { } /** - * Get the {{RequestType}} from an interger value. + * Get the {@link RequestType} from an interger value. * @param value Integer value. * @return RequestType */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ResponseType.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ResponseType.java index dce287e4..e8f88d58 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ResponseType.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/ResponseType.java @@ -21,7 +21,7 @@ public int getVal() { } /** - * Get the {{ResponseType}} from an interger value. + * Get the {@link ResponseType} from an interger value. * @param value Integer value. * @return ResponseType */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/SlapProtocol.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/SlapProtocol.java index bd5fdc77..01c88d6d 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/SlapProtocol.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/SlapProtocol.java @@ -4,6 +4,7 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.channels.AsynchronousCloseException; import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -78,21 +79,21 @@ enum State { */ public interface SlapEventListener { /** - * Handle an incoming {{SlapEvent}}. + * Handle an incoming {@link SlapEvent}. * @param event Incoming event. */ void handleEvent(ISlapEvent event); } + private final InetSocketAddress inetSocketAddress; private final SlapEventListener listener; - private final SocketChannel socketChannel; - private final ByteBuffer inputBuffer = ByteBuffer.allocate(10240); + private SocketChannel socketChannel; + private final ByteBuffer inputBuffer = ByteBuffer.allocate(65536); private ByteOrder byteOrder = ByteOrder.nativeOrder(); private State state; private RequestType multiResponseRequestType; private long version; - private final List requestFutures = - Collections.synchronizedList(new ArrayList<>()); + private final List requestFutures = Collections.synchronizedList(new ArrayList<>()); private final List subResponses = new ArrayList<>(); /** @@ -102,17 +103,22 @@ public interface SlapEventListener { * @param port Port to connect to. * @throws SlapException - */ - public SlapProtocol(final String host, final int port, final SlapEventListener listener) - throws IOException, SlapException { + public SlapProtocol(final String host, final int port, final SlapEventListener listener) { + this.inetSocketAddress = new InetSocketAddress(host, port); this.listener = listener; this.state = State.WAITING; this.version = -1; + } - // Connect socket. + /** + * Connect to the running session. + * @throws IOException - + * @throws SlapErrorException - + */ + public void connect() throws IOException, SlapException { this.socketChannel = SocketChannel.open(); - final InetSocketAddress inetSocketAddress = new InetSocketAddress(host, port); - this.socketChannel.connect(inetSocketAddress); + this.socketChannel.connect(this.inetSocketAddress); this.doHandshake(); this.startReceiverThread(); @@ -129,6 +135,9 @@ private void startReceiverThread() { } protocol.handleData(); + } catch (IOException exception) { + LOGGER.error(exception.getMessage(), exception); + break; } catch (Exception exception) { LOGGER.error(exception.getMessage(), exception); } @@ -171,7 +180,7 @@ private void doHandshake() throws IOException, SlapException { this.inputBuffer.order(this.byteOrder); // Is this correct? - this.version = ByteBufferUtils.readUInt32(handshakeResponse, 20); + this.version = ByteBufferHelper.readUInt32(handshakeResponse, 20); LOGGER.debug("Connected with MDA, version: {}", this.version); } @@ -252,10 +261,10 @@ private CompletableFuture sendRequest( final int requestLength = buffer.limit(); final int requestVal = requestType.getVal(); - ByteBufferUtils.writeUInt32(buffer, requestLength); - ByteBufferUtils.writeUInt32(buffer, requestVal); - ByteBufferUtils.writeUInt32(buffer, param0); - ByteBufferUtils.writeUInt32(buffer, param1); + ByteBufferHelper.writeUInt32(buffer, requestLength); + ByteBufferHelper.writeUInt32(buffer, requestVal); + ByteBufferHelper.writeUInt32(buffer, param0); + ByteBufferHelper.writeUInt32(buffer, param1); buffer.put(data); buffer.flip(); @@ -265,6 +274,7 @@ private CompletableFuture sendRequest( "Thread: {}, Sending, type: {}, param0: {}, param1: {}", Thread.currentThread().getName(), requestType, param0, param1); final CompletableFuture future = this.addFutureRequest(requestType); + this.socketChannel.write(buffer); LOGGER.trace( "Thread: {}, Sent, type: {}, param0: {}, param1: {}", @@ -281,8 +291,9 @@ private CompletableFuture sendRequest( */ private void handleData() throws IOException { // Read from socket. - final int read = this.socketChannel.read(this.inputBuffer); - if (read == -1) { + try { + this.socketChannel.read(this.inputBuffer); + } catch (final AsynchronousCloseException ex) { // Channel has reached end-of-stream. this.socketChannel.close(); return; @@ -291,33 +302,31 @@ private void handleData() throws IOException { final int limit = this.inputBuffer.limit(); LOGGER.trace("Received data, byte count: {}", limit); - if (limit < 4) { - LOGGER.warn("Ignoring received data, byte count: {}", limit); - // Ignore this data. - this.inputBuffer.clear(); - return; - } while (this.inputBuffer.hasRemaining()) { final int startPosition = this.inputBuffer.position(); - final int messageLength = (int) ByteBufferUtils.readUInt32(this.inputBuffer); // byte: 0-4 - if (this.inputBuffer.limit() < messageLength) { + final int bufferLength = this.inputBuffer.limit() - startPosition; + final int messageLength = (int) ByteBufferHelper.peekUInt32(this.inputBuffer); // byte: 0-4 + LOGGER.trace( + "Message length: {}, buffer size: {}", + messageLength, bufferLength); + if (bufferLength < messageLength) { // Did not receive enough data (yet), wait for more data. break; } - // Rewind, copy data, decode message. - this.inputBuffer.position(startPosition); - final byte[] message = new byte[messageLength]; - this.inputBuffer.get(message); - final ByteBuffer roBuffer = ByteBuffer.wrap(message).asReadOnlyBuffer(); - roBuffer.order(this.inputBuffer.order()); - this.handleMessage(roBuffer); - } - - if (this.inputBuffer.position() != this.inputBuffer.limit()) { - // Sanity. - LOGGER.warn("Euh..."); + // Rewind, decode message from data. + final ByteOrder order = this.inputBuffer.order(); + final ByteBuffer messageBuffer = this.inputBuffer + .position(startPosition) + .slice() + .limit(messageLength) + .asReadOnlyBuffer() + .order(order); + this.handleMessage(messageBuffer); + + // Skip past message. + this.inputBuffer.position(startPosition + messageLength); } this.inputBuffer.compact(); @@ -331,7 +340,7 @@ private void handleMessage(final ByteBuffer buffer) { // Basic message layout: // 00-04: uint32, message length // 04-08: uint32, response type - final int val = (int) ByteBufferUtils.readUInt32(buffer, 4); + final int val = (int) ByteBufferHelper.readUInt32(buffer, 4); final ResponseType responseType = ResponseType.valueOf(val); switch (responseType) { case ERROR: @@ -347,6 +356,7 @@ private void handleMessage(final ByteBuffer buffer) { break; default: + LOGGER.warn("Unknown response type, val: {}", val); break; } } @@ -364,7 +374,7 @@ private void handleReplyMessage(final ByteBuffer buffer) { RequestType requestType = RequestType.UNKOWN; boolean isEndPacket = false; if (this.state == State.WAITING) { - final int val = (int) ByteBufferUtils.readUInt32(buffer, 8); + final int val = (int) ByteBufferHelper.readUInt32(buffer, 8); requestType = RequestType.valueOf(val); } else if (this.state == State.WAITING_FOR_MULTIPLE_RESPONSES) { requestType = this.multiResponseRequestType; @@ -456,7 +466,7 @@ private void handleReplyMessage(final ByteBuffer buffer) { default: if (LOGGER.isWarnEnabled()) { LOGGER.warn("Unknown response, ByteBuffer: {}, contents:\n{}", - buffer, ByteBufferUtils.toHexDump(buffer)); + buffer, ByteBufferHelper.toHexDump(buffer)); } throw new IllegalStateException("Unknown response"); } @@ -480,7 +490,7 @@ private void handleEventMessage(final ByteBuffer buffer) { // 00-04: uint32, message length // 04-08: uint32, response type // 08-12: uint32, event type - final int val = (int) ByteBufferUtils.readUInt32(buffer, 8); + final int val = (int) ByteBufferHelper.readUInt32(buffer, 8); final EventType eventType = EventType.valueOf(val); LOGGER.trace( "Thread: {}, Received event: event type: {}", @@ -521,7 +531,7 @@ private void handleErrorMessage(final ByteBuffer buffer) { // 04-08: uint32, response type // 08-12: uint32, request type // 12-16: uint32, error message - final int val = (int) ByteBufferUtils.readUInt32(buffer, 8); + final int val = (int) ByteBufferHelper.readUInt32(buffer, 8); final RequestType requestType = RequestType.valueOf(val); LOGGER.trace( "Thread: {}, Received error: request type: {}", @@ -549,7 +559,7 @@ public CompletableFuture setBreakpoint(final String method, final final ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(this.byteOrder); - ByteBufferUtils.writeString(buffer, method); + ByteBufferHelper.writeString(buffer, method); return this.sendRequest(RequestType.BREAKPOINT_SET, 0, line, data); } @@ -683,7 +693,7 @@ public CompletableFuture getSourceFile(final String method) throw final ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(this.byteOrder); - ByteBufferUtils.writeString(buffer, method); + ByteBufferHelper.writeString(buffer, method); return this.sendRequest(RequestType.SOURCE_FILE, 0, 0, data); } @@ -705,7 +715,7 @@ public CompletableFuture step( } /** - * Evaluate {{code}}. + * Evaluate {@code expresion}. * @param threadId Thread to evaluate in. * @param level Stack level to evaluate in. * @param expression Code to evaluate. @@ -722,7 +732,7 @@ public CompletableFuture evaluate( final ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(this.byteOrder); - ByteBufferUtils.writeString(buffer, expression); + ByteBufferHelper.writeString(buffer, expression); return this.sendRequest(RequestType.EVALUATE, threadId, level, data); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/StepType.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/StepType.java index aa4b2e45..0a5e5fba 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/StepType.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/StepType.java @@ -22,7 +22,7 @@ public int getVal() { } /** - * Get the {{StepType}} from an interger value. + * Get the {@link StepType} from an interger value. * @param value Integer value. * @return StepType */ diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/BreakpointEvent.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/BreakpointEvent.java index ea9d5b4a..58a4aca2 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/BreakpointEvent.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/BreakpointEvent.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.events; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.EventType; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapEvent; @@ -55,8 +55,8 @@ public String toString() { * @return Decoded event. */ public static BreakpointEvent decode(final ByteBuffer buffer) { - final long breakpointId = ByteBufferUtils.readUInt32(buffer, OFFSET_BREAKPOINT_ID); - final long threadId = ByteBufferUtils.readUInt32(buffer, OFFSET_THREAD_ID); + final long breakpointId = ByteBufferHelper.readUInt32(buffer, OFFSET_BREAKPOINT_ID); + final long threadId = ByteBufferHelper.readUInt32(buffer, OFFSET_THREAD_ID); return new BreakpointEvent(breakpointId, threadId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/StepCompletedEvent.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/StepCompletedEvent.java index afec857f..f3dcfe47 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/StepCompletedEvent.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/StepCompletedEvent.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.events; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.EventType; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapEvent; @@ -39,7 +39,7 @@ public EventType getEventType() { * @return Decoded event. */ public static StepCompletedEvent decode(final ByteBuffer buffer) { - final long threadId = ByteBufferUtils.readUInt32(buffer, OFFSET_THREAD_ID); + final long threadId = ByteBufferHelper.readUInt32(buffer, OFFSET_THREAD_ID); return new StepCompletedEvent(threadId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadEndedEvent.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadEndedEvent.java index dd7e4393..d85491ad 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadEndedEvent.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadEndedEvent.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.events; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.EventType; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapEvent; @@ -47,7 +47,7 @@ public String toString() { * @return Decoded event. */ public static ThreadEndedEvent decode(final ByteBuffer buffer) { - final long threadId = ByteBufferUtils.readUInt32(buffer, OFFSET_THREAD_ID); + final long threadId = ByteBufferHelper.readUInt32(buffer, OFFSET_THREAD_ID); return new ThreadEndedEvent(threadId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadStartedEvent.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadStartedEvent.java index abd53a88..7f15d146 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadStartedEvent.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/events/ThreadStartedEvent.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.events; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.EventType; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapEvent; @@ -47,7 +47,7 @@ public String toString() { * @return Decoded event. */ public static ThreadStartedEvent decode(final ByteBuffer buffer) { - final long threadId = ByteBufferUtils.readUInt32(buffer, OFFSET_THREAD_ID); + final long threadId = ByteBufferHelper.readUInt32(buffer, OFFSET_THREAD_ID); return new ThreadStartedEvent(threadId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/BreakpointSetResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/BreakpointSetResponse.java index 1f6f604f..a4e3d2d2 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/BreakpointSetResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/BreakpointSetResponse.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.responses; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -47,7 +47,7 @@ public String toString() { * @return Decoded message. */ public static BreakpointSetResponse decode(final ByteBuffer buffer) { - final long breakpointId = ByteBufferUtils.readUInt32(buffer, OFFSET_BREAKPOINT_ID); + final long breakpointId = ByteBufferHelper.readUInt32(buffer, OFFSET_BREAKPOINT_ID); return new BreakpointSetResponse(breakpointId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ErrorResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ErrorResponse.java index ce9b019a..dfc52f6d 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ErrorResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ErrorResponse.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.responses; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ErrorMessage; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -50,9 +50,9 @@ public RequestType getRequestType() { * @return Decoded message. */ public static ErrorResponse decode(final ByteBuffer buffer) { - final int requestTypeVal = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_REQUEST_TYPE); + final int requestTypeVal = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_REQUEST_TYPE); final RequestType requestType = RequestType.valueOf(requestTypeVal); - final int errorMessageVal = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_ERROR_TYPE); + final int errorMessageVal = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_ERROR_TYPE); final ErrorMessage errorMessage = ErrorMessage.valueOf(errorMessageVal); return new ErrorResponse(requestType, errorMessage); diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/EvalResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/EvalResponse.java index c61344c6..f1020da4 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/EvalResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/EvalResponse.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.responses; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -45,7 +45,7 @@ public RequestType getRequestType() { * @return Decoded message. */ public static EvalResponse decode(final ByteBuffer buffer) { - final String result = ByteBufferUtils.readString(buffer, OFFSET_RESULT_LENGTH); + final String result = ByteBufferHelper.readString(buffer, OFFSET_RESULT_LENGTH); return new EvalResponse(result); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/SourceFileResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/SourceFileResponse.java index c0d0de60..a84fb16f 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/SourceFileResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/SourceFileResponse.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.debugadapter.slap.responses; import java.nio.ByteBuffer; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -43,7 +43,7 @@ public RequestType getRequestType() { * @return Decoded message. */ public static SourceFileResponse decode(final ByteBuffer buffer) { - final String filename = ByteBufferUtils.readString(buffer, OFFSET_FILENAME_LENGTH); + final String filename = ByteBufferHelper.readString(buffer, OFFSET_FILENAME_LENGTH); return new SourceFileResponse(filename); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/StackFrameLocalsResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/StackFrameLocalsResponse.java index c156ac06..adb167cb 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/StackFrameLocalsResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/StackFrameLocalsResponse.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -62,7 +62,7 @@ public int getVal() { } /** - * Get the {{LocalType}} from an interger value. + * Get the {@link LocalType} from an interger value. * @param value Integer value. * @return LocalType */ @@ -100,7 +100,7 @@ public int getVal() { } /** - * Get the {{VariableType}}s from an interger value. + * Get the {@link VariableType}s from an interger value. * @param flags Integer value. * @return VariableTypes */ @@ -167,11 +167,11 @@ public Set getVariableTypes() { */ @SuppressWarnings("checkstyle:AvoidNestedBlocks") public static Local decode(final ByteBuffer buffer) { - final int status = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_STATUS); + final int status = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_STATUS); final Set variableTypes = VariableType.flagsOf(status); final LocalType type = LocalType.valueOf((status & BYTE_2_MASK) >> BYTE_2_SHIFT); - final int nameLength = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_NAME_LENGTH); - final String name = ByteBufferUtils.readString(buffer, OFFSET_NAME_LENGTH); + final int nameLength = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_NAME_LENGTH); + final String name = ByteBufferHelper.readString(buffer, OFFSET_NAME_LENGTH); String valueStr = null; final int valueOffset = OFFSET_NAME + nameLength; @@ -215,7 +215,7 @@ public static Local decode(final ByteBuffer buffer) { } case TYPE_OBJ: { - valueStr = ByteBufferUtils.readString(buffer, valueOffset); + valueStr = ByteBufferHelper.readString(buffer, valueOffset); break; } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadInfoResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadInfoResponse.java index bd074318..cef91960 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadInfoResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadInfoResponse.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -58,7 +58,7 @@ public int getVal() { } /** - * Get the {{ThreadState}} from an interger value. + * Get the {@link ThreadState} from an interger value. * @param value Integer value. * @return ThreadState */ @@ -95,7 +95,7 @@ public int getVal() { } /** - * Get the {{ThreadFlag}} from an interger value. + * Get the {@link ThreadFlag} from an interger value. * @param value Integer value. * @return ThreadFlag */ @@ -110,7 +110,7 @@ public static ThreadFlag valueOf(final int value) { } /** - * Get the {{ThreadFlag}}s from an interger value. + * Get the {@link ThreadFlag}s from an interger value. * @param flags Integer value. * @return ThreadFlags */ @@ -192,10 +192,10 @@ public RequestType getRequestType() { * @return Decoded message. */ public static ThreadInfoResponse decode(final ByteBuffer buffer) { - final int priority = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_PRIORITY); - final boolean daemon = ByteBufferUtils.readUInt32(buffer, OFFSET_DAEMON) != 0; - final int flags = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_FLAGS); - final String name = ByteBufferUtils.readString(buffer, OFFSET_NAME_LENGTH); + final int priority = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_PRIORITY); + final boolean daemon = ByteBufferHelper.readUInt32(buffer, OFFSET_DAEMON) != 0; + final int flags = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_FLAGS); + final String name = ByteBufferHelper.readString(buffer, OFFSET_NAME_LENGTH); final ThreadState threadState = ThreadState.valueOf(flags & BYTE_1_MASK); final Set threadFlags = ThreadFlag.flagsOf((flags >> BYTE_2_SHIFT) & BYTE_1_MASK); diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadListResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadListResponse.java index 6d0cd903..0509e977 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadListResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadListResponse.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -53,9 +53,9 @@ public RequestType getRequestType() { */ public static ThreadListResponse decode(final ByteBuffer buffer) { final List threadIds = new ArrayList<>(); - final int numThreads = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_NUM_THREADS); + final int numThreads = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_NUM_THREADS); for (int i = 0; i < numThreads; ++i) { - final long threadId = ByteBufferUtils.readUInt32(buffer, OFFSET_THREAD_IDS + i * INT_SIZE_BYTES); + final long threadId = ByteBufferHelper.readUInt32(buffer, OFFSET_THREAD_IDS + i * INT_SIZE_BYTES); threadIds.add(threadId); } diff --git a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadStackResponse.java b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadStackResponse.java index d9ef2cde..ef052ca9 100644 --- a/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadStackResponse.java +++ b/magik-debug-adapter/src/main/java/nl/ramsolutions/sw/magik/debugadapter/slap/responses/ThreadStackResponse.java @@ -5,7 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferUtils; +import nl.ramsolutions.sw.magik.debugadapter.slap.ByteBufferHelper; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.RequestType; @@ -94,16 +94,16 @@ public RequestType getRequestType() { * @return Decoded message. */ public static StackElement decode(final ByteBuffer buffer) { - final int level = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_LEVEL); - final int offset = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_OFFSET); + final int level = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_LEVEL); + final int offset = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_OFFSET); - final int nameLength = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_NAME_LENGTH); + final int nameLength = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_NAME_LENGTH); final byte[] nameArr = new byte[nameLength]; buffer.position(OFFSET_NAME_LANGUAGE); buffer.get(nameArr); final String name = new String(nameArr, StandardCharsets.UTF_8); - final int languageLength = (int) ByteBufferUtils.readUInt32(buffer, OFFSET_LANGUAGE_LENGTH); + final int languageLength = (int) ByteBufferHelper.readUInt32(buffer, OFFSET_LANGUAGE_LENGTH); final byte[] langArr = new byte[languageLength]; buffer.position(OFFSET_NAME_LANGUAGE + nameLength); buffer.get(langArr); diff --git a/magik-debug-adapter/src/main/resources/debug-logging.properties b/magik-debug-adapter/src/main/resources/debug-logging.properties index 80d5ca16..9be86b1b 100644 --- a/magik-debug-adapter/src/main/resources/debug-logging.properties +++ b/magik-debug-adapter/src/main/resources/debug-logging.properties @@ -1,9 +1,13 @@ -handlers = java.util.logging.ConsoleHandler +handlers = java.util.logging.ConsoleHandler, java.util.logging.FileHandler .level = ALL java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.FileHandler.level = ALL +java.util.logging.FileHandler.pattern = %h/magik-debug-adapter.log +java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter + java.util.logging.SimpleFormatter.format = %1$tF %1$tT %4$-7s %2$s : %5$s %6$s%n -nl.ramsolutions.sw.magik.debugadapter.slap.SlapProtocol.level = OFF +#nl.ramsolutions.sw.magik.debugadapter.slap.SlapProtocol.level = OFF diff --git a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManagerTest.java b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManagerTest.java index 3b64ab59..0906e78a 100644 --- a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManagerTest.java +++ b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/BreakpointManagerTest.java @@ -27,7 +27,7 @@ class BreakpointManagerTest { /** * VSCode runs from module directory, mvn runs from project directory. * - * @return Proper {{Path}} to use. + * @return Proper {@link Path} to use. */ private Path getPath(final String relativePath) { final Path path = Path.of(".").toAbsolutePath().getParent(); diff --git a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/TestSlapProtocol.java b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/TestSlapProtocol.java index 1f142725..cb0b023c 100644 --- a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/TestSlapProtocol.java +++ b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/TestSlapProtocol.java @@ -6,6 +6,7 @@ import java.util.concurrent.CompletableFuture; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapProtocol; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; +import nl.ramsolutions.sw.magik.debugadapter.slap.SlapException; import nl.ramsolutions.sw.magik.debugadapter.slap.StepType; import nl.ramsolutions.sw.magik.debugadapter.slap.responses.BreakpointModifyResponse; import nl.ramsolutions.sw.magik.debugadapter.slap.responses.BreakpointSetResponse; @@ -21,6 +22,11 @@ public class TestSlapProtocol implements ISlapProtocol { public TestSlapProtocol() { } + @Override + public void connect() throws IOException, SlapException { + // Does nothing. + } + @Override public long getVersion() { return -1; diff --git a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManagerTest.java b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManagerTest.java index 318ae12b..28c406ad 100644 --- a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManagerTest.java +++ b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/ThreadManagerTest.java @@ -1,8 +1,11 @@ package nl.ramsolutions.sw.magik.debugadapter; import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import nl.ramsolutions.sw.magik.debugadapter.slap.ISlapResponse; @@ -47,7 +50,8 @@ public CompletableFuture getThreadInfo(long threadId) throws IOEx } }; - final ThreadManager manager = new ThreadManager(slapProtocol, null); + final PathMapper pathMapper = new PathMapper(Collections.emptyMap()); + final ThreadManager manager = new ThreadManager(slapProtocol, null, pathMapper); final List threads = manager.threads(); assertThat(threads).hasSize(2); @@ -82,28 +86,31 @@ public CompletableFuture evaluate( public CompletableFuture getSourceFile(final String method) throws IOException { String result = null; if ("sw:object.m1()".equals(method)) { - result = "file1.magik"; + result = "/src/module/sources/file1.magik"; } else if ("sw:object.m2()".equals(method)) { - result = "file2.magik"; + result = "/src/module/sources/file2.magik"; } final SourceFileResponse response = new SourceFileResponse(result); return CompletableFuture.completedFuture(response); } }; - final ThreadManager manager = new ThreadManager(slapProtocol, null); + final Map pathMapping = Map.of( + Path.of("/src"), Path.of("/home/user/src")); + final PathMapper pathMapper = new PathMapper(pathMapping); + final ThreadManager manager = new ThreadManager(slapProtocol, null, pathMapper); final List stackFrames = manager.stackTrace(1); assertThat(stackFrames).hasSize(2); final StackFrame frame0 = stackFrames.get(0); assertThat(frame0.getId()).isEqualTo(Lsp4jConversion.threadIdLevelToFrameId(1, 0)); assertThat(frame0.getName()).isEqualTo("sw:object.m1()"); - assertThat(frame0.getSource().getPath()).isEqualTo("file1.magik"); + assertThat(frame0.getSource().getPath()).isEqualTo("/home/user/src/module/sources/file1.magik"); final StackFrame frame1 = stackFrames.get(1); assertThat(frame1.getId()).isEqualTo(Lsp4jConversion.threadIdLevelToFrameId(1, 2)); assertThat(frame1.getName()).isEqualTo("sw:object.m2()"); - assertThat(frame1.getSource().getPath()).isEqualTo("file2.magik"); + assertThat(frame1.getSource().getPath()).isEqualTo("/home/user/src/module/sources/file2.magik"); } } diff --git a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtilsTest.java b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtilsTest.java index ca0058e6..7a6702a5 100644 --- a/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtilsTest.java +++ b/magik-debug-adapter/src/test/java/nl/ramsolutions/sw/magik/debugadapter/slap/ByteBufferUtilsTest.java @@ -19,7 +19,7 @@ void testReadUInt32LE() { ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(ByteOrder.LITTLE_ENDIAN); - long actual = ByteBufferUtils.readUInt32(buffer); + long actual = ByteBufferHelper.readUInt32(buffer); long expected = 132; assertThat(actual).isEqualTo(expected); } @@ -31,7 +31,7 @@ void testReadUInt32BE() { ByteBuffer buffer = ByteBuffer.wrap(data); buffer.order(ByteOrder.BIG_ENDIAN); - long actual = ByteBufferUtils.readUInt32(buffer); + long actual = ByteBufferHelper.readUInt32(buffer); long expected = 132; assertThat(actual).isEqualTo(expected); } @@ -41,12 +41,12 @@ void testWriteUInt32LE() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.LITTLE_ENDIAN); long writeValue = 0x0A0B0C0D; - ByteBufferUtils.writeUInt32(buffer, writeValue); + ByteBufferHelper.writeUInt32(buffer, writeValue); assertThat(buffer.array()).isEqualTo(new byte[] {0x0D, 0x0C, 0x0B, 0x0A}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isEqualTo(0x0A0B0C0D); } @@ -55,12 +55,12 @@ void testWriteUInt32BE() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.BIG_ENDIAN); long writeValue = 0x0A0B0C0D; - ByteBufferUtils.writeUInt32(buffer, writeValue); + ByteBufferHelper.writeUInt32(buffer, writeValue); assertThat(buffer.array()).isEqualTo(new byte[] {0x0A, 0x0B, 0x0C, 0x0D}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isEqualTo(0x0A0B0C0D); } @@ -69,12 +69,12 @@ void testWriteUInt32LE1() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.LITTLE_ENDIAN); long writeValue = 1; - ByteBufferUtils.writeUInt32(buffer, writeValue); + ByteBufferHelper.writeUInt32(buffer, writeValue); assertThat(buffer.array()).isEqualTo(new byte[] {0x01, 0x00, 0x00, 0x00}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isEqualTo(1); } @@ -83,12 +83,12 @@ void testWriteUInt32BE1() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.BIG_ENDIAN); long value = 1; - ByteBufferUtils.writeUInt32(buffer, value); + ByteBufferHelper.writeUInt32(buffer, value); assertThat(buffer.array()).isEqualTo(new byte[] {0x00, 0x00, 0x00, 0x01}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isEqualTo(1); } @@ -97,12 +97,12 @@ void testWriteUInt32LEOver() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.LITTLE_ENDIAN); long writeValue = 1L << 32; - ByteBufferUtils.writeUInt32(buffer, writeValue); + ByteBufferHelper.writeUInt32(buffer, writeValue); assertThat(buffer.array()).isEqualTo(new byte[] {0x00, 0x00, 0x00, 0x00}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isZero(); } @@ -111,12 +111,12 @@ void testWriteUInt32BEOver() { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.BIG_ENDIAN); long value = 1L << 32; - ByteBufferUtils.writeUInt32(buffer, value); + ByteBufferHelper.writeUInt32(buffer, value); assertThat(buffer.array()).isEqualTo(new byte[] {0x00, 0x00, 0x00, 0x00}); buffer.flip(); - long readValue = ByteBufferUtils.readUInt32(buffer, 0); + long readValue = ByteBufferHelper.readUInt32(buffer, 0); assertThat(readValue).isZero(); } diff --git a/magik-language-server/client-vscode/client/package-lock.json b/magik-language-server/client-vscode/client/package-lock.json index 594715da..da1b9f6a 100644 --- a/magik-language-server/client-vscode/client/package-lock.json +++ b/magik-language-server/client-vscode/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "magik-language-server-client", - "version": "0.5.5-SNAPSHOT", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11,9 +11,9 @@ "dev": true }, "@types/vscode": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.60.0.tgz", - "integrity": "sha512-wZt3VTmzYrgZ0l/3QmEbCq4KAJ71K3/hmMQ/nfpv84oH8e81KKwPEoQ5v8dNCxfHFVJ1JabHKmCvqdYOoVm1Ow==", + "version": "1.70.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.70.0.tgz", + "integrity": "sha512-3/9Fz0F2eBgwciazc94Ien+9u1elnjFg9YAhvAb3qDy/WeFWD9VrOPU7CIytryOVUdbxus8uzL4VZYONA0gDtA==", "dev": true }, "@types/xml2js": { @@ -26,9 +26,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { "version": "1.1.11", @@ -42,7 +42,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "lru-cache": { "version": "6.0.0", @@ -53,9 +53,9 @@ } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -79,9 +79,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" } diff --git a/magik-language-server/client-vscode/client/package.json b/magik-language-server/client-vscode/client/package.json index a103f2fa..6e267d75 100644 --- a/magik-language-server/client-vscode/client/package.json +++ b/magik-language-server/client-vscode/client/package.json @@ -3,14 +3,14 @@ "description": "Magik Language Server and Debug Adapter client for VSCode", "author": "Steven Looman", "license": "GPL-3.0-only", - "version": "0.5.5-SNAPSHOT", + "version": "0.6.0", "publisher": "StevenLooman", "repository": { "type": "git", "url": "https://github.com/StevenLooman/magik-tools" }, "engines": { - "vscode": "^1.59.0" + "vscode": "^1.70.0" }, "dependencies": { "vscode-languageclient": "^7.0.0", @@ -18,7 +18,7 @@ "xml2js": "^0.4.23" }, "devDependencies": { - "@types/vscode": "^1.59.0", + "@types/vscode": "^1.70.0", "@types/xml2js": "^0.4.9" } } diff --git a/magik-language-server/client-vscode/client/src/debug-provider.ts b/magik-language-server/client-vscode/client/src/debug-provider.ts index c265c4fa..0eaf8231 100644 --- a/magik-language-server/client-vscode/client/src/debug-provider.ts +++ b/magik-language-server/client-vscode/client/src/debug-provider.ts @@ -31,7 +31,7 @@ class DebugAdapterExecutableFactory implements vscode.DebugAdapterDescriptorFact vscode.window.showWarningMessage('Could locate java executable, either set Java Home setting ("magik.javaHome") or JAVA_HOME environment variable.'); return; } - const jar = path.join(__dirname, '..', '..', 'server', 'magik-debug-adapter-0.5.5-SNAPSHOT.jar'); + const jar = path.join(__dirname, '..', '..', 'server', 'magik-debug-adapter-0.6.0.jar'); const javaDebuggerOptions = '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,quiet=y,address=5006'; const command = javaExec.toString(); diff --git a/magik-language-server/client-vscode/client/src/language-client.ts b/magik-language-server/client-vscode/client/src/language-client.ts index 98b3256c..da71669f 100644 --- a/magik-language-server/client-vscode/client/src/language-client.ts +++ b/magik-language-server/client-vscode/client/src/language-client.ts @@ -41,7 +41,7 @@ export class MagikLanguageClient implements vscode.Disposable { vscode.window.showWarningMessage('Could locate java executable, either set Java Home setting ("magik.javaHome") or JAVA_HOME environment variable.'); return; } - const jar = path.join(__dirname, '..', '..', 'server', 'magik-language-server-0.5.5-SNAPSHOT.jar'); + const jar = path.join(__dirname, '..', '..', 'server', 'magik-language-server-0.6.0.jar'); const javaDebuggerOptions = '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,quiet=y,address=5005'; const serverOptions: vscodeLanguageClient.ServerOptions = { diff --git a/magik-language-server/client-vscode/client/src/magik-session.ts b/magik-language-server/client-vscode/client/src/magik-session.ts index 7ae3f92d..41a1c4a0 100644 --- a/magik-language-server/client-vscode/client/src/magik-session.ts +++ b/magik-language-server/client-vscode/client/src/magik-session.ts @@ -91,7 +91,7 @@ class MagikSession implements vscode.Disposable { public dispose() { if (this._workdir !== null) { - fs.rmdirSync(this._workdir, {recursive: true}); + fs.rmSync(this._workdir, {recursive: true}); this._workdir = null; } } diff --git a/magik-language-server/client-vscode/client/src/test-provider.ts b/magik-language-server/client-vscode/client/src/test-provider.ts index 6c04f740..08e58c78 100644 --- a/magik-language-server/client-vscode/client/src/test-provider.ts +++ b/magik-language-server/client-vscode/client/src/test-provider.ts @@ -105,7 +105,7 @@ export class MagikTestProvider implements vscode.Disposable { dispose() { if (this.workdir !== null) { - fs.rmdirSync(this.workdir, {recursive: true}); + fs.rmSync(this.workdir, {recursive: true}); this.workdir = null; } } diff --git a/magik-language-server/client-vscode/package-lock.json b/magik-language-server/client-vscode/package-lock.json index 5d8511dd..89fbd437 100644 --- a/magik-language-server/client-vscode/package-lock.json +++ b/magik-language-server/client-vscode/package-lock.json @@ -1,6 +1,6 @@ { "name": "magik-language-server", - "version": "0.5.5-SNAPSHOT", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/magik-language-server/client-vscode/package.json b/magik-language-server/client-vscode/package.json index f38500eb..2cfd9ef5 100644 --- a/magik-language-server/client-vscode/package.json +++ b/magik-language-server/client-vscode/package.json @@ -3,7 +3,7 @@ "description": "Magik Language Server", "author": "Steven Looman", "license": "GPL-3.0-only", - "version": "0.5.5-SNAPSHOT", + "version": "0.6.0", "repository": { "type": "git", "url": "https://github.com/StevenLooman/magik-tools" @@ -110,6 +110,26 @@ "type": "string", "description": "Hostname or IP address to connect to.", "default": "127.0.0.1" + }, + "path_mapping": { + "default": [], + "label": "Path mapping", + "description": "Path mapping", + "type": "array", + "items": { + "label": "Path substitution", + "type": "object", + "properties": { + "from": { + "label": "From path", + "type": "string" + }, + "to": { + "label": "To path", + "type": "string" + } + } + } } } } @@ -207,17 +227,6 @@ "description": "Enable typing checks.", "type": "boolean", "default": false - }, - "magik.trace.server": { - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the Magik language server.", - "scope": "window" } } } diff --git a/magik-language-server/client-vscode/server/magik-debug-adapter-0.5.5-SNAPSHOT.jar b/magik-language-server/client-vscode/server/magik-debug-adapter-0.5.5-SNAPSHOT.jar deleted file mode 120000 index 1ec93567..00000000 --- a/magik-language-server/client-vscode/server/magik-debug-adapter-0.5.5-SNAPSHOT.jar +++ /dev/null @@ -1 +0,0 @@ -../../../magik-debug-adapter/target/magik-debug-adapter-0.5.5-SNAPSHOT.jar \ No newline at end of file diff --git a/magik-language-server/client-vscode/server/magik-debug-adapter-0.6.0.jar b/magik-language-server/client-vscode/server/magik-debug-adapter-0.6.0.jar new file mode 120000 index 00000000..52333995 --- /dev/null +++ b/magik-language-server/client-vscode/server/magik-debug-adapter-0.6.0.jar @@ -0,0 +1 @@ +../../../magik-debug-adapter/target/magik-debug-adapter-0.6.0.jar \ No newline at end of file diff --git a/magik-language-server/client-vscode/server/magik-language-server-0.5.5-SNAPSHOT.jar b/magik-language-server/client-vscode/server/magik-language-server-0.5.5-SNAPSHOT.jar deleted file mode 120000 index 2db0abe9..00000000 --- a/magik-language-server/client-vscode/server/magik-language-server-0.5.5-SNAPSHOT.jar +++ /dev/null @@ -1 +0,0 @@ -../../../magik-language-server/target/magik-language-server-0.5.5-SNAPSHOT.jar \ No newline at end of file diff --git a/magik-language-server/client-vscode/server/magik-language-server-0.6.0.jar b/magik-language-server/client-vscode/server/magik-language-server-0.6.0.jar new file mode 120000 index 00000000..816f2090 --- /dev/null +++ b/magik-language-server/client-vscode/server/magik-language-server-0.6.0.jar @@ -0,0 +1 @@ +../../../magik-language-server/target/magik-language-server-0.6.0.jar \ No newline at end of file diff --git a/magik-language-server/pom.xml b/magik-language-server/pom.xml index 66818103..e53b7ab4 100644 --- a/magik-language-server/pom.xml +++ b/magik-language-server/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-language-server diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Lsp4jConversion.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Lsp4jConversion.java index a0d2f359..1a5141d8 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Lsp4jConversion.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Lsp4jConversion.java @@ -15,7 +15,7 @@ private Lsp4jConversion() { /** * Convert a Position from LSP4J. * @param position Position to convert. - * @return Position in {{magik.analysis}}. + * @return Position in {@code magik.analysis}. */ public static Position positionFromLsp4j(final org.eclipse.lsp4j.Position position) { return new Position(position.getLine() + 1, position.getCharacter()); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikLanguageServer.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikLanguageServer.java index 3dea37eb..b54540f5 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikLanguageServer.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikLanguageServer.java @@ -11,10 +11,12 @@ import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.InitializedParams; import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.SetTraceParams; import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.lsp4j.services.NotebookDocumentService; import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.WorkspaceService; import org.slf4j.Logger; @@ -31,6 +33,7 @@ public class MagikLanguageServer implements LanguageServer, LanguageClientAware private final List workspaceFolders = new ArrayList<>(); private final MagikTextDocumentService magikTextDocumentService; private final MagikWorkspaceService magikWorkspaceService; + private final MagikNotebookDocumentService magikNotebookDocumentService; private LanguageClient languageClient; private MagikSettings settings = MagikSettings.DEFAULT; @@ -40,6 +43,7 @@ public class MagikLanguageServer implements LanguageServer, LanguageClientAware public MagikLanguageServer() { this.magikTextDocumentService = new MagikTextDocumentService(this, this.typeKeeper); this.magikWorkspaceService = new MagikWorkspaceService(this, this.typeKeeper); + this.magikNotebookDocumentService = new MagikNotebookDocumentService(this); } @SuppressWarnings("deprecation") @@ -72,11 +76,20 @@ public CompletableFuture initialize(final InitializeParams par final ServerCapabilities capabilities = new ServerCapabilities(); this.magikTextDocumentService.setCapabilities(capabilities); this.magikWorkspaceService.setCapabilities(capabilities); + this.magikNotebookDocumentService.setCapabilities(capabilities); return new InitializeResult(capabilities); }); } + @Override + public void setTrace(final SetTraceParams params) { + LOGGER.trace( + "trace, value: {}", + params.getValue()); + // Do nothing. + } + @Override public void initialized(final InitializedParams params) { LOGGER.trace("initialized"); @@ -85,7 +98,12 @@ public void initialized(final InitializedParams params) { @Override public CompletableFuture shutdown() { LOGGER.trace("shutdown"); - return CompletableFuture.supplyAsync(() -> null); + + return CompletableFuture.supplyAsync(() -> { + this.magikWorkspaceService.shutdown(); + + return null; + }); } @Override @@ -103,13 +121,18 @@ public WorkspaceService getWorkspaceService() { return this.magikWorkspaceService; } + @Override + public NotebookDocumentService getNotebookDocumentService() { + return this.magikNotebookDocumentService; + } + @Override public void connect(final LanguageClient newLanguageClient) { this.languageClient = newLanguageClient; } /** - * Get the {{LanguageClient}}. + * Get the {@link LanguageClient}. * @return Language client. */ public LanguageClient getLanguageClient() { @@ -117,8 +140,8 @@ public LanguageClient getLanguageClient() { } /** - * Get the {{WorkspaceFolder}}s. - * @return {{WorkspaceFolder}}s. + * Get the {@link WorkspaceFolder}s. + * @return {@link WorkspaceFolder}s. */ public List getWorkspaceFolders() { return Collections.unmodifiableList(this.workspaceFolders); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikNotebookDocumentService.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikNotebookDocumentService.java new file mode 100644 index 00000000..df02844e --- /dev/null +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikNotebookDocumentService.java @@ -0,0 +1,41 @@ +package nl.ramsolutions.sw.magik.languageserver; + +import org.eclipse.lsp4j.DidChangeNotebookDocumentParams; +import org.eclipse.lsp4j.DidCloseNotebookDocumentParams; +import org.eclipse.lsp4j.DidOpenNotebookDocumentParams; +import org.eclipse.lsp4j.DidSaveNotebookDocumentParams; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.services.NotebookDocumentService; + +/** + * Magik NotebookDocumentService. + */ +public class MagikNotebookDocumentService implements NotebookDocumentService { + + @SuppressWarnings("unused") + private final MagikLanguageServer magikLanguageServer; + + public MagikNotebookDocumentService(final MagikLanguageServer magikLanguageServer) { + this.magikLanguageServer = magikLanguageServer; + } + + @Override + public void didOpen(final DidOpenNotebookDocumentParams params) { + } + + @Override + public void didChange(final DidChangeNotebookDocumentParams params) { + } + + @Override + public void didSave(final DidSaveNotebookDocumentParams params) { + } + + @Override + public void didClose(final DidCloseNotebookDocumentParams params) { + } + + public void setCapabilities(final ServerCapabilities capabilities) { + } + +} diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikTextDocumentService.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikTextDocumentService.java index a6ceb0f9..7c2d4b39 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikTextDocumentService.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikTextDocumentService.java @@ -43,6 +43,7 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; import org.eclipse.lsp4j.PrepareRenameParams; import org.eclipse.lsp4j.PrepareRenameResult; import org.eclipse.lsp4j.PublishDiagnosticsParams; @@ -54,7 +55,6 @@ import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureHelpParams; -import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentItem; @@ -63,6 +63,7 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Either3; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.TextDocumentService; import org.slf4j.Logger; @@ -357,7 +358,8 @@ public CompletableFuture semanticTokensFull(final SemanticTokens } @Override - public CompletableFuture> prepareRename(final PrepareRenameParams params) { + public CompletableFuture> prepareRename( + PrepareRenameParams params) { final TextDocumentIdentifier textDocument = params.getTextDocument(); LOGGER.trace( "prepareRename, uri: {}, position: {},{}", @@ -381,8 +383,9 @@ public CompletableFuture rename(final RenameParams params) { return CompletableFuture.supplyAsync(() -> this.renameProvider.provideRename(magikFile, position, newName)); } + @SuppressWarnings("deprecation") @Override - public CompletableFuture>> documentSymbol( + public CompletableFuture>> documentSymbol( final DocumentSymbolParams params) { final TextDocumentIdentifier textDocument = params.getTextDocument(); LOGGER.trace("documentSymbol, uri: {}", textDocument.getUri()); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikWorkspaceService.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikWorkspaceService.java index 2efd85d0..ed69bc51 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikWorkspaceService.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/MagikWorkspaceService.java @@ -12,12 +12,12 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import javax.annotation.Nullable; +import nl.ramsolutions.sw.IgnoreHandler; import nl.ramsolutions.sw.magik.analysis.typing.ClassInfoTypeKeeperReader; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.JsonTypeKeeperReader; import nl.ramsolutions.sw.magik.analysis.typing.ReadOnlyTypeKeeperAdapter; -import nl.ramsolutions.sw.magik.languageserver.indexer.MagikIndexer; -import nl.ramsolutions.sw.magik.languageserver.indexer.MagikPreIndexer; +import nl.ramsolutions.sw.magik.analysis.typing.indexer.MagikIndexer; import nl.ramsolutions.sw.magik.languageserver.munit.MUnitTestItem; import nl.ramsolutions.sw.magik.languageserver.munit.MUnitTestItemProvider; import nl.ramsolutions.sw.magik.languageserver.symbol.SymbolProvider; @@ -27,11 +27,11 @@ import org.eclipse.lsp4j.FileEvent; import org.eclipse.lsp4j.ProgressParams; import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.WorkDoneProgressBegin; import org.eclipse.lsp4j.WorkDoneProgressCreateParams; import org.eclipse.lsp4j.WorkDoneProgressEnd; import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.WorkspaceSymbol; import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; @@ -57,7 +57,7 @@ public class MagikWorkspaceService implements WorkspaceService { /** * Constructor. * @param languageServer Owner language server. - * @param typeKeeper {{TypeKeeper}} used for type storage. + * @param typeKeeper {@link TypeKeeper} used for type storage. */ public MagikWorkspaceService(final MagikLanguageServer languageServer, final ITypeKeeper typeKeeper) { this.languageServer = languageServer; @@ -113,28 +113,13 @@ private void runIgnoreFilesIndexer() { } } - private void runPreIndexer() { - LOGGER.trace("Running MagikPreIndexer"); - for (final WorkspaceFolder workspaceFolder : this.languageServer.getWorkspaceFolders()) { - try { - LOGGER.debug("Running MagikPreIndexer from: {}", workspaceFolder.getUri()); - final MagikPreIndexer preIndexer = new MagikPreIndexer(this.typeKeeper); - final Stream indexableFiles = this.getIndexableFiles(workspaceFolder); - preIndexer.indexPaths(indexableFiles); - } catch (final IOException exception) { - LOGGER.error(exception.getMessage(), exception); - } - } - } - private void runIndexer() { LOGGER.trace("Running MagikIndexer"); for (final WorkspaceFolder workspaceFolder : this.languageServer.getWorkspaceFolders()) { try { LOGGER.debug("Running MagikIndexer from: {}", workspaceFolder.getUri()); - final MagikIndexer indexer = new MagikIndexer(this.typeKeeper); final Stream indexableFiles = this.getIndexableFiles(workspaceFolder); - indexer.indexPaths(indexableFiles); + this.magikIndexer.indexPaths(indexableFiles); } catch (final IOException exception) { LOGGER.error(exception.getMessage(), exception); } @@ -234,15 +219,18 @@ private void handleIgnoreFileChange(final FileEvent fileEvent) { } } + @SuppressWarnings("deprecation") @Override - public CompletableFuture> symbol(final WorkspaceSymbolParams params) { + public CompletableFuture< + Either, List> + > symbol(WorkspaceSymbolParams params) { final String query = params.getQuery(); LOGGER.trace("symbol, query: {}", query); return CompletableFuture.supplyAsync(() -> { - final List queryResults = this.symbolProvider.getSymbols(query); + final List queryResults = this.symbolProvider.getSymbols(query); LOGGER.debug("Symbols found for: '{}', count: {}", query, queryResults.size()); - return queryResults; + return Either.forRight(queryResults); }); } @@ -266,6 +254,17 @@ public CompletableFuture reIndex() { */ @JsonRequest(value = "custom/munit/getTestItems") public CompletableFuture> getTestItems() { + // TODO: Rewrite this to generic queries on types. Such as: + // - Get type by name + // - doc + // - location + // - parents + // - children + // - ... + // - methods? + // - Get methods from type name + // - Get method by name + // In fact, maybe we can use LSP typeHierarchy support? LOGGER.trace("munit/getTestItems"); return CompletableFuture.supplyAsync(this.testItemProvider::getTestItems); @@ -298,9 +297,6 @@ private void runIndexers() { // Index .magik-tools-ignore files. this.runIgnoreFilesIndexer(); - // Run pre-indexer. - this.runPreIndexer(); - // Run indexer. this.runIndexer(); } @@ -339,4 +335,9 @@ private void runIndexersInBackground() { }); } + public void shutdown() { + // TODO: Dump type database, and read it again when starting? + // Requires timestamping of definitions/files! + } + } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Main.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Main.java index dc9d96f9..4b546e0d 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Main.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/Main.java @@ -19,14 +19,14 @@ public final class Main { private static final Options OPTIONS; - private static final String OPTION_DEBUG = "debug"; + private static final Option OPTION_DEBUG = Option.builder() + .longOpt("debug") + .desc("Show debug messages") + .build(); static { OPTIONS = new Options(); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_DEBUG) - .desc("Show debug messages") - .build()); + OPTIONS.addOption(OPTION_DEBUG); } private Main() { diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/completion/CompletionProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/completion/CompletionProvider.java index 378f23ad..df34e612 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/completion/CompletionProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/completion/CompletionProvider.java @@ -23,8 +23,8 @@ import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; @@ -188,8 +188,8 @@ private List provideGlobalCompletion( final AstNode methodDefinitionNode = scopeNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); if (methodDefinitionNode != null) { final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(methodDefinitionNode); - final GlobalReference globalRef = helper.getTypeGlobalReference(); - final AbstractType type = typeKeeper.getType(globalRef); + final TypeString typeString = helper.getTypeString(); + final AbstractType type = typeKeeper.getType(typeString); type.getSlots().stream() .map(slot -> { final String slotName = slot.getName(); @@ -212,7 +212,7 @@ private List provideGlobalCompletion( .filter(type -> type.getFullName().indexOf(identifierPart) != -1) .map(type -> { final CompletionItem item = new CompletionItem(type.getFullName()); - item.setInsertText(type.getFullName()); // TODO: if visible from current package, don't prefix package. + item.setInsertText(type.getFullName()); item.setDetail(type.getFullName()); item.setDocumentation(type.getDoc()); item.setKind(CompletionItemKind.Class); @@ -234,8 +234,7 @@ private List provideMethodInvocationCompletion( final MagikTypedFile magikFile, final AstNode tokenNode, final String tokenValue) { // Reason (on newly parsed source, thus not from magikFile) and get type. final LocalTypeReasoner reasoner = new LocalTypeReasoner(magikFile); - final AstNode topNode = tokenNode.getFirstAncestor(MagikGrammar.MAGIK); - reasoner.run(topNode); + reasoner.run(); // Token --> // - parent: any --> parent: ATOM @@ -262,9 +261,9 @@ private List provideMethodInvocationCompletion( if (type == SelfType.INSTANCE) { final AstNode methodDefNode = tokenNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = helper.getTypeGlobalReference(); + final TypeString typeString = helper.getTypeString(); final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); - type = typeKeeper.getType(globalRef); + type = typeKeeper.getType(typeString); } // Convert all known methods to CompletionItems. @@ -334,9 +333,9 @@ private String[] cleanSource(final String source, final Position position) { } /** - * Provide keyword {{CompletionItem}}s. + * Provide keyword {@link CompletionItem}s. * - * @return {{CompletionItem}}s. + * @return {@link CompletionItem}s. */ private List provideKeywordCompletions() { return Arrays.stream(MagikKeyword.values()) @@ -350,10 +349,10 @@ private List provideKeywordCompletions() { } /** - * Get the current character at {{position}} in {{text}}. + * Get the current character at {@code position} in {@code text}. * @param text Text to use. * @param position Position to get character from. - * @return Character at {{position}}. + * @return Character at {@code position}. */ @CheckForNull private Character getCurrentChar(final String text, final Position position) { diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikLintDiagnosticsProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikLintDiagnosticsProvider.java index 3c270752..3d0219f7 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikLintDiagnosticsProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikLintDiagnosticsProvider.java @@ -57,10 +57,10 @@ public MagikLintDiagnosticsProvider(final @Nullable Path overrideConfigurationPa } /** - * Get {{Diagnostic}}s using {{MagikLint}}. + * Get {@link Diagnostic}s using {@link MagikLint}. * * @param magikFile Magik file. - * @return List with {{Diagnostic}}s. + * @return List with {@link Diagnostic}s. * @throws IOException - */ public List getDiagnostics(final MagikFile magikFile) throws IOException { diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikTypeDiagnosticsProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikTypeDiagnosticsProvider.java index db564460..82738745 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikTypeDiagnosticsProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/diagnostics/MagikTypeDiagnosticsProvider.java @@ -24,9 +24,9 @@ public class MagikTypeDiagnosticsProvider { static final Logger LOGGER = LoggerFactory.getLogger(MagikTypeDiagnosticsProvider.class); /** - * Get {{Diagnostic}}s for typing errors.. + * Get {@link Diagnostic}s for typing errors.. * @param magikFile Magik file. - * @return List with {{Diagnostic}}s. + * @return List with {@link Diagnostic}s. * @throws IOException - */ public List getDiagnostics(final MagikTypedFile magikFile) throws IOException { diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/documentsymbols/DocumentSymbolProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/documentsymbols/DocumentSymbolProvider.java index 6d1ff6c9..37d257e7 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/documentsymbols/DocumentSymbolProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/documentsymbols/DocumentSymbolProvider.java @@ -17,7 +17,6 @@ import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -35,11 +34,13 @@ public void setCapabilities(final ServerCapabilities capabilities) { } /** - * Provide {{DocumentSymbol}}s. + * Provide {@link DocumentSymbol}s. * @param magikFile Magik file to provide symbols for. - * @return {{DocumentSymbol}}s. + * @return {@link DocumentSymbol}s. */ - public List> provideDocumentSymbol(final MagikTypedFile magikFile) { + @SuppressWarnings("deprecation") + public List> provideDocumentSymbol( + final MagikTypedFile magikFile) { final DefinitionReader definitionReader = new DefinitionReader(); final AstNode topNode = magikFile.getTopNode(); definitionReader.walkAst(topNode); @@ -47,7 +48,7 @@ public List> provideDocumentSymbol(fin // Convert definitions to DocumentSymbols. return definitionReader.getDefinitions().stream() .map(this::convertDefinition) - .map(Either::forRight) + .map(Either::forRight) .collect(Collectors.toList()); } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/folding/FoldingRangeProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/folding/FoldingRangeProvider.java index b27c70c2..570fc101 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/folding/FoldingRangeProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/folding/FoldingRangeProvider.java @@ -24,7 +24,7 @@ public void setCapabilities(final ServerCapabilities capabilities) { /** * Provide folding ranges. * @param magikFile Magik file. - * @return {{FoldingRange}}s. + * @return {@link FoldingRange}s. */ public List provideFoldingRanges(final MagikTypedFile magikFile) { // Parse and reason magik. diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FinalNewlineStrategy.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FinalNewlineStrategy.java index d6d5f022..de82bda4 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FinalNewlineStrategy.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FinalNewlineStrategy.java @@ -2,7 +2,6 @@ import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -13,7 +12,7 @@ */ public class FinalNewlineStrategy extends FormattingStrategy { - private static final String EOL_TOKEN_VALUE = "\n"; // TODO: Extract this from AST. + private static final String EOL_TOKEN_VALUE = "\n"; FinalNewlineStrategy(final FormattingOptions options) { super(options); @@ -40,7 +39,7 @@ TextEdit walkWhitespaceToken(final Token token) { TextEdit walkCommentToken(final Token token) { TextEdit textEdit = null; if (this.lastToken != null - && this.lastToken.getType() == ExtendedTokenType.WHITESPACE) { + && this.lastToken.getType() == GenericTokenType.WHITESPACE) { textEdit = this.editToken(this.lastToken, ""); } return textEdit; diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingProvider.java index d97dc1db..e95aefac 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingProvider.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.MagikTypedFile; +import nl.ramsolutions.sw.magik.MagikFile; import nl.ramsolutions.sw.magik.api.MagikGrammar; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.ServerCapabilities; @@ -28,10 +28,10 @@ public void setCapabilities(final ServerCapabilities capabilities) { * * @param magikFile Magik file. * @param options Formatting options - * @return {{TextEdit}}s. + * @return {@link TextEdit}s. * @throws IOException - */ - public List provideFormatting(final MagikTypedFile magikFile, final FormattingOptions options) { + public List provideFormatting(final MagikFile magikFile, final FormattingOptions options) { final AstNode node = magikFile.getTopNode(); try { @@ -54,7 +54,7 @@ public List provideFormatting(final MagikTypedFile magikFile, final Fo * @param magikFile Magik file. * @return False if AST contains a SYNTAX_ERROR, true otherwise. */ - public boolean canFormat(MagikTypedFile magikFile) { + public boolean canFormat(final MagikFile magikFile) { final AstNode node = magikFile.getTopNode(); return node.getFirstDescendant(MagikGrammar.SYNTAX_ERROR) == null; } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingStrategy.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingStrategy.java index c1618236..fa211325 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingStrategy.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingStrategy.java @@ -4,7 +4,6 @@ import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; import javax.annotation.CheckForNull; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -64,7 +63,7 @@ public void walkPostNode(final AstNode node) { * @param token Token to set. */ void setLastToken(final Token token) { - if (token.getType() != ExtendedTokenType.WHITESPACE + if (token.getType() != GenericTokenType.WHITESPACE && token.getType() != GenericTokenType.EOL && token.getType() != GenericTokenType.EOF) { this.lastTextToken = token; @@ -82,7 +81,7 @@ protected TextEdit editWhitespaceBefore(final Token token) { // Ensure " " before token. TextEdit textEdit = null; if (this.lastToken == null - || this.lastToken.getType() != ExtendedTokenType.WHITESPACE) { + || this.lastToken.getType() != GenericTokenType.WHITESPACE) { textEdit = this.insertBeforeToken(token, " "); } return textEdit; @@ -97,7 +96,7 @@ protected TextEdit editNoWhitespaceBefore(final Token token) { // Ensure no whitespace before token. TextEdit textEdit = null; if (this.lastToken != null - && this.lastToken.getType() == ExtendedTokenType.WHITESPACE) { + && this.lastToken.getType() == GenericTokenType.WHITESPACE) { textEdit = this.editToken(this.lastToken, ""); } return textEdit; diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingWalker.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingWalker.java index 12f24314..76bc9f80 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingWalker.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/FormattingWalker.java @@ -9,19 +9,18 @@ import java.util.List; import java.util.stream.Stream; import nl.ramsolutions.sw.magik.analysis.AstWalker; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.TextEdit; /** - * Formatting AST walker which produces {{TextEdit}}s. + * Formatting AST walker which produces {@link TextEdit}s. */ public class FormattingWalker extends AstWalker { private final List textEdits = new ArrayList<>(); - private PragmaFormattingStrategy pragmaStrategy; - private StandardFormattingStrategy standardStrategy; - private FinalNewlineStrategy finalNewlineStrategy; + private final PragmaFormattingStrategy pragmaStrategy; + private final StandardFormattingStrategy standardStrategy; + private final FinalNewlineStrategy finalNewlineStrategy; private FormattingStrategy activeStrategy; /** @@ -85,7 +84,7 @@ protected void walkTrivia(final Trivia trivia) { } else if (trivia.isSkippedText()) { if (token.getType() == GenericTokenType.EOL) { this.walkEolToken(token); - } else if (token.getType() == ExtendedTokenType.WHITESPACE) { + } else if (token.getType() == GenericTokenType.WHITESPACE) { this.walkWhitespaceToken(token); } } @@ -121,7 +120,7 @@ private void walkCommentToken(final Token token) { final Token whitespaceToken = Token.builder(token) .setValueAndOriginalValue(trimmed) .setColumn(token.getColumn() + trimmedComment.length()) - .setType(ExtendedTokenType.WHITESPACE) + .setType(GenericTokenType.WHITESPACE) .build(); this.walkWhitespaceToken(whitespaceToken); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/StandardFormattingStrategy.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/StandardFormattingStrategy.java index 0f907c2a..bdf55a8a 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/StandardFormattingStrategy.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/formatting/StandardFormattingStrategy.java @@ -6,9 +6,9 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; +import nl.ramsolutions.sw.magik.api.MagikPunctuator; import org.eclipse.lsp4j.FormattingOptions; import org.eclipse.lsp4j.TextEdit; @@ -24,9 +24,9 @@ class StandardFormattingStrategy extends FormattingStrategy { // Tokens surround the AstNodes, e.g.: '(', pre PARAMETERS, post PARAMETERS, ')', or // '_method', '...', pre BODY, ..., post BODY, '# comment', '_endmethod'. private static final Set INDENT_INCREASE = Collections.unmodifiableSet(Set.of( - // MagikPunctuator.BRACE_L.getValue(), // MagikPunctuator.PAREN_L.getValue(), - // MagikPunctuator.SQUARE_L.getValue(), + MagikPunctuator.BRACE_L.getValue(), + MagikPunctuator.SQUARE_L.getValue(), MagikKeyword.PROC.getValue(), MagikKeyword.METHOD.getValue(), MagikKeyword.BLOCK.getValue(), @@ -43,9 +43,9 @@ class StandardFormattingStrategy extends FormattingStrategy { )); private static final Set INDENT_DECREASE = Collections.unmodifiableSet(Set.of( - // MagikPunctuator.BRACE_R.getValue(), // MagikPunctuator.PAREN_R.getValue(), - // MagikPunctuator.SQUARE_R.getValue(), + MagikPunctuator.BRACE_R.getValue(), + MagikPunctuator.SQUARE_R.getValue(), MagikKeyword.ENDPROC.getValue(), MagikKeyword.ENDMETHOD.getValue(), MagikKeyword.ENDBLOCK.getValue(), @@ -56,6 +56,7 @@ class StandardFormattingStrategy extends FormattingStrategy { MagikKeyword.ENDCATCH.getValue(), MagikKeyword.ENDLOCK.getValue(), MagikKeyword.ELSE.getValue(), + MagikKeyword.ELIF.getValue(), MagikKeyword.ENDIF.getValue(), MagikKeyword.ENDLOOP.getValue(), MagikKeyword.FINALLY.getValue() @@ -83,7 +84,7 @@ public TextEdit walkEolToken(final Token token) { TextEdit textEdit = null; if (this.options.isTrimTrailingWhitespace() && this.lastToken != null - && this.lastToken.getType() == ExtendedTokenType.WHITESPACE) { + && this.lastToken.getType() == GenericTokenType.WHITESPACE) { textEdit = this.editToken(this.lastToken, ""); } return textEdit; @@ -129,25 +130,34 @@ private boolean requireWhitespaceBefore(final Token token) { return this.lastTextToken != null // Don't string keywords: _if _not, _method obj, obj _andif.. && this.lastTextToken.getLine() == token.getLine() && (KEYWORDS.contains(this.lastTextToken.getOriginalValue().toLowerCase()) - || KEYWORDS.contains(tokenValue)) + || KEYWORDS.contains(tokenValue) + || "<<".equals(tokenValue) // Not really part of stringing keywords. + || "^<<".equals(tokenValue)) && !".".equals(tokenValue) && !",".equals(tokenValue) && !")".equals(tokenValue) && !"}".equals(tokenValue) - && !"]".equals(tokenValue); + && !"]".equals(tokenValue) + && !"(".equals(this.lastToken.getValue()) + && !"{".equals(this.lastToken.getValue()) + && !"[".equals(this.lastToken.getValue()); } private boolean requireNoWhitespaceBefore(final Token token) { final String tokenValue = token.getOriginalValue(); return token.getType() != GenericTokenType.COMMENT - && (")".equals(tokenValue) - || this.nodeIsSlot() - || this.lastTokenIs("@", "(", "{", "[") - || this.currentNode.is(MagikGrammar.ARGUMENTS) - || this.currentNode.is(MagikGrammar.PARAMETERS) - || this.nodeIsMethodDefinition() - || this.nodeIsInvocation() - || this.nodeIsUnaryExpression()); + && ( + ")".equals(tokenValue) + || "}".equals(tokenValue) + || "]".equals(tokenValue) + || ",".equals(tokenValue) + || this.nodeIsSlot() + || this.lastTokenIs("@", "(", "{", "[") + || this.currentNode.is(MagikGrammar.ARGUMENTS) + || this.currentNode.is(MagikGrammar.PARAMETERS) + || this.nodeIsMethodDefinition() + || this.nodeIsInvocation() + || this.nodeIsUnaryExpression()); } private boolean lastTokenIs(final String... values) { @@ -172,7 +182,10 @@ private boolean nodeIsSlot() { private boolean nodeIsMethodDefinition() { return this.currentNode.is(MagikGrammar.METHOD_DEFINITION) - || this.currentNode.getParent().is(MagikGrammar.METHOD_DEFINITION); + || this.currentNode.getParent().is( + MagikGrammar.METHOD_DEFINITION, + MagikGrammar.EXEMPLAR_NAME, + MagikGrammar.METHOD_NAME); } private boolean nodeIsInvocation() { @@ -189,9 +202,10 @@ public void walkPreNode(final AstNode node) { // Reset indenting. this.indent = 0; } else if (node.is( - MagikGrammar.PROCEDURE_INVOCATION, - MagikGrammar.METHOD_INVOCATION, - MagikGrammar.SIMPLE_VECTOR)) { + MagikGrammar.VARIABLE_DEFINITION, + MagikGrammar.VARIABLE_DEFINITION_MULTI, + MagikGrammar.PROCEDURE_INVOCATION, + MagikGrammar.METHOD_INVOCATION)) { this.indent += 1; } } @@ -200,9 +214,10 @@ public void walkPreNode(final AstNode node) { public void walkPostNode(final AstNode node) { if (this.isBinaryExpression(node) || node.is( + MagikGrammar.VARIABLE_DEFINITION, + MagikGrammar.VARIABLE_DEFINITION_MULTI, MagikGrammar.PROCEDURE_INVOCATION, - MagikGrammar.METHOD_INVOCATION, - MagikGrammar.SIMPLE_VECTOR)) { + MagikGrammar.METHOD_INVOCATION)) { // Count token nodes. // long count = node.getChildren().stream() // .filter(childNode -> childNode.isNot(MagikGrammar.values())) @@ -231,12 +246,12 @@ private boolean isBinaryExpression(final AstNode node) { private TextEdit ensureIndenting(final Token token) { // Indenting. if (this.indent == 0 - && this.lastToken.getType() != ExtendedTokenType.WHITESPACE) { + && this.lastToken.getType() != GenericTokenType.WHITESPACE) { return null; } final String indentText = this.indentText(); - if (this.lastToken.getType() != ExtendedTokenType.WHITESPACE) { + if (this.lastToken.getType() != GenericTokenType.WHITESPACE) { return this.insertBeforeToken(token, indentText); } else if (!this.lastToken.getOriginalValue().equals(indentText)) { return this.editToken(this.lastToken, indentText); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/hover/HoverProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/hover/HoverProvider.java index edc65b24..f62c3889 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/hover/HoverProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/hover/HoverProvider.java @@ -2,7 +2,7 @@ import com.sonar.sslr.api.AstNode; import java.util.Collection; -import java.util.List; +import java.util.Comparator; import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.MagikTypedFile; import nl.ramsolutions.sw.magik.analysis.AstQuery; @@ -12,10 +12,13 @@ import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.Package; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; @@ -61,23 +64,37 @@ public Hover provideHover(final MagikTypedFile magikFile, final Position positio LOGGER.debug("Hovering on: {}", hoveredNode.getTokenValue()); // See what we should provide a hover for. + final AstNode parentNode = hoveredNode.getParent(); final StringBuilder builder = new StringBuilder(); - if (hoveredNode.is(MagikGrammar.IDENTIFIER) - && hoveredNode.getParent().is(MagikGrammar.METHOD_DEFINITION)) { - // Method definition (exemplar or method name). + if (hoveredNode.is(MagikGrammar.PACKAGE_IDENTIFIER)) { + this.provideHoverPackage(magikFile, hoveredNode, builder); + } else if (parentNode != null + && parentNode.is(MagikGrammar.EXEMPLAR_NAME, MagikGrammar.METHOD_NAME)) { this.provideHoverMethodDefinition(magikFile, hoveredNode, builder); } else if (hoveredNode.is(MagikGrammar.IDENTIFIER) - && hoveredNode.getParent().is(MagikGrammar.METHOD_INVOCATION)) { + && parentNode != null + && parentNode.is(MagikGrammar.METHOD_INVOCATION)) { this.provideHoverMethodInvocation(magikFile, hoveredNode, builder); - } else if (hoveredNode.getParent().is(MagikGrammar.ATOM) - || hoveredNode.getParent().is(MagikGrammar.SLOT)) { + } else if (parentNode != null + && parentNode.is(MagikGrammar.ATOM) + || parentNode != null + && parentNode.is(MagikGrammar.SLOT)) { final AstNode atomNode = hoveredNode.getFirstAncestor(MagikGrammar.ATOM); this.provideHoverAtom(magikFile, atomNode, builder); - } else if (hoveredNode.getParent().is(MagikGrammar.PARAMETER)) { + } else if (parentNode != null + && parentNode.is(MagikGrammar.PARAMETER)) { final AstNode parameterNode = hoveredNode.getParent(); this.provideHoverAtom(magikFile, parameterNode, builder); - } else if (hoveredNode.getParent().is(MagikGrammar.VARIABLE_DEFINITION)) { + } else if (parentNode != null + && parentNode.is(MagikGrammar.VARIABLE_DEFINITION)) { this.provideHoverAtom(magikFile, hoveredNode, builder); + } else if (parentNode != null + && parentNode.is(MagikGrammar.EXPRESSION)) { + final AstNode expressionNode = hoveredNode.getParent(); + this.provideHoverExpression(magikFile, expressionNode, builder); + } else if (parentNode != null + && parentNode.is(MagikGrammar.CONDITION_NAME)) { + this.provideHoverCondition(magikFile, hoveredNode, builder); } final MarkupContent contents = new MarkupContent(MarkupKind.MARKDOWN, builder.toString()); @@ -86,6 +103,129 @@ public Hover provideHover(final MagikTypedFile magikFile, final Position positio return new Hover(contents, rangeLsp4j); } + private void provideHoverPackage( + final MagikTypedFile magikFile, + final AstNode hoveredNode, + final StringBuilder builder) { + final String packageName = hoveredNode.getTokenValue(); + final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); + final Package pakkage = typeKeeper.getPackage(packageName); + if (pakkage == null) { + return; + } + + // Name. + builder + .append("## ") + .append(pakkage.getName()) + .append(SECTION_END); + + // Doc. + final String doc = pakkage.getDoc(); + if (doc != null) { + final String docMd = doc.lines() + .map(String::trim) + .collect(Collectors.joining("\n\n")); + builder + .append(docMd) + .append(SECTION_END); + } + + // Uses. + this.buildUsesDoc(pakkage, builder, 0); + } + + private void buildUsesDoc(final Package pakkage, final StringBuilder builder, final int indent) { + if (indent == 0) { + builder + .append(pakkage.getName()) + .append("\n\n"); + } + + final String indentStr = "  ".repeat(indent); + final Comparator byName = Comparator.comparing(Package::getName); + pakkage.getUses().stream() + .sorted(byName) + .forEach(uses -> { + builder + .append(indentStr) + .append(" ↳ ") + .append(uses.getName()) + .append("\n\n"); + + this.buildUsesDoc(uses, builder, indent + 1); + }); + + if (indent == 0) { + builder.append(SECTION_END); + } + } + + private void provideHoverCondition( + final MagikTypedFile magikFile, + final AstNode hoveredNode, + final StringBuilder builder) { + final String conditionName = hoveredNode.getTokenValue(); + final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); + final Condition condition = typeKeeper.getCondition(conditionName); + if (condition == null) { + return; + } + + // Name. + builder + .append("## ") + .append(condition.getName()) + .append(SECTION_END); + + // Doc. + final String doc = condition.getDoc(); + if (doc != null) { + final String docMd = doc.lines() + .map(String::trim) + .collect(Collectors.joining("\n\n")); + builder + .append(docMd) + .append(SECTION_END); + } + + // Taxonomy. + builder.append("## Taxonomy: \n\n"); + Condition parentCondition = typeKeeper.getCondition(condition.getParent()); + while (parentCondition != null) { + final String parentName = parentCondition.getName(); + builder + .append(parentName) + .append("\n"); + + parentCondition = typeKeeper.getCondition(parentCondition.getParent()); + } + builder.append(SECTION_END); + + // Data names. + builder.append("## Data:\n"); + condition.getDataNameList().stream() + .forEach(dataName -> { + builder + .append("* ") + .append(dataName) + .append("\n"); + }); + } + + private void provideHoverExpression( + final MagikTypedFile magikFile, + final AstNode expressionNode, + final StringBuilder builder) { + final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + final ExpressionResult result = reasoner.getNodeTypeSilent(expressionNode); + if (result != null) { + LOGGER.debug("Providing hover for node: {}", expressionNode.getTokenValue()); + final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); + this.buildTypeDoc(typeKeeper, reasoner, expressionNode, builder); + } + } + /** * Provide hover for an atom. * @param magikFile Magik file. @@ -138,43 +278,43 @@ private void provideHoverMethodDefinition( final AstNode hoveredNode, final StringBuilder builder) { final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); - final AstNode methodDefNode = hoveredNode.getParent(); + final AstNode methodDefNode = hoveredNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(methodDefNode); - final List identifierNodes = methodDefNode.getChildren(MagikGrammar.IDENTIFIER); - if (hoveredNode == identifierNodes.get(0)) { + final AstNode exemplarNameNode = methodDefNode.getFirstChild(MagikGrammar.EXEMPLAR_NAME).getFirstChild(); + final AstNode methodNameNode = methodDefNode.getFirstChild(MagikGrammar.METHOD_NAME).getFirstChild(); + final TypeString typeString = methodDefHelper.getTypeString(); + if (hoveredNode == exemplarNameNode) { // Hovered over exemplar. - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); - LOGGER.debug("Providing hover for type: {}", globalRef); - this.buildTypeDoc(typeKeeper, globalRef, builder); - } else if (hoveredNode == identifierNodes.get(1)) { + LOGGER.debug("Providing hover for type: {}", typeString); + this.buildTypeDoc(typeKeeper, typeString, builder); + } else if (hoveredNode == methodNameNode) { // Hovered over method name. - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); final String methodName = methodDefHelper.getMethodName(); - LOGGER.debug("Providing hover for type: {}, method: {}", globalRef, methodName); - this.buildMethodDoc(typeKeeper, globalRef, methodName, builder); + LOGGER.debug("Providing hover for type: {}, method: {}", typeString, methodName); + this.buildMethodDoc(typeKeeper, typeString, methodName, builder); } } /** * Build hover text for type doc. * @param typeKeeper TypeKeeper to use. - * @param globalReference Global reference to type. - * @param builder {{StringBuilder}} to fill. + * @param typeString Global reference to type. + * @param builder {@link StringBuilder} to fill. */ private void buildTypeDoc( final ITypeKeeper typeKeeper, - final GlobalReference globalReference, + final TypeString typeString, final StringBuilder builder) { - final AbstractType type = typeKeeper.getType(globalReference); + final AbstractType type = typeKeeper.getType(typeString); this.buildTypeSignatureDoc(type, builder); } /** * Build hover text for type doc. * @param typeKeeper TypeKeeper. - * @param reasoner {{LocalTypeReasoner}} which has reasoned over the AST. - * @param node {{AstNode}} to get info from. - * @param builder {{StringBuilder}} to fill. + * @param reasoner {@link LocalTypeReasoner} which has reasoned over the AST. + * @param node {@link AstNode} to get info from. + * @param builder {@link StringBuilder} to fill. */ private void buildTypeDoc( final ITypeKeeper typeKeeper, @@ -193,8 +333,8 @@ private void buildTypeDoc( type = UndefinedType.INSTANCE; } else { final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); - type = typeKeeper.getType(globalRef); + final TypeString typeString = methodDefHelper.getTypeString(); + type = typeKeeper.getType(typeString); } } @@ -207,14 +347,14 @@ private void buildTypeDoc( * @param pakkage Name of package. * @param typeName Name of type. * @param methodName Name of method. - * @param builder {{StringBuilder}} to fill. + * @param builder {@link StringBuilder} to fill. */ private void buildMethodDoc( final ITypeKeeper typeKeeper, - final GlobalReference globalReference, + final TypeString typeString, final String methodName, final StringBuilder builder) { - final AbstractType type = typeKeeper.getType(globalReference); + final AbstractType type = typeKeeper.getType(typeString); // Get method info. final Collection methods = type.getMethods(methodName); @@ -232,7 +372,7 @@ private void buildMethodDoc( * @param reasoner LocalTypeReasoner. * @param node AstNode, METHOD_INVOCATION. * @param methodName Name of invoked method. - * @param builder {{StringBuilder}} to fill. + * @param builder {@link StringBuilder} to fill. */ private void buildMethodDoc( final ITypeKeeper typeKeeper, @@ -252,8 +392,8 @@ private void buildMethodDoc( type = UndefinedType.INSTANCE; } else { final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = helper.getTypeGlobalReference(); - type = typeKeeper.getType(globalRef); + final TypeString typeString = helper.getTypeString(); + type = typeKeeper.getType(typeString); } } @@ -268,6 +408,12 @@ private void buildMethodDoc( } private void buildInheritanceDoc(final AbstractType type, final StringBuilder builder, final int indent) { + if (indent == 0) { + builder + .append(type.getFullName()) + .append("\n\n"); + } + final String indentStr = "  ".repeat(indent); type.getParents().forEach(parentType -> { builder @@ -278,6 +424,7 @@ private void buildInheritanceDoc(final AbstractType type, final StringBuilder bu this.buildInheritanceDoc(parentType, builder, indent + 1); }); + if (indent == 0) { builder.append(SECTION_END); } @@ -322,9 +469,6 @@ private void buildTypeSignatureDoc(final AbstractType type, final StringBuilder .append(type.getFullName()) .append(SECTION_END); - // Inheritance. - this.buildInheritanceDoc(type, builder, 0); - // Type doc. final String typeDoc = type.getDoc(); if (typeDoc != null) { @@ -335,6 +479,14 @@ private void buildTypeSignatureDoc(final AbstractType type, final StringBuilder .append(typeDocMd) .append(SECTION_END); } + + // Inheritance. + if (type instanceof CombinedType) { + final CombinedType combinedType = (CombinedType) type; + combinedType.getTypes().forEach(cType -> this.buildInheritanceDoc(cType, builder, 0)); + } else { + this.buildInheritanceDoc(type, builder, 0); + } } } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/implementation/ImplementationProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/implementation/ImplementationProvider.java index d76942f1..e89e8c1a 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/implementation/ImplementationProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/implementation/ImplementationProvider.java @@ -2,18 +2,22 @@ import com.sonar.sslr.api.AstNode; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; import nl.ramsolutions.sw.magik.MagikTypedFile; import nl.ramsolutions.sw.magik.analysis.AstQuery; +import nl.ramsolutions.sw.magik.analysis.Location; import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; @@ -38,7 +42,7 @@ public void setCapabilities(final ServerCapabilities capabilities) { } /** - * Provide implementations for {{position}} in {{path}}. + * Provide implementations for {@code position} in {@code path}. * @param magikFile Magik file. * @param position Location in file. * @return List of Locations for implementation. @@ -59,6 +63,17 @@ public List provideImplementations( final List methodLocations = this.implementationsForMethodInvocation(typeKeeper, reasoner, currentNode); locations.addAll(methodLocations); + break; + } else if (currentNode.is(MagikGrammar.ATOM)) { + final List methodLocations = + this.implentationsForAtom(typeKeeper, reasoner, currentNode); + locations.addAll(methodLocations); + break; + } else if (currentNode.is(MagikGrammar.CONDITION_NAME)) { + final List methodLocations = + this.implentationsForCondition(typeKeeper, reasoner, currentNode); + locations.addAll(methodLocations); + break; } currentNode = currentNode.getParent(); @@ -67,6 +82,56 @@ public List provideImplementations( return locations; } + private List implentationsForCondition( + final ITypeKeeper typeKeeper, final LocalTypeReasoner reasoner, final AstNode currentNode) { + final String conditionName = currentNode.getTokenValue(); + if (conditionName == null) { + return Collections.emptyList(); + } + + final Condition condition = typeKeeper.getCondition(conditionName); + if (condition == null) { + return Collections.emptyList(); + } + + final Location location = condition.getLocation(); + if (location == null) { + return Collections.emptyList(); + } + + return List.of(Lsp4jConversion.locationToLsp4j(location)); + } + + private List implentationsForAtom( + final ITypeKeeper typeKeeper, final LocalTypeReasoner reasoner, final AstNode currentNode) { + final ExpressionResult result = reasoner.getNodeType(currentNode); + final AbstractType reasonedType = result.get(0, null); + if (reasonedType == null) { + return Collections.emptyList(); + } + + final AbstractType type; + if (reasonedType == SelfType.INSTANCE) { + final AstNode methodDefNode = currentNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); + final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(methodDefNode); + final TypeString typeString = methodDefHelper.getTypeString(); + type = typeKeeper.getType(typeString); + } else { + type = reasonedType; + } + + if (type == UndefinedType.INSTANCE) { + return Collections.emptyList(); + } + + final Location location = type.getLocation(); + if (location == null) { + return Collections.emptyList(); + } + + return List.of(Lsp4jConversion.locationToLsp4j(location)); + } + private List implementationsForMethodInvocation( final ITypeKeeper typeKeeper, final LocalTypeReasoner reasoner, final AstNode currentNode) { final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(currentNode); @@ -75,7 +140,7 @@ private List implementationsForMethodInvocation( final AstNode previousSiblingNode = currentNode.getPreviousSibling(); final ExpressionResult result = reasoner.getNodeType(previousSiblingNode); - final AbstractType unsetType = typeKeeper.getType(GlobalReference.of("sw:unset")); + final AbstractType unsetType = typeKeeper.getType(TypeString.of("sw:unset")); AbstractType type = result.get(0, unsetType); final List locations = new ArrayList<>(); if (type == UndefinedType.INSTANCE) { @@ -90,12 +155,13 @@ private List implementationsForMethodInvocation( if (type == SelfType.INSTANCE) { final AstNode methodDefNode = currentNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); - type = typeKeeper.getType(globalRef); + final TypeString typeString = methodDefHelper.getTypeString(); + type = typeKeeper.getType(typeString); } LOGGER.debug("Finding implementations for type:, {}, method: {}", type.getFullName(), methodName); type.getMethods(methodName).stream() .map(Method::getLocation) + .filter(Objects::nonNull) .map(Lsp4jConversion::locationToLsp4j) .forEach(locations::add); } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikPreIndexer.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikPreIndexer.java deleted file mode 100644 index 6cd60647..00000000 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikPreIndexer.java +++ /dev/null @@ -1,188 +0,0 @@ -package nl.ramsolutions.sw.magik.languageserver.indexer; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.stream.Stream; -import nl.ramsolutions.sw.magik.MagikFile; -import nl.ramsolutions.sw.magik.analysis.definitions.Definition; -import nl.ramsolutions.sw.magik.analysis.definitions.EnumerationDefinition; -import nl.ramsolutions.sw.magik.analysis.definitions.IndexedExemplarDefinition; -import nl.ramsolutions.sw.magik.analysis.definitions.MixinDefinition; -import nl.ramsolutions.sw.magik.analysis.definitions.PackageDefinition; -import nl.ramsolutions.sw.magik.analysis.definitions.SlottedExemplarDefinition; -import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IndexedType; -import nl.ramsolutions.sw.magik.analysis.typing.types.IntrinsicType; -import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; -import nl.ramsolutions.sw.magik.analysis.typing.types.Package; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Pre-indexer step to create initial packages and exemplars/mixins. - * - *

- * The {{MagikIndexer}} does the real indexing, this pre-indexing is required to not be bound - * to the load order of the session, but still get the correct inheritance hierarchy. - *

- * - *

- * Does a two-step part to correctly build the hierachy: - * 1. Gather all types (exemplars, mixins) - * 2. Builds the complete type hierarchy - *

- */ -public class MagikPreIndexer { - - private static final Logger LOGGER = LoggerFactory.getLogger(MagikPreIndexer.class); - - private static final String DEF_MIXIN = "def_mixin"; - private static final String DEF_INDEXED_EXEMPLAR = "def_indexed_exemplar"; - private static final String DEF_SLOTTED_EXEMPLAR = "def_slotted_exemplar"; - private static final String DEF_ENUMERATION = "def_enumeration"; - private static final String DEF_PACKAGE = "def_package"; - - private final ITypeKeeper typeKeeper; - - public MagikPreIndexer(ITypeKeeper typeKeeper) { - this.typeKeeper = typeKeeper; - } - - /** - * Index all magik file(s). - * @param paths Paths to index. - * @throws IOException - - */ - public void indexPaths(Stream paths) throws IOException { - paths.forEach(this::indexPath); - } - - /** - * Index a single magik file. - * @param path Path to magik file. - */ - @SuppressWarnings("checkstyle:IllegalCatch") - public void indexPath(Path path) { - LOGGER.debug("Scanning file: {}", path); - try { - final MagikFile magikFile = new MagikFile(path); - - // Ensure its worth parsing this file. - final String code = magikFile.getSource(); - if (!this.sourceContainsDefinition(code)) { - return; - } - - magikFile.getDefinitions() - .forEach(this::handleDefinition); - } catch (IOException exception) { - LOGGER.error(exception.getMessage(), exception); - } catch (Exception exception) { - LOGGER.error("Error pre-indexing file: " + path, exception); - } - } - - private boolean sourceContainsDefinition(final String code) { - final String codeLowered = code.toLowerCase(); - return codeLowered.contains(DEF_PACKAGE) - || codeLowered.contains(DEF_ENUMERATION) - || codeLowered.contains(DEF_SLOTTED_EXEMPLAR) - || codeLowered.contains(DEF_INDEXED_EXEMPLAR) - || codeLowered.contains(DEF_MIXIN); - } - - private void handleDefinition(final Definition definition) { - if (definition instanceof PackageDefinition) { - final PackageDefinition packageDefinition = (PackageDefinition) definition; - this.handleDefinition(packageDefinition); - } else if (definition instanceof IndexedExemplarDefinition) { - final IndexedExemplarDefinition indexedExemplarDefinition = (IndexedExemplarDefinition) definition; - this.handleDefinition(indexedExemplarDefinition); - } else if (definition instanceof EnumerationDefinition) { - final EnumerationDefinition enumerationDefinition = (EnumerationDefinition) definition; - this.handleDefinition(enumerationDefinition); - } else if (definition instanceof SlottedExemplarDefinition) { - final SlottedExemplarDefinition slottedExemplarDefinition = (SlottedExemplarDefinition) definition; - this.handleDefinition(slottedExemplarDefinition); - } else if (definition instanceof MixinDefinition) { - final MixinDefinition mixinDefinition = (MixinDefinition) definition; - this.handleDefinition(mixinDefinition); - } - } - - private void handleDefinition(final PackageDefinition definition) { - final String pakkageName = definition.getName(); - final Package pakkage; - if (this.typeKeeper.hasPackage(pakkageName)) { - pakkage = this.typeKeeper.getPackage(pakkageName); - } else { - pakkage = new Package(pakkageName); - this.typeKeeper.addPackage(pakkage); - } - - definition.getUses().stream() - .forEach(use -> { - final Package usePakkage; - if (!this.typeKeeper.hasPackage(use)) { - usePakkage = new Package(use); - this.typeKeeper.addPackage(usePakkage); - } else { - usePakkage = this.typeKeeper.getPackage(use); - } - - pakkage.addUse(usePakkage); - }); - - LOGGER.debug("Indexed package: {}", pakkage); - } - - private void handleDefinition(final IndexedExemplarDefinition definition) { - final GlobalReference globalRef = definition.getGlobalReference(); - this.ensurePackage(globalRef); - - final MagikType magikType = new IndexedType(globalRef); - this.typeKeeper.addType(magikType); - LOGGER.debug("Indexed indexed exemplar: {}", magikType); - } - - private void handleDefinition(final EnumerationDefinition definition) { - final GlobalReference globalRef = definition.getGlobalReference(); - this.ensurePackage(globalRef); - - final MagikType magikType = new SlottedType(globalRef); - this.typeKeeper.addType(magikType); - LOGGER.debug("Indexed enumeration: {}", magikType); - } - - private void handleDefinition(final SlottedExemplarDefinition definition) { - final GlobalReference globalRef = definition.getGlobalReference(); - this.ensurePackage(globalRef); - - final MagikType magikType = new SlottedType(globalRef); - this.typeKeeper.addType(magikType); - LOGGER.debug("Indexed slotted exemplar: {}", magikType); - } - - private void handleDefinition(final MixinDefinition definition) { - final GlobalReference globalRef = definition.getGlobalReference(); - this.ensurePackage(globalRef); - - final MagikType magikType = new IntrinsicType(globalRef); - this.typeKeeper.addType(magikType); - LOGGER.debug("Indexed mixin: {}", magikType); - } - - private Package ensurePackage(final GlobalReference globalReference) { - final String pakkageName = globalReference.getPakkage(); - if (!this.typeKeeper.hasPackage(pakkageName)) { - final Package pakkage = new Package(pakkageName); - this.typeKeeper.addPackage(pakkage); - LOGGER.debug("Indexed package: {}", pakkage); - } - - return this.typeKeeper.getPackage(pakkageName); - } - -} diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/munit/MUnitTestItemProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/munit/MUnitTestItemProvider.java index 5e8ee990..b7b9f2d4 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/munit/MUnitTestItemProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/munit/MUnitTestItemProvider.java @@ -15,20 +15,20 @@ import nl.ramsolutions.sw.magik.analysis.Location; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; import org.eclipse.lsp4j.ServerCapabilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * MUnit {{@code TestItem}} provider. + * MUnit {@code TestItem} provider. */ public class MUnitTestItemProvider { private static final Logger LOGGER = LoggerFactory.getLogger(MUnitTestItemProvider.class); - private static final GlobalReference MUNIT_TEST_CASE_EXEMPLAR_NAME = GlobalReference.of("sw:test_case"); + private static final TypeString MUNIT_TEST_CASE_EXEMPLAR_NAME = TypeString.of("sw:test_case"); private static final String MUNIT_TEST_METHOD_PREFIX = "test"; private final ITypeKeeper typeKeeper; @@ -46,7 +46,7 @@ public void setCapabilities(final ServerCapabilities capabilities) { } /** - * Get {{@Code TestItem}}s. + * Get {@Link TestItem}s. * *

* A hierarchy per product is provided: diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/references/ReferencesProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/references/ReferencesProvider.java index 714d53d8..38ae479a 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/references/ReferencesProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/references/ReferencesProvider.java @@ -1,9 +1,13 @@ package nl.ramsolutions.sw.magik.languageserver.references; import com.sonar.sslr.api.AstNode; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.MagikTypedFile; import nl.ramsolutions.sw.magik.analysis.AstQuery; @@ -12,8 +16,8 @@ import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; @@ -59,73 +63,88 @@ public List provideReferences(final MagikTypedFile magikFile, final Po final AstNode wantedNode = currentNode.getFirstAncestor( MagikGrammar.METHOD_INVOCATION, - MagikGrammar.METHOD_DEFINITION); + MagikGrammar.METHOD_DEFINITION, + MagikGrammar.ATOM, + MagikGrammar.CONDITION_NAME); LOGGER.trace("Wanted node: {}", wantedNode); + final PackageNodeHelper packageHelper = new PackageNodeHelper(wantedNode); if (wantedNode == null) { return Collections.emptyList(); } else if (wantedNode.is(MagikGrammar.METHOD_INVOCATION)) { final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(wantedNode); final String methodName = helper.getMethodName(); LOGGER.debug("Getting references to method: {}", methodName); - return this.referencesToMethod(typeKeeper, methodName); + return this.referencesToMethod(typeKeeper, UndefinedType.SERIALIZED_NAME, methodName); } else if (wantedNode.is(MagikGrammar.METHOD_DEFINITION)) { final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(wantedNode); - final PackageNodeHelper packageHelper = new PackageNodeHelper(wantedNode); final String identifier = currentNode.getTokenValue(); // Name of the method. - if (this.nodeIsMethodDefinitionMethodName(currentNode, wantedNode)) { - String methodName = helper.getMethodName(); + if (currentNode.getFirstAncestor(MagikGrammar.METHOD_NAME) != null) { + final String methodName = helper.getMethodName(); LOGGER.debug("Getting references to method: {}", methodName); - return this.referencesToMethod(typeKeeper, methodName); - } else if (this.nodeIsMethodDefinitionExemplarName(currentNode, wantedNode)) { + return this.referencesToMethod(typeKeeper, UndefinedType.SERIALIZED_NAME, methodName); + } else if (currentNode.getFirstAncestor(MagikGrammar.EXEMPLAR_NAME) != null) { // Must be the exemplar name. final String pakkage = packageHelper.getCurrentPackage(); - final GlobalReference globalRef = GlobalReference.of(pakkage, identifier); - final AbstractType type = magikFile.getTypeKeeper().getType(globalRef); + final TypeString typeString = TypeString.of(identifier, pakkage); + final AbstractType type = typeKeeper.getType(typeString); if (type != UndefinedType.INSTANCE) { final String typeName = type.getFullName(); LOGGER.debug("Getting references to type: {}", typeName); return this.referencesToType(typeKeeper, typeName); } - } else { - // A random identifier, regard it as a type. - final String pakkage = packageHelper.getCurrentPackage(); - final GlobalReference globalRef = GlobalReference.of(pakkage, identifier); - final AbstractType type = magikFile.getTypeKeeper().getType(globalRef); - if (type != UndefinedType.INSTANCE) { - final String typeName = type.getFullName(); - LOGGER.debug("Getting references to method: {}", typeName); - return this.referencesToType(typeKeeper, typeName); - } } + } else if (wantedNode.is(MagikGrammar.ATOM) + && wantedNode.getFirstChild().is(MagikGrammar.IDENTIFIER)) { + // A random identifier, regard it as a type. + final String pakkage = packageHelper.getCurrentPackage(); + final String identifier = currentNode.getTokenValue(); + final TypeString typeString = TypeString.of(identifier, pakkage); + final AbstractType type = typeKeeper.getType(typeString); + if (type != UndefinedType.INSTANCE) { + final String typeName = type.getFullName(); + LOGGER.debug("Getting references to type: {}", typeName); + return this.referencesToType(typeKeeper, typeName); + } + } else if (wantedNode.is(MagikGrammar.CONDITION_NAME)) { + final String conditionName = currentNode.getTokenValue(); + LOGGER.debug("Getting references to condition: {}", conditionName); + return this.referencesToCondition(typeKeeper, conditionName); } return Collections.emptyList(); } - private boolean nodeIsMethodDefinitionMethodName(final AstNode currentNode, final AstNode methodDefinitionNode) { - // TODO: This does not support [] methods. - final List identifierNodes = methodDefinitionNode.getChildren(MagikGrammar.IDENTIFIER); - final AstNode methodNameNode = identifierNodes.get(1); - return methodNameNode.getToken() == currentNode.getToken(); - } - - private boolean nodeIsMethodDefinitionExemplarName(final AstNode currentNode, final AstNode methodDefinitionNode) { - final List identifierNodes = methodDefinitionNode.getChildren(MagikGrammar.IDENTIFIER); - final AstNode exemplarNameNode = identifierNodes.get(0); - return exemplarNameNode.getToken() == currentNode.getToken(); - } - - private List referencesToMethod(final ITypeKeeper typeKeeper, final String methodName) { + private List referencesToMethod( + final ITypeKeeper typeKeeper, final String typeName, final String methodName) { LOGGER.debug("Finding references to method: {}", methodName); + // Build set of types which may contain this method: type + ancestors. + final AbstractType type = + typeName.equals(UndefinedType.SERIALIZED_NAME) + ? UndefinedType.INSTANCE + : typeKeeper.getType(TypeString.of(typeName)); + final Set wantedTypes = new HashSet<>(); + wantedTypes.add(UndefinedType.INSTANCE); // For unreasoned/undetermined calls. + wantedTypes.add(type); + wantedTypes.addAll(type.getAncestors()); + + final Collection wantedMethodUsages = wantedTypes.stream() + .map(wantedType -> { + final String wantedTypeName = wantedType.getFullName(); + final TypeString wantedTypeRef = TypeString.of(wantedTypeName); + return new Method.MethodUsage(wantedTypeRef, methodName); + }) + .collect(Collectors.toSet()); + final Predicate filterPredicate = methodUsage -> wantedMethodUsages.contains(methodUsage); + // Find references. return typeKeeper.getTypes().stream() - .flatMap(type -> type.getMethods().stream()) - .filter(method -> method.getCalledMethods().contains(methodName)) - // TODO: can't we get the location to the usage? - .map(Method::getLocation) + .flatMap(type_ -> type_.getMethods().stream()) + .flatMap(method -> method.getMethodUsages().stream()) + .filter(filterPredicate::test) + .map(Method.MethodUsage::getLocation) .filter(Objects::nonNull) .map(Lsp4jConversion::locationToLsp4j) .collect(Collectors.toList()); @@ -134,15 +153,41 @@ private List referencesToMethod(final ITypeKeeper typeKeeper, final St private List referencesToType(final ITypeKeeper typeKeeper, final String typeName) { LOGGER.debug("Finding references to type: {}", typeName); + final AbstractType type = typeKeeper.getType(TypeString.of(typeName)); + final Set wantedTypes = new HashSet<>(); + wantedTypes.add(type); + // wantedTypes.addAll(type.getAncestors()); // TODO: Ancestors or descendants? + + final Collection wantedTypeUsages = wantedTypes.stream() + .map(wantedType -> { + final String wantedTypeName = wantedType.getFullName(); + final TypeString wantedTypeRef = TypeString.of(wantedTypeName); + return new Method.GlobalUsage(wantedTypeRef, null); + }) + .collect(Collectors.toSet()); + final Predicate filterPredicate = typeUsage -> wantedTypeUsages.contains(typeUsage); + // Find references. return typeKeeper.getTypes().stream() - .flatMap(type -> type.getMethods().stream()) - .filter(method -> method.getUsedTypes().contains(typeName)) - // TODO: can't we get the location to the usage? - .map(Method::getLocation) + .flatMap(type_ -> type_.getMethods().stream()) + .flatMap(method -> method.getTypeUsages().stream()) + .filter(filterPredicate::test) + .map(Method.GlobalUsage::getLocation) .filter(Objects::nonNull) .map(Lsp4jConversion::locationToLsp4j) .collect(Collectors.toList()); } + private List referencesToCondition(final ITypeKeeper typeKeeper, final String conditionName) { + LOGGER.debug("Finding references to condition: {}", conditionName); + + return typeKeeper.getTypes().stream() + .flatMap(type -> type.getLocalMethods().stream()) + .flatMap(method -> method.getConditionUsages().stream()) + .filter(conditionUsage -> conditionUsage.getConditionName().equals(conditionName)) + .map(Method.ConditionUsage::getLocation) + .map(Lsp4jConversion::locationToLsp4j) + .collect(Collectors.toList()); + } + } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/rename/RenameProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/rename/RenameProvider.java index b69c314c..40dd3b29 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/rename/RenameProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/rename/RenameProvider.java @@ -15,12 +15,13 @@ import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; import org.eclipse.lsp4j.PrepareRenameResult; import org.eclipse.lsp4j.RenameOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Either3; /** * Rename provider. @@ -43,7 +44,7 @@ public void setCapabilities(final ServerCapabilities capabilities) { * @param position Position in magik source. * @return Prepare rename or null if no rename possible. */ - public Either providePrepareRename( + public Either3 providePrepareRename( final MagikTypedFile magikFile, final Position position) { // Parse magik. final AstNode topNode = magikFile.getTopNode(); @@ -70,7 +71,7 @@ public Either providePrepareRename final org.eclipse.lsp4j.Range rangeLsp4j = Lsp4jConversion.rangeToLsp4j(range); final String identifier = node.getTokenOriginalValue(); final PrepareRenameResult result = new PrepareRenameResult(rangeLsp4j, identifier); - return Either.forRight(result); + return Either3.forSecond(result); } /** @@ -125,7 +126,7 @@ private ScopeEntry findScopeEntry(final MagikTypedFile magikFile, final AstNode return null; } - final String identifier = node.getTokenOriginalValue(); + final String identifier = node.getTokenValue(); return scope.getScopeEntry(identifier); } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenProvider.java index d36e8e78..55b15857 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenProvider.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.languageserver.semantictokens; import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; import java.io.IOException; import java.net.URI; @@ -11,7 +12,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import nl.ramsolutions.sw.magik.MagikTypedFile; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import org.eclipse.lsp4j.DocumentFilter; import org.eclipse.lsp4j.SemanticTokens; import org.eclipse.lsp4j.SemanticTokensLegend; @@ -84,7 +84,7 @@ private SemanticToken createStartSemanticToken() { .setLine(1) .setColumn(0) .setValueAndOriginalValue("") - .setType(ExtendedTokenType.OTHER) + .setType(GenericTokenType.UNKNOWN_CHAR) .setURI(URI.create("magik://dummy")) .build(); return new SemanticToken(startToken, SemanticToken.Type.CLASS, Collections.emptySet()); diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenWalker.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenWalker.java index 474ccdb9..62f7b2f6 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenWalker.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/semantictokens/SemanticTokenWalker.java @@ -18,8 +18,8 @@ import nl.ramsolutions.sw.magik.analysis.scope.Scope; import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; import nl.ramsolutions.sw.magik.api.MagikOperator; @@ -163,14 +163,14 @@ private void walkCommentToken(final Token token) { NewDocGrammar.TYPE_NAME, NewDocGrammar.TYPE_CLONE, NewDocGrammar.TYPE_SELF); typeNodes.forEach(typeTypeNode -> { final String identifier = typeTypeNode.getTokenValue(); - final GlobalReference globalRef = identifier.indexOf(':') != -1 - ? GlobalReference.of(identifier) - : GlobalReference.of(this.currentPakkage, identifier); + final TypeString typeString = identifier.indexOf(':') != -1 + ? TypeString.of(identifier) + : TypeString.of(identifier, this.currentPakkage); if (typeTypeNode.is(NewDocGrammar.TYPE_CLONE, NewDocGrammar.TYPE_SELF)) { final Set constModifier = Set.of(SemanticToken.Modifier.DOCUMENTATION, SemanticToken.Modifier.READONLY); this.addSemanticToken(typeTypeNode, SemanticToken.Type.CLASS, constModifier); - } else if (this.isKnownType(globalRef)) { + } else if (this.isKnownType(typeString)) { this.addSemanticToken(typeTypeNode, SemanticToken.Type.CLASS, docModifier); } }); @@ -242,8 +242,12 @@ protected void walkPostIdentifier(final AstNode node) { return; } - if (parentNode.is(MagikGrammar.METHOD_DEFINITION)) { - this.walkPostIdentifierMethodDefintion(node); + if (parentNode.is(MagikGrammar.EXEMPLAR_NAME)) { + this.addSemanticToken(node, SemanticToken.Type.CLASS); + } else if (parentNode.is(MagikGrammar.METHOD_NAME)) { + this.addSemanticToken(node, SemanticToken.Type.METHOD); + } else if (parentNode.is(MagikGrammar.CONDITION_NAME)) { + this.addSemanticToken(node, SemanticToken.Type.CLASS); } else if (parentNode.is(MagikGrammar.ATOM) && !this.isPartOfProcedureInvocation(node)) { this.walkPostIdentifierAtom(node); @@ -283,19 +287,6 @@ private void walkPostIdentifierFor(final AstNode node) { this.addSemanticToken(node, SemanticToken.Type.VARIABLE); } - private void walkPostIdentifierMethodDefintion(final AstNode node) { - final AstNode methodDefinitionNode = node.getParent(); - final List identifierNodes = methodDefinitionNode.getChildren(MagikGrammar.IDENTIFIER); - final int index = identifierNodes.indexOf(node); - if (index == 0) { - final AstNode typeIdentifierNode = identifierNodes.get(0); - this.addSemanticToken(typeIdentifierNode, SemanticToken.Type.CLASS); - } else if (index == 1) { - final AstNode methodIdentifierNode = identifierNodes.get(1); - this.addSemanticToken(methodIdentifierNode, SemanticToken.Type.METHOD); - } - } - private void walkPostIdentifierAtom(final AstNode node) { final GlobalScope globalScope = this.magikFile.getGlobalScope(); final Scope scope = globalScope.getScopeForNode(node); @@ -321,10 +312,8 @@ private void walkPostIdentifierAtom(final AstNode node) { case GLOBAL: case DYNAMIC: - final GlobalReference globalRef = identifier.indexOf(':') != -1 - ? GlobalReference.of(identifier) - : GlobalReference.of(this.currentPakkage, identifier); - if (this.isKnownType(globalRef)) { + final TypeString typeString = TypeString.of(identifier, this.currentPakkage); + if (this.isKnownType(typeString)) { this.addSemanticToken( node, SemanticToken.Type.CLASS, Set.of(SemanticToken.Modifier.VARIABLE_GLOBAL)); } else { @@ -362,9 +351,9 @@ protected void walkPostPackageSpecification(final AstNode node) { this.currentPakkage = helper.getCurrentPackage(); } - private boolean isKnownType(final GlobalReference globalReference) { + private boolean isKnownType(final TypeString typeString) { final ITypeKeeper typeKeeper = this.magikFile.getTypeKeeper(); - return typeKeeper.getType(globalReference) instanceof MagikType; + return typeKeeper.getType(typeString) instanceof MagikType; } } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/signaturehelp/SignatureHelpProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/signaturehelp/SignatureHelpProvider.java index abb39887..1b6bd4d3 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/signaturehelp/SignatureHelpProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/signaturehelp/SignatureHelpProvider.java @@ -12,8 +12,8 @@ import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; @@ -43,10 +43,10 @@ public void setCapabilities(final ServerCapabilities capabilities) { } /** - * Provide a {{SignatureHelp}} for {{position}} in {{path}}. + * Provide a {@link SignatureHelp} for {@code position} in {@code path}. * @param magikFile Magik file. * @param position Position in file. - * @return {{SignatureHelp}}. + * @return {@link SignatureHelp}. */ public SignatureHelp provideSignatureHelp(final MagikTypedFile magikFile, final Position position) { // Get intended method and called type. @@ -68,7 +68,7 @@ public SignatureHelp provideSignatureHelp(final MagikTypedFile magikFile, final final AstNode previousSiblingNode = currentNode.getPreviousSibling(); final ExpressionResult result = reasoner.getNodeType(previousSiblingNode); final ITypeKeeper typeKeeper = magikFile.getTypeKeeper(); - final AbstractType unsetType = typeKeeper.getType(GlobalReference.of("sw:unset")); + final AbstractType unsetType = typeKeeper.getType(TypeString.of("sw:unset")); AbstractType type = result.get(0, unsetType); LOGGER.debug("Provide signature for type: {}, method: {}", type.getFullName(), methodName); @@ -85,8 +85,8 @@ public SignatureHelp provideSignatureHelp(final MagikTypedFile magikFile, final if (type == SelfType.INSTANCE) { final AstNode methodDefNode = currentNode.getFirstAncestor(MagikGrammar.METHOD_DEFINITION); final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); - type = typeKeeper.getType(globalRef); + final TypeString typeString = methodDefHelper.getTypeString(); + type = typeKeeper.getType(typeString); } // Provide methods for this type with the name. sigInfos = type.getMethods().stream() diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/symbol/SymbolProvider.java b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/symbol/SymbolProvider.java index bd2373f7..72f91159 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/symbol/SymbolProvider.java +++ b/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/symbol/SymbolProvider.java @@ -11,11 +11,13 @@ import nl.ramsolutions.sw.magik.analysis.Location; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,11 +44,11 @@ public void setCapabilities(final ServerCapabilities capabilities) { } /** - * Get symbols matching {{query}}. + * Get symbols matching {@code query}. * @param query Query to match against. - * @return {{SymbolInformation}}s with query results. + * @return {@link SymbolInformation}s with query results. */ - public List getSymbols(final String query) { + public List getSymbols(final String query) { LOGGER.debug("Searching for: '{}'", query); if (query.trim().isEmpty()) { @@ -54,10 +56,12 @@ public List getSymbols(final String query) { } final Predicate typePredicate; + final Predicate conditionPredicate; final Predicate methodPredicate; final BiPredicate typeMethodPredicate; try { typePredicate = this.buildTypePredicate(query); + conditionPredicate = this.buildConditionPredicate(query); methodPredicate = this.buildMethodPredicate(query); typeMethodPredicate = this.buildTypeMethodPredicate(query); } catch (PatternSyntaxException ex) { @@ -65,17 +69,17 @@ public List getSymbols(final String query) { return Collections.emptyList(); } - final List symbolInformations = new ArrayList<>(); + final List symbolInformations = new ArrayList<>(); for (final AbstractType type : this.typeKeeper.getTypes()) { if (typePredicate.test(type)) { final Location location = type.getLocation() != null ? type.getLocation() : DUMMY_LOCATION; - final SymbolInformation information = new SymbolInformation( + final WorkspaceSymbol symbol = new WorkspaceSymbol( "Exemplar: " + type.getFullName(), SymbolKind.Class, - Lsp4jConversion.locationToLsp4j(location)); - symbolInformations.add(information); + Either.forLeft(Lsp4jConversion.locationToLsp4j(location))); + symbolInformations.add(symbol); } for (final Method method : type.getLocalMethods()) { @@ -83,36 +87,49 @@ public List getSymbols(final String query) { final Location location = method.getLocation() != null ? method.getLocation() : DUMMY_LOCATION; - final SymbolInformation information = new SymbolInformation( + final WorkspaceSymbol symbol = new WorkspaceSymbol( "Method: " + method.getSignature(), SymbolKind.Method, - Lsp4jConversion.locationToLsp4j(location)); - symbolInformations.add(information); + Either.forLeft(Lsp4jConversion.locationToLsp4j(location))); + symbolInformations.add(symbol); } if (typeMethodPredicate.test(type, method)) { final Location location = method.getLocation() != null ? method.getLocation() : DUMMY_LOCATION; - final SymbolInformation information = new SymbolInformation( + final WorkspaceSymbol symbol = new WorkspaceSymbol( "Method: " + method.getSignature(), SymbolKind.Method, - Lsp4jConversion.locationToLsp4j(location)); - symbolInformations.add(information); + Either.forLeft(Lsp4jConversion.locationToLsp4j(location))); + symbolInformations.add(symbol); } } } + for (final Condition condition : this.typeKeeper.getConditions()) { + if (conditionPredicate.test(condition)) { + final Location location = condition.getLocation() != null + ? condition.getLocation() + : DUMMY_LOCATION; + final WorkspaceSymbol symbol = new WorkspaceSymbol( + "Condition: " + condition.getName(), + SymbolKind.Class, + Either.forLeft(Lsp4jConversion.locationToLsp4j(location))); + symbolInformations.add(symbol); + } + } + LOGGER.debug("Finished searching for: '{}', result count: {}", query, symbolInformations.size()); return symbolInformations; } /** - * Build {{Predicate}} which matches {{AbstractType}}. This only gives a matchable + * Build {@link Predicate} which matches {@link AbstractType}. This only gives a matchable * predicate if no '.' appears in the query. * * @param query Query string - * @return Predicate to match {{AbstractType}} + * @return Predicate to match {@link AbstractType}. */ private Predicate buildTypePredicate(final String query) { final int dotIndex = query.indexOf('.'); @@ -125,10 +142,27 @@ private Predicate buildTypePredicate(final String query) { } /** - * Build {{Predicate}} which matches {{Method}}. + * Build {@link Predicate} which matches {@link Condition}. This only gives a matchable + * predicate if no '.' appears in the query. + * + * @param query Query string + * @return {@link Predicate} to match {@link Condition} + */ + private Predicate buildConditionPredicate(final String query) { + final int dotIndex = query.indexOf('.'); + if (dotIndex != -1) { + return type -> false; + } + + final String regexp = ".*" + query + ".*"; + return condition -> condition.getName().matches(regexp); + } + + /** + * Build {@link Predicate} which matches {@link Method}. * This only gives a matchable predicate if no '.' appears in the query. * @param query Query string - * @return Predicate to match {{Method}} + * @return Predicate to match {@link Method} */ private Predicate buildMethodPredicate(final String query) { final int dotIndex = query.indexOf('.'); @@ -141,10 +175,10 @@ private Predicate buildMethodPredicate(final String query) { } /** - * Build {{BiPredicate}} which matches {{AbstractType}} and {{Method}}. + * Build {@link BiPredicate} which matches {@link AbstractType} and {@link Method}. * This only gives a matchable predicate if '.' appears in the query. * @param query Query string - * @return BiPredicate to match {{AbstractType}} and {{Method}} + * @return {@link BiPredicate} to match {@link AbstractType} and {@link Method} */ private BiPredicate buildTypeMethodPredicate(final String query) { final int dotIndex = query.indexOf('.'); diff --git a/magik-language-server/src/main/resources/debug-logging.properties b/magik-language-server/src/main/resources/debug-logging.properties index 64cbf62a..74375952 100644 --- a/magik-language-server/src/main/resources/debug-logging.properties +++ b/magik-language-server/src/main/resources/debug-logging.properties @@ -10,8 +10,19 @@ java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format = %1$tF %1$tT %4$-7s %2$s : %5$s %6$s%n -nl.ramsolutions.sw.magik.analysis.typing.ClassInfoTypeKeeperReader.level = OFF -nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner.level = OFF -nl.ramsolutions.sw.magik.languageserver.indexer.MagikIndexer.level = OFF -nl.ramsolutions.sw.magik.languageserver.indexer.PreMagikIndexer.level = OFF -nl.ramsolutions.sw.magik.languageserver.indexer.MagikPreIndexer.level = OFF +nl.ramsolutions.sw.magik.analysis.typing.ClassInfoTypeKeeperReader.level = WARNING +nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper.level = WARNING +nl.ramsolutions.sw.magik.analysis.typing.indexer.MagikPreIndexer.level = WARNING +nl.ramsolutions.sw.magik.analysis.typing.indexer.MagikIndexer.level = WARNING +nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner.level = WARNING + +nl.ramsolutions.sw.magik.languageserver.completion.CompletionProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.diagnostics.DiagnosticsProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.formatting.FormattingProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.hover.HoverProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.implementation.ImplementationProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.references.ReferencesProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.rename.RenameProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.semantictokens.SemanticTokenProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.signaturehelp.SignatureHelpProvider.level = WARNING +nl.ramsolutions.sw.magik.languageserver.symbol.SymbolProvider.level = WARNING diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/completion/CompletionProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/completion/CompletionProviderTest.java index 03d247a7..ffab11db 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/completion/CompletionProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/completion/CompletionProviderTest.java @@ -11,11 +11,11 @@ import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikKeyword; import nl.ramsolutions.sw.magik.languageserver.completion.CompletionProvider; import org.eclipse.lsp4j.CompletionItem; @@ -69,14 +69,17 @@ void testMethodCompletionBare() { + " 1.\n" + "_endmethod"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "find_me()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final Position position = new Position(1, 6); // On '.'. final List completions = this.getCompletions(code, typeKeeper, position); @@ -96,15 +99,17 @@ void testMethodCompletionSelf() { + " _self.\n" + "_endmethod"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType aType = new SlottedType(GlobalReference.of("user:a")); - typeKeeper.addType(aType); + final TypeString aRef = TypeString.of("user:a"); + final MagikType aType = new MagikType(typeKeeper, Sort.SLOTTED, aRef); aType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "find_me()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final Position position = new Position(1, 10); // On '.'. final List completions = this.getCompletions(code, typeKeeper, position); @@ -124,14 +129,17 @@ void testMethodCompletionExisting() { + " 1.fi\n" + "_endmethod"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "find_me()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final Position position = new Position(1, 8); // On 'i'. final List completions = this.getCompletions(code, typeKeeper, position); @@ -198,9 +206,9 @@ void testGlobalCompletionSlot() { + " \n" + "_endmethod"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType aType = new SlottedType(GlobalReference.of("user", "a")); + final TypeString aRef = TypeString.of("user:a"); + final MagikType aType = new MagikType(typeKeeper, Sort.SLOTTED, aRef); aType.addSlot(null, "slot1"); - typeKeeper.addType(aType); final Position position = new Position(1, 2); // On ''. final List completions = this.getCompletions(code, typeKeeper, position); diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/formatting/FormattingProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/formatting/FormattingProviderTest.java index c6f4a4db..619711bf 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/formatting/FormattingProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/formatting/FormattingProviderTest.java @@ -34,9 +34,9 @@ private List getEdits(final String code, final FormattingOptions optio return provider.provideFormatting(magikFile, options); } - // region: Whitespaces + // region: Whitespace @Test - void testMethodDefintion1() { + void testWhitespaceMethodDefintion1() { final String code = "" + "_method a. b(x, y, z)\n" + "_endmethod\n"; @@ -46,7 +46,7 @@ void testMethodDefintion1() { } @Test - void testMethodDefintion2() { + void testWhitespaceMethodDefintion2() { final String code = "" + "_method a.b (x, y, z)\n" + "_endmethod\n"; @@ -56,7 +56,7 @@ void testMethodDefintion2() { } @Test - void testParameters1() { + void testWhitespaceParameters1() { final String code = "" + "_method a.b(x,y, z)\n" + "_endmethod\n"; @@ -74,7 +74,7 @@ void testParameters1() { } @Test - void testParameters2() { + void testWhitespaceParameters2() { final String code = "" + "_method a.b(x, y,z)\n" + "_endmethod\n"; @@ -92,7 +92,7 @@ void testParameters2() { } @Test - void testParameters3() { + void testWhitespaceParameters3() { final String code = "" + "_method a.b(x, y , z)\n" + "_endmethod\n"; @@ -110,7 +110,7 @@ void testParameters3() { } @Test - void testParameters4() { + void testWhitespaceParameters4() { final String code = "print(a,b, c)\n"; final List edits = this.getEdits(code); @@ -126,7 +126,7 @@ void testParameters4() { } @Test - void testParameters5() { + void testWhitespaceParameters5() { final String code = "print(a, b,c)\n"; final List edits = this.getEdits(code); @@ -142,7 +142,7 @@ void testParameters5() { } @Test - void testMethodInvocation1() { + void testWhitespaceMethodInvocation1() { final String code = "class .method(a, b, c)\n"; final List edits = this.getEdits(code); @@ -158,7 +158,7 @@ void testMethodInvocation1() { } @Test - void testMethodInvocation2() { + void testWhitespaceMethodInvocation2() { final String code = "class. method(a, b, c)\n"; final List edits = this.getEdits(code); @@ -174,7 +174,7 @@ void testMethodInvocation2() { } @Test - void testMethodInvocation3() { + void testWhitespaceMethodInvocation3() { final String code = "class.method (a, b, c)\n"; final List edits = this.getEdits(code); @@ -190,7 +190,7 @@ void testMethodInvocation3() { } @Test - void testArguments1() { + void testWhitespaceArguments1() { final String code = "prc( a, b, c)\n"; final List edits = this.getEdits(code); @@ -206,7 +206,7 @@ void testArguments1() { } @Test - void testArguments2() { + void testWhitespaceArguments2() { final String code = "prc(a,b, c)\n"; final List edits = this.getEdits(code); @@ -222,7 +222,7 @@ void testArguments2() { } @Test - void testArguments3() { + void testWhitespaceArguments3() { final String code = "prc(a, b,c)\n"; final List edits = this.getEdits(code); @@ -238,7 +238,7 @@ void testArguments3() { } @Test - void testArguments4() { + void testWhitespaceArguments4() { final String code = "prc(a, b , c)\n"; final List edits = this.getEdits(code); @@ -254,7 +254,15 @@ void testArguments4() { } @Test - void testMethodInvocationMultiLine() { + void testWhitespaceArgumentsSelf() { + final String code = "prc(_self)\n"; + final List edits = this.getEdits(code); + + assertThat(edits).isEmpty(); + } + + @Test + void testWhitespaceMethodInvocationMultiLine() { final String code = "" + "obj.\n" + "m()\n"; @@ -270,11 +278,29 @@ void testMethodInvocationMultiLine() { "\t"); assertThat(edit0).isEqualTo(expected0); } + + @Test + void testWhitespaceSimpleVector() { + final String code = "" + + "{:slot1, _unset, :readable, :public}"; + + final List edits = this.getEdits(code); + assertThat(edits).isEmpty(); + } + + @Test + void testWhitespaceAssignmentMethod() { + final String code = "" + + "_self.x() << 10"; + + final List edits = this.getEdits(code); + assertThat(edits).isEmpty(); + } // endregion // region: Indenting. @Test - void testIndentingStatement() { + void testIndentBlockStatement() { final String code = "" + "_block\n" + "print(1)\n" @@ -293,7 +319,7 @@ void testIndentingStatement() { } @Test - void testIndentingComments() { + void testIndentComments() { final String code = "" + "_block\n" + "# comment\n" @@ -312,7 +338,7 @@ void testIndentingComments() { } @Test - void testCommentsAfterStatement() { + void testIndentCommentsAfterStatement() { final String code = "" + "_method a.b(a, b, c)\n" + "\tprint(1) # test method\n" @@ -323,7 +349,7 @@ void testCommentsAfterStatement() { } @Test - void testNewlineExpression() { + void testIndentNewlineExpression() { final String code = "" + "_if a() _andif\n" + "b()\n" @@ -361,7 +387,7 @@ void testIndentAssignmentExpression() { } @Test - void testAssignmentExpression() { + void testIndentAssignmentExpression2() { final String code = "" + "a << _if x?\n" + "_then\n" @@ -413,6 +439,43 @@ void testAssignmentExpression() { "\t"); assertThat(edit4).isEqualTo(expected4); } + + @Test + void testIndentArguments() { + final String code = "" + + "def_slotted_exemplar(\n" + + "\t:test_ex,\n" + + "\t{\n" + + "\t\t{:slot1, _unset}\n" + + "\t})\n"; + final List edits = this.getEdits(code); + assertThat(edits).isEmpty(); + } + + @Test + void testIndentIfElif() { + final String code = "" + + "_if a\n" + + "_then\n" + + "\tshow(:a)\n" + + "_elif b\n" + + "_then\n" + + "\tshow(:b)\n" + + "_else\n" + + "\tshow(:c)\n" + + "_endif\n"; + final List edits = this.getEdits(code); + assertThat(edits).isEmpty(); + } + + @Test + void testIndentVariableDefinitionAssignment() { + final String code = "" + + "_local a <<\n" + + "\t10"; + final List edits = this.getEdits(code); + assertThat(edits).isEmpty(); + } // endregion // region: Comments diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/hover/HoverProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/hover/HoverProviderTest.java index eaa88c6a..79a1347e 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/hover/HoverProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/hover/HoverProviderTest.java @@ -4,13 +4,14 @@ import java.util.Collections; import java.util.EnumSet; import nl.ramsolutions.sw.magik.MagikTypedFile; +import nl.ramsolutions.sw.magik.analysis.typing.BinaryOperator; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.languageserver.hover.HoverProvider; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkupContent; @@ -37,15 +38,17 @@ private Hover provideHover(final String code, final Position position, final ITy void testProvideHoverMethodDefinitionName() { // Set up a method in the TypeKeeper. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType objectType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:object")); - final Method method = objectType.addMethod( - EnumSet.noneOf(Method.Modifier.class), + final TypeString objectRef = TypeString.of("sw:object"); + final MagikType objectType = (MagikType) typeKeeper.getType(objectRef); + objectType.addMethod( null, + EnumSet.noneOf(Method.Modifier.class), "hover_me_method()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - method.setDoc("method_doc"); + "method_doc", + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final String code = "" + "_method object.hover_me_method()\n" @@ -64,9 +67,9 @@ void testProvideHoverMethodDefinitionName() { void testProvideHoverMethodDefinitionExemplar() { // Set up a method. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType hoverMeType = new SlottedType(GlobalReference.of("user:hover_me_type")); + final TypeString hoverMeTypeRef = TypeString.of("user:hover_me_type"); + final MagikType hoverMeType = new MagikType(typeKeeper, Sort.SLOTTED, hoverMeTypeRef); hoverMeType.setDoc("type_doc"); - typeKeeper.addType(hoverMeType); final String code = "" + "_method hover_me_type.method()\n" @@ -85,15 +88,17 @@ void testProvideHoverMethodDefinitionExemplar() { void testProvideHoverMethod() { // Set up a method. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); - final Method method = integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + integerType.addMethod( null, + EnumSet.noneOf(Method.Modifier.class), "hover_me()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - method.setDoc("method_doc"); + "method_doc", + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final String code = "" + "_method a.b\n" @@ -132,7 +137,8 @@ void testProvideHoverMethodUnknown() { void testProvideHoverType() { // Set up a method. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType symbolType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final MagikType symbolType = (MagikType) typeKeeper.getType(symbolRef); symbolType.setDoc("type_doc"); final String code = "" @@ -154,15 +160,13 @@ void testProvideHoverType() { void testProvideHoverTypeUnknown() { // Set up a method. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType symbolType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:symbol")); - symbolType.setDoc("type_doc"); final String code = "" + "_method a.b\n" + " _local var << some_object\n" + " var.hover_me()\n" + "_endmethod"; - final Position position = new Position(2, 4); + final Position position = new Position(2, 4); // On `var`. // Hover and test. final Hover hover = this.provideHover(code, position, typeKeeper); @@ -175,13 +179,13 @@ void testProvideHoverTypeUnknown() { void testProvideHoverAssignedVariable() { // Set up a method. final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType symbolType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final MagikType symbolType = (MagikType) typeKeeper.getType(symbolRef); symbolType.setDoc("type_doc"); final String code = "" + "_method a.b\n" + " _local var << :symbol\n" - + " var.hover_me()\n" + "_endmethod"; final Position position = new Position(1, 11); // On `var`. @@ -193,4 +197,30 @@ void testProvideHoverAssignedVariable() { assertThat(content.getValue()).contains("type_doc"); } + @Test + void testBinaryOperatorTimes() { + // Set up a method. + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + integerType.setDoc("type_doc"); + + final BinaryOperator binaryOperator = + new BinaryOperator(BinaryOperator.Operator.STAR, integerRef, integerRef, integerRef); + typeKeeper.addBinaryOperator(binaryOperator); + + final String code = "" + + "_method a.b\n" + + " _local var << 4 * 4\n" + + "_endmethod"; + final Position position = new Position(1, 20); // On `*`. + + // Hover and test. + final Hover hover = this.provideHover(code, position, typeKeeper); + final MarkupContent content = hover.getContents().getRight(); + assertThat(content.getKind()).isEqualTo(MarkupKind.MARKDOWN); + assertThat(content.getValue()).contains("integer"); + assertThat(content.getValue()).contains("type_doc"); + } + } diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/implementation/ImplementationProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/implementation/ImplementationProviderTest.java index 9fd9b4c0..0cb9a64d 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/implementation/ImplementationProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/implementation/ImplementationProviderTest.java @@ -10,10 +10,10 @@ import nl.ramsolutions.sw.magik.analysis.Range; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.languageserver.Lsp4jConversion; import nl.ramsolutions.sw.magik.languageserver.implementation.ImplementationProvider; import org.junit.jupiter.api.Test; @@ -33,14 +33,17 @@ class ImplementationProviderTest { @Test void testProvideImplementation() { final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), EMPTY_LOCATION, + EnumSet.noneOf(Method.Modifier.class), "implementation()", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final URI uri = URI.create("tests://unittest"); final String code = "" diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/indexer/MagikIndexerTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/indexer/MagikIndexerTest.java index fac4c0d3..11be016e 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/indexer/MagikIndexerTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/indexer/MagikIndexerTest.java @@ -4,11 +4,11 @@ import java.util.Collection; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; +import nl.ramsolutions.sw.magik.analysis.typing.indexer.MagikIndexer; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; -import nl.ramsolutions.sw.magik.languageserver.indexer.MagikIndexer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -21,7 +21,7 @@ class MagikIndexerTest { /** * VSCode runs from module directory, mvn runs from project directory. * - * @return Proper {{Path}} to file. + * @return Proper {@link Path} to file. */ protected Path getPath(final Path relativePath) { final Path path = Path.of(".").toAbsolutePath().getParent(); @@ -40,8 +40,8 @@ void testFileCreated() { magikIndexer.indexPathCreated(fixedPath); // Test type. - final GlobalReference globalRef = GlobalReference.of("user:test_exemplar"); - final AbstractType type = typeKeeper.getType(globalRef); + final TypeString typeString = TypeString.of("user:test_exemplar"); + final AbstractType type = typeKeeper.getType(typeString); assertThat(type).isNotEqualTo(UndefinedType.INSTANCE); // Test methods. @@ -64,8 +64,8 @@ void testFileChanged() { magikIndexer.indexPathChanged(fixedPath); // Test type. - final GlobalReference globalRef = GlobalReference.of("user:test_exemplar"); - final AbstractType type = typeKeeper.getType(globalRef); + final TypeString typeString = TypeString.of("user:test_exemplar"); + final AbstractType type = typeKeeper.getType(typeString); assertThat(type).isNotEqualTo(UndefinedType.INSTANCE); // Test methods. @@ -85,15 +85,15 @@ void testFileDeleted() { magikIndexer.indexPathCreated(fixedPath); // Test type. - final GlobalReference globalRef = GlobalReference.of("user:test_exemplar"); - final AbstractType type = typeKeeper.getType(globalRef); + final TypeString typeString = TypeString.of("user:test_exemplar"); + final AbstractType type = typeKeeper.getType(typeString); assertThat(type).isNotEqualTo(UndefinedType.INSTANCE); - // Pretend update. + // Pretend delete. magikIndexer.indexPathDeleted(fixedPath); // Test type. - final AbstractType typeRemoved = typeKeeper.getType(globalRef); + final AbstractType typeRemoved = typeKeeper.getType(typeString); assertThat(typeRemoved).isEqualTo(UndefinedType.INSTANCE); } diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/references/ReferencesProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/references/ReferencesProviderTest.java index dd2c8688..ab61946f 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/references/ReferencesProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/references/ReferencesProviderTest.java @@ -10,10 +10,11 @@ import nl.ramsolutions.sw.magik.analysis.Range; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; +import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.languageserver.references.ReferencesProvider; import org.junit.jupiter.api.Test; @@ -40,15 +41,21 @@ private List getReferences( @Test void testProvideMethodReferenceFromMethodInvocation() { final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); final Method referingMethod = integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), EMPTY_LOCATION, + EnumSet.noneOf(Method.Modifier.class), "refering", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - referingMethod.addCalledMethod("refering"); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); + final TypeString undefinedTypeRef = TypeString.of(UndefinedType.SERIALIZED_NAME); + final Method.MethodUsage calledMethod = + new Method.MethodUsage(undefinedTypeRef, "refering", EMPTY_LOCATION); + referingMethod.addCalledMethod(calledMethod); final String code = "" + "_method integer.refering\n" @@ -62,21 +69,27 @@ void testProvideMethodReferenceFromMethodInvocation() { @Test void testProvideMethodReferenceFromMethodDefintion() { final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString interRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(interRef); final Method referingMethod = integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), EMPTY_LOCATION, + EnumSet.noneOf(Method.Modifier.class), "refering", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - referingMethod.addCalledMethod("refering"); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); + final TypeString undefinedTypeRef = TypeString.of(UndefinedType.SERIALIZED_NAME); + final Method.MethodUsage calledMethod = + new Method.MethodUsage(undefinedTypeRef, "refering", EMPTY_LOCATION); + referingMethod.addCalledMethod(calledMethod); final String code = "" + "_method integer.refering\n" + " _self.refering\n" + "_endmethod\n"; - final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(0, 20); // On refering + final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(0, 20); // On `refering`. final List references = this.getReferences(code, position, typeKeeper); assertThat(references).hasSize(1); } @@ -84,21 +97,25 @@ void testProvideMethodReferenceFromMethodDefintion() { @Test void testProvideTypeReferenceFromAtom() { final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); final Method referingMethod = integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), EMPTY_LOCATION, + EnumSet.noneOf(Method.Modifier.class), "refering", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - referingMethod.addUsedType("sw:integer"); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); + final Method.GlobalUsage typeUsage = new Method.GlobalUsage(integerRef, EMPTY_LOCATION); + referingMethod.addUsedType(typeUsage); final String code = "" + "_method integer.refering\n" + " integer\n" + "_endmethod\n"; - final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(1, 4); // On integer. + final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(1, 4); // On `integer`. final List references = this.getReferences(code, position, typeKeeper); assertThat(references).hasSize(1); } @@ -106,21 +123,25 @@ void testProvideTypeReferenceFromAtom() { @Test void testProvideTypeReferenceFromMethodDefinition() { final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); final Method referingMethod = integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), EMPTY_LOCATION, + EnumSet.noneOf(Method.Modifier.class), "refering", Collections.emptyList(), null, - ExpressionResult.UNDEFINED); - referingMethod.addUsedType("sw:integer"); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); + final Method.GlobalUsage typeUsage = new Method.GlobalUsage(integerRef, EMPTY_LOCATION); + referingMethod.addUsedType(typeUsage); final String code = "" + "_method integer.refering\n" + " print(integer)\n" + "_endmethod\n"; - final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(0, 10); // On integer. + final org.eclipse.lsp4j.Position position = new org.eclipse.lsp4j.Position(0, 10); // On `integer`. final List references = this.getReferences(code, position, typeKeeper); assertThat(references).hasSize(1); } diff --git a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/rename/RenameProviderTest.java b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/rename/RenameProviderTest.java index 2961b488..73112aed 100644 --- a/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/rename/RenameProviderTest.java +++ b/magik-language-server/src/test/java/nl/ramsolutions/sw/magik/ramsolutions/rename/RenameProviderTest.java @@ -8,11 +8,12 @@ import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; import nl.ramsolutions.sw.magik.languageserver.rename.RenameProvider; import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; import org.eclipse.lsp4j.PrepareRenameResult; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Either3; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -23,7 +24,8 @@ @SuppressWarnings("checkstyle:MagicNumber") class RenameProviderTest { - private Either getPrepareRename(String code, final Position position) { + private Either3 getPrepareRename( + String code, final Position position) { final URI uri = URI.create("tests://unittest"); final ITypeKeeper typeKeeper = new TypeKeeper(); final MagikTypedFile magikFile = new MagikTypedFile(uri, code, typeKeeper); @@ -48,10 +50,11 @@ void testPrepareRenameLocal() { + "_endblock\n"; final Position position = new Position(1, 12); // On `var`. - final Either either = this.getPrepareRename(code, position); + final Either3 either = + this.getPrepareRename(code, position); assertThat(either).isNotNull(); - final PrepareRenameResult prepareRenameResult = either.getRight(); + final PrepareRenameResult prepareRenameResult = either.getSecond(); assertThat(prepareRenameResult.getRange()).isEqualTo( new Range( new Position(1, 11), new Position(1, 14))); @@ -105,10 +108,11 @@ void testPrepareRenameForVariable() { + "_endblock\n"; final Position position = new Position(1, 10); // on `iter_var`. - final Either either = this.getPrepareRename(code, position); + final Either3 either = + this.getPrepareRename(code, position); assertThat(either).isNotNull(); - final PrepareRenameResult prepareRenameResult = either.getRight(); + final PrepareRenameResult prepareRenameResult = either.getSecond(); assertThat(prepareRenameResult.getRange()).isEqualTo( new Range( new Position(1, 9), new Position(1, 17))); diff --git a/magik-lint/pom.xml b/magik-lint/pom.xml index 0acd0bcb..95967a6a 100644 --- a/magik-lint/pom.xml +++ b/magik-lint/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-lint @@ -27,10 +27,6 @@ commons-cli commons-cli - - org.apache.commons - commons-text - org.slf4j slf4j-api diff --git a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Configuration.java b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Configuration.java index e14f080b..d7244990 100644 --- a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Configuration.java +++ b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Configuration.java @@ -36,8 +36,8 @@ public Configuration() { } /** - * Constructor which reads properties from {{path}}. - * @param path {{Path}} to read properties from. + * Constructor which reads properties from {@code path}. + * @param path {@link Path} to read properties from. */ public Configuration(final Path path) { LOGGER.debug("Reading configuration from: {}", path.toAbsolutePath()); diff --git a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandler.java b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandler.java deleted file mode 100644 index 9bea30b2..00000000 --- a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandler.java +++ /dev/null @@ -1,179 +0,0 @@ -package nl.ramsolutions.sw.magik.lint; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.CheckForNull; -import nl.ramsolutions.sw.magik.MagikFile; -import nl.ramsolutions.sw.magik.analysis.scope.GlobalScope; -import nl.ramsolutions.sw.magik.analysis.scope.Scope; - -/** - * Read mlint: a=b;c=d instructions at lines and scopes. - */ -public class LintInstructionsHandler { - - private static final Pattern MLINT_PATTERN_EOL = Pattern.compile(".*# ?mlint: ?(.*)"); - private static final Pattern MLINT_PATTERN_SINGLE = Pattern.compile("^\\s*# ?mlint: ?(.*)"); - - private final MagikFile magikFile; - private final Map> scopeInstructions = new HashMap<>(); - private final Map> lineInstructions = new HashMap<>(); - - /** - * Constructor. - * @param magikFile {{MagikVisitorContext}} to use. - */ - public LintInstructionsHandler(final MagikFile magikFile) { - this.magikFile = magikFile; - this.parseInstructionsInScopes(); - this.parseInstructionsFromLines(); - } - - // #region: parsing - /** - * Extract instructions-string (at the end) from {{str}}. - * @param str String to extract from. - * @param single True if it should be the only thing in the string. - * @return Unparsed instructions-string. - */ - @CheckForNull - private String extractInstructionsInStr(final String str, final boolean single) { - final Pattern pattern = single - ? MLINT_PATTERN_SINGLE - : MLINT_PATTERN_EOL; - final Matcher matcher = pattern.matcher(str); - if (!matcher.find()) { - return null; - } - - return matcher.group(1); - } - - /** - * Parse all mlint instructions. - * Mlint instructions are key=value pairs, each pair being ;-separated. - * @param str String to parse. - * @return Map with parsed instructions. - */ - private Map parseMLintInstructions(final String str) { - final Map instructions = new HashMap<>(); - - Arrays.stream(str.split(";")) - .map(String::trim) - .forEach(item -> { - final String[] parts = item.split("="); - if (parts.length != 2) { - return; - } - - final String key = parts[0].trim(); - final String value = parts[1].trim(); - instructions.put(key, value); - }); - - return instructions; - } - // #endregion - - // #region: scopes - /** - * Get instructions in {{Scope}} and any ancestor {{Scope}}s. - * @param line Line in file - * @param column Column in file - * @return Map with instructions for the Scope at line/column - */ - public Map getInstructionsInScope(final int line, final int column) { - final Map instructions = new HashMap<>(); - final GlobalScope globalScope = this.magikFile.getGlobalScope(); - if (globalScope == null) { - return instructions; - } - - // ensure we can find a Scope - final Scope fromScope = globalScope.getScopeForLineColumn(line, column); - if (fromScope == null) { - return instructions; - } - - // iterate over all (ancestor) scopes, see if the check is disabled in any scope - final List scopes = fromScope.getSelfAndAncestorScopes(); - // Reverse such that if a narrower scope overrides a broader scope instruction. - Collections.reverse(scopes); - for (final Scope scope : scopes) { - final Map scopeInstuctions = this.scopeInstructions.get(scope); - if (scopeInstuctions != null) { - instructions.putAll(scopeInstuctions); - } - } - - return instructions; - } - - private void parseInstructionsInScopes() { - final String[] lines = this.magikFile.getSourceLines(); - final GlobalScope globalScope = this.magikFile.getGlobalScope(); - for (int lineNo = 0; lineNo < lines.length; ++lineNo) { - final String line = lines[lineNo]; - final String str = this.extractInstructionsInStr(line, true); - if (str == null) { - continue; - } - - final Map instructions = this.parseMLintInstructions(str); - final Scope scope = globalScope.getScopeForLineColumn(lineNo + 1, 0); - if (scope == null) { - continue; - } - - final Map instructionsInScope = this.scopeInstructions.get(scope); - if (instructionsInScope == null) { - this.scopeInstructions.put(scope, instructions); - } else { - instructionsInScope.putAll(instructions); - } - } - } - // #endregion - - // #region: lines - /** - * Get instructions at (the end of) {{line}}. - * @param line Line number to extract from. - * @return Instructions at {{line}}. - */ - public Map getInstructionsAtLine(int line) { - final Map instructions = this.lineInstructions.get(line); - if (instructions == null) { - return new HashMap<>(); - } - return instructions; - } - - /** - * Read all instructions from all lines. - */ - private void parseInstructionsFromLines() { - final String[] lines = this.magikFile.getSourceLines(); - if (lines == null) { - return; - } - - for (int lineNo = 0; lineNo < lines.length; ++lineNo) { - final String line = lines[lineNo]; - final String str = this.extractInstructionsInStr(line, false); - if (str == null) { - continue; - } - - final Map instructions = this.parseMLintInstructions(str); - this.lineInstructions.put(lineNo + 1, instructions); - } - } - // #endregion - -} diff --git a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/MagikLint.java b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/MagikLint.java index 9534e97b..46f353f5 100644 --- a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/MagikLint.java +++ b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/MagikLint.java @@ -12,7 +12,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -22,11 +24,14 @@ import nl.ramsolutions.sw.FileCharsetDeterminer; import nl.ramsolutions.sw.magik.MagikFile; import nl.ramsolutions.sw.magik.analysis.Location; +import nl.ramsolutions.sw.magik.analysis.scope.GlobalScope; +import nl.ramsolutions.sw.magik.analysis.scope.Scope; import nl.ramsolutions.sw.magik.checks.CheckList; import nl.ramsolutions.sw.magik.checks.MagikCheck; import nl.ramsolutions.sw.magik.checks.MagikCheckHolder; import nl.ramsolutions.sw.magik.checks.MagikIssue; import nl.ramsolutions.sw.magik.lint.output.Reporter; +import nl.ramsolutions.sw.magik.parser.CommentInstructionReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.check.Rule; @@ -39,6 +44,11 @@ public class MagikLint { private static final Logger LOGGER = LoggerFactory.getLogger(MagikLint.class); + private static final CommentInstructionReader.InstructionType MLINT_INSTRUCTION = + CommentInstructionReader.InstructionType.createInstructionType("mlint"); + private static final CommentInstructionReader.InstructionType MLINT_SCOPE_INSTRUCTION = + CommentInstructionReader.InstructionType.createScopeInstructionType("mlint"); + private final Configuration config; private final Reporter reporter; @@ -128,21 +138,18 @@ void showChecks(final Writer writer) throws ReflectiveOperationException, IOExce * @return true if issue is disabled at line. */ private boolean isMagikIssueDisabled( - final MagikIssue magikIssue, final LintInstructionsHandler instructionsHandler) { + final MagikFile magikFile, + final MagikIssue magikIssue, + final CommentInstructionReader instructionReader) { final MagikCheckHolder holder = magikIssue.check().getHolder(); - if (holder == null) { - throw new IllegalStateException(); - } + Objects.requireNonNull(holder); final Integer line = magikIssue.startLine(); - final Integer column = magikIssue.startColumn(); - if (line == null || column == null) { - return false; - } final String checkKey = holder.getCheckKeyKebabCase(); - final Map scopeInstructions = instructionsHandler.getInstructionsInScope(line, column); - final Map lineInstructions = instructionsHandler.getInstructionsAtLine(line); + final Map scopeInstructions = + MagikLint.getScopeInstructions(magikFile, instructionReader, line); + final Map lineInstructions = MagikLint.getLineInstructions(instructionReader, line); final String[] scopeDisableds = scopeInstructions.getOrDefault("disable", "").split(","); final String[] lineDisableds = lineInstructions.getOrDefault("disable", "").split(","); return List.of(scopeDisableds).contains(checkKey) @@ -150,15 +157,16 @@ private boolean isMagikIssueDisabled( } /** - * Run {{MagikCheckHolder}}s on {{MagikFile}}. + * Run {@link MagikCheckHolder}s on {@link MagikFile}. * @param magikFile File to run on. - * @param holders {{MagikCheckHolder}}s to run. - * @return List of {{MagikIssue}}s for the given file. + * @param holders {@link MagikCheckHolder}s to run. + * @return List of {@link MagikIssue}s for the given file. */ private List runChecksOnFile(final MagikFile magikFile, final Iterable holders) { LOGGER.trace("Thread: {}, checking file: {}", Thread.currentThread().getName(), magikFile); - final LintInstructionsHandler instructionsHandler = new LintInstructionsHandler(magikFile); + final CommentInstructionReader instructionReader = new CommentInstructionReader( + magikFile, Set.of(MLINT_INSTRUCTION, MLINT_SCOPE_INSTRUCTION)); final List magikIssues = new ArrayList<>(); // run checks on files @@ -169,7 +177,7 @@ private List runChecksOnFile(final MagikFile magikFile, final Iterab try { final List issues = this.runCheckOnFile(magikFile, holder).stream() - .filter(magikIssue -> !this.isMagikIssueDisabled(magikIssue, instructionsHandler)) + .filter(magikIssue -> !this.isMagikIssueDisabled(magikFile, magikIssue, instructionReader)) .collect(Collectors.toList()); magikIssues.addAll(issues); } catch (ReflectiveOperationException exception) { @@ -181,7 +189,7 @@ private List runChecksOnFile(final MagikFile magikFile, final Iterab } /** - * Run the linter on {{paths}}. + * Run the linter on {@code paths}. * * @throws IOException - * @throws ReflectiveOperationException - @@ -239,7 +247,7 @@ public void run(final Collection paths) throws IOException, ReflectiveOper } /** - * Run the linter on {{magikFile}}. + * Run the linter on {@code magikFile}. * @param magikFile File to run on. * @throws ReflectiveOperationException - */ @@ -256,7 +264,7 @@ public void run(final MagikFile magikFile) throws ReflectiveOperationException { * Get all checks, enabled in the given configuration. * * @param config Configuration to use - * @return Collection of {{MagikCheckHolder}}s. + * @return Collection of {@link MagikCheckHolder}s. */ @SuppressWarnings("unchecked") public static List getAllChecks(final Configuration config) { @@ -311,4 +319,61 @@ public static List getAllChecks(final Configuration config) { return holders; } + /** + * Get scope instructions at line. + * @param magikFile Magik file. + * @param instructionReader Instruction reader to use. + * @param line Line of scope. + * @return Instructions in scope and ancestor scopes. + */ + public static Map getScopeInstructions( + final MagikFile magikFile, + final CommentInstructionReader instructionReader, + final int line) { + final Map instructions = new HashMap<>(); + final GlobalScope globalScope = magikFile.getGlobalScope(); + if (globalScope == null) { + return instructions; + } + + // Ensure we can find a Scope. + final String[] sourceLines = magikFile.getSourceLines(); + final int column = sourceLines[line - 1].length() - 1; + final Scope fromScope = globalScope.getScopeForLineColumn(line, column); + if (fromScope == null) { + return instructions; + } + + // Iterate over all ancestor scopes, see if the check is disabled in any scope. + final List scopes = fromScope.getSelfAndAncestorScopes(); + // Reverse such that if a narrower scope overrides a broader scope instruction. + Collections.reverse(scopes); + for (final Scope scope : scopes) { + final Set scopeInstructions = + instructionReader.getScopeInstructions(scope, MLINT_SCOPE_INSTRUCTION); + final Map parsedScopeInstructions = scopeInstructions.stream() + .map(str -> CommentInstructionReader.parseInstructions(str)) + .reduce( + instructions, + (acc, elem) -> { + acc.putAll(elem); + return acc; + }); + instructions.putAll(parsedScopeInstructions); + } + + return instructions; + } + + private static Map getLineInstructions( + final CommentInstructionReader instructionReader, + final int line) { + final String str = instructionReader.getInstructionsAtLine(line, MLINT_INSTRUCTION); + if (str == null) { + return Collections.emptyMap(); + } + + return CommentInstructionReader.parseInstructions(str); + } + } diff --git a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Main.java b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Main.java index f79398d5..cdfa670c 100644 --- a/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Main.java +++ b/magik-lint/src/main/java/nl/ramsolutions/sw/magik/lint/Main.java @@ -29,59 +29,59 @@ public final class Main { private static final Options OPTIONS; - private static final String OPTION_MSG_TEMPLATE = "msg-template"; - private static final String OPTION_RCFILE = "rcfile"; - private static final String OPTION_SHOW_CHECKS = "show-checks"; - private static final String OPTION_COLUMN_OFFSET = "column-offset"; - private static final String OPTION_MAX_INFRACTIONS = "max-infractions"; - private static final String OPTION_UNTABIFY = "untabify"; - private static final String OPTION_DEBUG = "debug"; - private static final String OPTION_HELP = "help"; + private static final Option OPTION_MSG_TEMPLATE = Option.builder() + .longOpt("msg-template") + .desc("Output pattern") + .hasArg() + .type(PatternOptionBuilder.STRING_VALUE) + .build(); + private static final Option OPTION_RCFILE = Option.builder() + .longOpt("rcfile") + .desc("Configuration file") + .hasArg() + .type(PatternOptionBuilder.FILE_VALUE) + .build(); + private static final Option OPTION_SHOW_CHECKS = Option.builder() + .longOpt("show-checks") + .desc("Show checks and quit") + .build(); + private static final Option OPTION_COLUMN_OFFSET = Option.builder() + .longOpt("column-offset") + .desc("Set column offset, positive or negative") + .hasArg() + .type(PatternOptionBuilder.NUMBER_VALUE) + .build(); + private static final Option OPTION_MAX_INFRACTIONS = Option.builder() + .longOpt("max-infractions") + .desc("Set max number of reporter infractions") + .hasArg() + .type(PatternOptionBuilder.NUMBER_VALUE) + .build(); + private static final Option OPTION_UNTABIFY = Option.builder() + .longOpt("untabify") + .desc("Expand tabs to N spaces") + .hasArg() + .type(PatternOptionBuilder.NUMBER_VALUE) + .build(); + private static final Option OPTION_DEBUG = Option.builder() + .longOpt("debug") + .desc("Enable showing of debug information") + .build(); + private static final Option OPTION_HELP = Option.builder() + .longOpt("help") + .desc("Show this help") + .build(); static { OPTIONS = new Options(); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_HELP) - .desc("Show this help") - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_MSG_TEMPLATE) - .desc("Output pattern") - .hasArg() - .type(PatternOptionBuilder.STRING_VALUE) - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_RCFILE) - .desc("Configuration file") - .hasArg() - .type(PatternOptionBuilder.FILE_VALUE) - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_SHOW_CHECKS) - .desc("Show checks and quit") - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_UNTABIFY) - .desc("Expand tabs to N spaces") - .hasArg() - .type(PatternOptionBuilder.NUMBER_VALUE) - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_COLUMN_OFFSET) - .desc("Set column offset, positive or negative") - .hasArg() - .type(PatternOptionBuilder.NUMBER_VALUE) - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_MAX_INFRACTIONS) - .desc("Set max number of reporter infractions") - .hasArg() - .type(PatternOptionBuilder.NUMBER_VALUE) - .build()); - OPTIONS.addOption(Option.builder() - .longOpt(OPTION_DEBUG) - .desc("Enable showing of debug information") - .build()); + OPTIONS.addOption(OPTION_HELP); + OPTIONS.addOption(OPTION_MSG_TEMPLATE); + OPTIONS.addOption(OPTION_RCFILE); + OPTIONS.addOption(OPTION_SHOW_CHECKS); + OPTIONS.addOption(OPTION_UNTABIFY); + OPTIONS.addOption(OPTION_COLUMN_OFFSET); + OPTIONS.addOption(OPTION_MAX_INFRACTIONS); + OPTIONS.addOption(OPTION_DEBUG); } private static final Map SEVERITY_EXIT_CODE_MAPPING = Map.of( @@ -124,12 +124,14 @@ private static void initDebugLogger() { * @return Reporter. */ private static Reporter createReporter(final Configuration configuration) { - final String template = configuration.hasProperty(OPTION_MSG_TEMPLATE) - ? configuration.getPropertyString(OPTION_MSG_TEMPLATE) + final String msgTemplateOptName = OPTION_MSG_TEMPLATE.getLongOpt(); + final String template = configuration.hasProperty(msgTemplateOptName) + ? configuration.getPropertyString(msgTemplateOptName) : MessageFormatReporter.DEFAULT_FORMAT; - final String columnOffsetStr = configuration.getPropertyString(OPTION_COLUMN_OFFSET); - final Long columnOffset = configuration.hasProperty(OPTION_COLUMN_OFFSET) + final String columnOffsetOptName = OPTION_COLUMN_OFFSET.getLongOpt(); + final String columnOffsetStr = configuration.getPropertyString(columnOffsetOptName); + final Long columnOffset = configuration.hasProperty(columnOffsetOptName) ? Long.parseLong(columnOffsetStr) : null; @@ -149,7 +151,7 @@ public static void main(final String[] args) throws ParseException, IOException, try { commandLine = Main.parseCommandline(args); } catch (UnrecognizedOptionException exception) { - System.out.println("Unrecognized option: " + exception.getMessage()); + System.err.println("Unrecognized option: " + exception.getMessage()); System.exit(1); return; // Keep inferer happy. @@ -165,7 +167,7 @@ public static void main(final String[] args) throws ParseException, IOException, final File rcfile = (File) commandLine.getParsedOptionValue(OPTION_RCFILE); final Path path = rcfile.toPath(); if (!Files.exists(path)) { - System.out.println("RC File does not exist: " + path); + System.err.println("RC File does not exist: " + path); System.exit(1); } @@ -218,7 +220,8 @@ public static void main(final String[] args) throws ParseException, IOException, private static void copyOptionToConfig( final CommandLine commandLine, final Configuration config, - final String key) { + final Option option) { + final String key = option.getLongOpt(); if (commandLine.hasOption(key)) { final String value = commandLine.getOptionValue(key); config.setProperty(key, value); diff --git a/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandlerTest.java b/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandlerTest.java deleted file mode 100644 index a20e1cde..00000000 --- a/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/LintInstructionsHandlerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package nl.ramsolutions.sw.magik.lint; - -import java.net.URI; -import java.util.Map; -import nl.ramsolutions.sw.magik.MagikFile; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test LintInstructionsHandler. - */ -@SuppressWarnings("checkstyle:MagicNumber") - class LintInstructionsHandlerTest { - - private LintInstructionsHandler getInstructions(String code) { - final URI uri = URI.create("tests://unittest"); - final MagikFile magikFile = new MagikFile(uri, code); - return new LintInstructionsHandler(magikFile); - } - - @Test - void testReadGlobalScopeInstruction() { - final String code = "" - + "# mlint: a=test1\n" - + "_method a.b\n" - + " write(1)\n" - + "_endmethod"; - final LintInstructionsHandler instructionsHandler = this.getInstructions(code); - - final Map instructionsGlobal = instructionsHandler.getInstructionsInScope(1, 0); - assertThat(instructionsGlobal).containsExactly(Map.entry("a", "test1")); - - final Map instructionsMethod = instructionsHandler.getInstructionsInScope(3, 0); - assertThat(instructionsMethod).containsExactly(Map.entry("a", "test1")); - } - - @Test - void testReadMethodScopeInstruction() { - final String code = "" - + "_method a.b\n" - + " # mlint: b=test2\n" - + " write(1)\n" - + "_endmethod"; - final LintInstructionsHandler instructionsHandler = this.getInstructions(code); - - final Map instructionsGlobal = instructionsHandler.getInstructionsInScope(1, 0); - assertThat(instructionsGlobal).isEmpty(); - - final Map instructionsMethod = instructionsHandler.getInstructionsInScope(3, 0); - assertThat(instructionsMethod).containsExactly(Map.entry("b", "test2")); - } - - @Test - void testReadCombinedScopeInstruction() { - final String code = "" - + "# mlint: a=test1\n" - + "_method a.b\n" - + " # mlint: b=test2\n" - + " write(1)\n" - + "_endmethod"; - final LintInstructionsHandler instructionsHandler = this.getInstructions(code); - - final Map instructionsGlobal = instructionsHandler.getInstructionsInScope(1, 0); - assertThat(instructionsGlobal).containsExactly(Map.entry("a", "test1")); - - final Map instructionsMethod = instructionsHandler.getInstructionsInScope(4, 0); - assertThat(instructionsMethod).containsExactly( - Map.entry("a", "test1"), - Map.entry("b", "test2")); - } - - @Test - void testReadLineInstruction() { - final String code = "" - + "_method a.b\n" - + " write(1) # mlint: c=test3\n" - + "_endmethod"; - final LintInstructionsHandler instructionsHandler = this.getInstructions(code); - - final Map instructionsGlobal = instructionsHandler.getInstructionsInScope(1, 0); - assertThat(instructionsGlobal).isEmpty(); - - final Map instructionsMethod = instructionsHandler.getInstructionsInScope(2, 0); - assertThat(instructionsMethod).isEmpty(); - - final Map instructions = instructionsHandler.getInstructionsAtLine(2); - assertThat(instructions).containsExactly(Map.entry("c", "test3")); - } - -} diff --git a/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/MagikFileScannerTest.java b/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/MagikFileScannerTest.java index ce9bf3dd..f34f1032 100644 --- a/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/MagikFileScannerTest.java +++ b/magik-lint/src/test/java/nl/ramsolutions/sw/magik/lint/MagikFileScannerTest.java @@ -15,7 +15,7 @@ class MagikFileScannerTest { /** * VSCode runs from module directory, mvn runs from project directory. * - * @return Proper {{Path}} to use. + * @return Proper {@link Path} to use. */ private Path getPath(String relativePath) { final Path path = Path.of(".").toAbsolutePath().getParent(); diff --git a/magik-squid/pom.xml b/magik-squid/pom.xml index de6edf4e..3cfecc8b 100644 --- a/magik-squid/pom.xml +++ b/magik-squid/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-squid diff --git a/magik-squid/src/main/java/com/sonar/sslr/api/GenericTokenType.java b/magik-squid/src/main/java/com/sonar/sslr/api/GenericTokenType.java new file mode 100644 index 00000000..ea8d39cc --- /dev/null +++ b/magik-squid/src/main/java/com/sonar/sslr/api/GenericTokenType.java @@ -0,0 +1,43 @@ +/* + * SonarSource Language Recognizer + * Copyright (C) 2010-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package com.sonar.sslr.api; + +public enum GenericTokenType implements TokenType { + COMMENT, IDENTIFIER, LITERAL, CONSTANT, EOF, EOL, UNKNOWN_CHAR, + + // Added: + WHITESPACE, + STATEMENT_SEPARATOR; + + @Override + public String getName() { + return name(); + } + + @Override + public String getValue() { + return name(); + } + + @Override + public boolean hasToBeSkippedFromAst(AstNode node) { + return false; + } +} diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/IgnoreHandler.java b/magik-squid/src/main/java/nl/ramsolutions/sw/IgnoreHandler.java similarity index 97% rename from magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/IgnoreHandler.java rename to magik-squid/src/main/java/nl/ramsolutions/sw/IgnoreHandler.java index cd44a171..6b93cff9 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/IgnoreHandler.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/IgnoreHandler.java @@ -1,4 +1,4 @@ -package nl.ramsolutions.sw.magik.languageserver; +package nl.ramsolutions.sw; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -78,7 +78,7 @@ public void removeIgnoreFile(final Path path) { } /** - * Test if {{path}} is ignored. + * Test if {@code path} is ignored. * *

* A file is either ignored when: diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModule.java b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModule.java index f3355832..0a0125e8 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModule.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModule.java @@ -21,7 +21,7 @@ public class SwModule { /** * Constructor. * @param name Name of module. - * @param path Path to {{@code module.def}} file. + * @param path Path to {@code module.def} file. */ public SwModule(final String name, final @Nullable Path path) { this.name = name; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModuleScanner.java b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModuleScanner.java index 4f563993..2a9f648b 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModuleScanner.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwModuleScanner.java @@ -110,7 +110,7 @@ public static SwModule moduleAtPath(final Path startPath) throws IOException { /** * Read module.def file. - * @param path Path to {{@code module.def}} file. + * @param path Path to {@code module.def} file. * @return Parsed module definition. * @throws IOException - */ diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProduct.java b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProduct.java index 9fa5ab54..ff6578dc 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProduct.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProduct.java @@ -37,8 +37,8 @@ public String getName() { } /** - * Get path to {{@code product.def}} file. - * @return Path to {{@code product.def}} file. + * Get path to {@code product.def} file. + * @return Path to {@code product.def} file. */ @CheckForNull public Path getPath() { @@ -46,32 +46,32 @@ public Path getPath() { } /** - * Get child {{@code SwProduct}}s of this product. - * @return Child {{@code SwProduct}}s of this product. + * Get child {@link SwProduct}s of this product. + * @return Child {@link SwProduct}s of this product. */ public Set getChildren() { return Collections.unmodifiableSet(this.children); } /** - * Add a {{@code SwProduct}} to this product. - * @param swProduct {{@code SwProduct}} to add. + * Add a {@link SwProduct} to this product. + * @param swProduct {@link SwProduct} to add. */ public void addChild(final SwProduct swProduct) { this.children.add(swProduct); } /** - * Get {{@code SwModule}}s in this product. - * @return Collection of {{@code SwModule}}s in this product. + * Get {@link SwModule}s in this product. + * @return Collection of {@link SwModule}s in this product. */ public Set getModules() { return Collections.unmodifiableSet(modules); } /** - * Add a {{@code SwModule}} to this product. - * @param swModule {{@code SwModule}} to add. + * Add a {@link SwModule} to this product. + * @param swModule {@link SwModule} to add. */ public void addModule(final SwModule swModule) { this.modules.add(swModule); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProductScanner.java b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProductScanner.java index a5c02a49..ee0b3a29 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProductScanner.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/definitions/SwProductScanner.java @@ -125,7 +125,7 @@ public static SwProduct productForPath(final Path startPath) throws IOException /** * Read product.def file. - * @param path Path to {{@code product.def}} file. + * @param path Path to {@code product.def} file. * @return Parsed product definition. * @throws IOException - */ diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikFile.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikFile.java index 79d5b73f..adaf9254 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikFile.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikFile.java @@ -81,8 +81,8 @@ public String[] getSourceLines() { } /** - * Parse the text for this file and return the top level {{AstNode}}. - * @return Top level {{AstNode}}. + * Parse the text for this file and return the top level {@link AstNode}. + * @return Top level {@link AstNode}. */ public synchronized AstNode getTopNode() { if (this.astNode == null) { @@ -98,8 +98,8 @@ public synchronized AstNode getTopNode() { } /** - * Get the {{GlobalScope}} for this file. - * @return {{GlobalScope}} for this file. + * Get the {@link GlobalScope} for this file. + * @return {@link GlobalScope} for this file. */ public synchronized GlobalScope getGlobalScope() { if (this.globalScope == null) { @@ -113,8 +113,8 @@ public synchronized GlobalScope getGlobalScope() { } /** - * Get {{Definition}}s in this file. - * @return {{Definition}}s in this file. + * Get {@link Definition}s in this file. + * @return {@link Definition}s in this file. */ public synchronized List getDefinitions() { if (this.definitions == null) { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikTypedFile.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikTypedFile.java index 065c3fce..0aca72e4 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikTypedFile.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/MagikTypedFile.java @@ -1,6 +1,5 @@ package nl.ramsolutions.sw.magik; -import com.sonar.sslr.api.AstNode; import java.io.IOException; import java.net.URI; import java.nio.file.Path; @@ -48,22 +47,20 @@ public MagikTypedFile(final Path path, final ITypeKeeper typeKeeper) throws IOEx } /** - * Get the {{ITypeKeeper}} used for the {{LocalTypeReasoner}}. + * Get the {@link ITypeKeeper} used for the {@link LocalTypeReasoner}. */ public ITypeKeeper getTypeKeeper() { return this.typeKeeper; } /** - * Run the (cached) {{LocalTypeReasoner}} and return it. - * @return The used LocalTypeReasoner. + * Run the (cached) {@link LocalTypeReasoner} and return it. + * @return The used {@link LocalTypeReasoner}. */ public synchronized LocalTypeReasoner getTypeReasoner() { if (this.typeReasoner == null) { - final AstNode node = this.getTopNode(); - this.typeReasoner = new LocalTypeReasoner(this); - this.typeReasoner.run(node); + this.typeReasoner.run(); } return this.typeReasoner; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstCompare.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstCompare.java index d989509e..3a0453aa 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstCompare.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstCompare.java @@ -14,8 +14,7 @@ public final class AstCompare { /** * Flags to influence the compare functionality. * Values: - * {{IDENTIFIER_IGNORE_NAME}}: Ignore the name of the IDENTIFIER node. - * {{ONLY_AST}}: Only compare AST, ignoring tokens such as '(' and ')'. + * {@code IDENTIFIER_IGNORE_NAME}: Ignore the name of the IDENTIFIER node. */ enum Flags { IGNORE_IDENTIFIER_NAME, diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstQuery.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstQuery.java index a87bc14d..2cb2d2c7 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstQuery.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstQuery.java @@ -20,10 +20,10 @@ private AstQuery() { } /** - * Get the AstNodes which match a chain of {{AstNodeType}}s. - * @param node {{AstNode}} to query - * @param nodeTypes Chain of {{AstNodeType}}s - * @return {{AstNode}}s which match query + * Get the AstNodes which match a chain of {@link AstNodeType}s. + * @param node {@link AstNode} to query + * @param nodeTypes Chain of {@link AstNodeType}s + * @return {@link AstNode}s which match query */ public static List getChildrenFromChain(final AstNode node, final AstNodeType... nodeTypes) { List nodes = List.of(node); @@ -43,11 +43,11 @@ public static List getChildrenFromChain(final AstNode node, final AstNo } /** - * Get the first AstNode which matches a chain of {{AstNodeTypes}}s. - * Tries to get {{getChildNode()}} on each node, for each {{nodeTypes}}. - * @param node {{AstNode}} to query - * @param nodeTypes Chain of {{AstNodeType}}s - * @return {{AstNode}} which matches query, {{null}} if none is found. + * Get the first AstNode which matches a chain of {@link AstNodeTypes}s. + * Tries to get {@code getChildNode()} on each node, for each {@code nodeTypes}. + * @param node {@link AstNode} to query + * @param nodeTypes Chain of {@link AstNodeType}s + * @return {@link AstNode} which matches query, {@code null} if none is found. */ @CheckForNull public static AstNode getFirstChildFromChain(final AstNode node, final AstNodeType... nodeTypes) { @@ -62,10 +62,10 @@ public static AstNode getFirstChildFromChain(final AstNode node, final AstNodeTy } /** - * Get a child {{AstNode}} from chain, but only if each node has one child and the type. - * @param node {{AstNode}} to query - * @param nodeTypes Chain of {{AstNodeType}}s - * @return {{AstNode}} which matches query, {{null}} if not is found. + * Get a child {@link AstNode} from chain, but only if each node has one child and the type. + * @param node {@link AstNode} to query + * @param nodeTypes Chain of {@link AstNodeType}s + * @return {@link AstNode} which matches query, {@code null} if not is found. */ @CheckForNull public static AstNode getOnlyFromChain(final AstNode node, final AstNodeType... nodeTypes) { @@ -84,10 +84,10 @@ public static AstNode getOnlyFromChain(final AstNode node, final AstNodeType... } /** - * Get a parent {{AstNode}} from chain, but only if each node matches the type. - * @param node {{AstNode}} to query. - * @param nodeTypes Chain of {{AstNodeType}}s. - * @return {{AstNode}} which matches query, {{null}} if not found. + * Get a parent {@link AstNode} from chain, but only if each node matches the type. + * @param node {@link AstNode} to query. + * @param nodeTypes Chain of {@link AstNodeType}s. + * @return {@link AstNode} which matches query, {@code null} if not found. */ @CheckForNull public static AstNode getParentFromChain(final AstNode node, final AstNodeType... nodeTypes) { @@ -103,7 +103,7 @@ public static AstNode getParentFromChain(final AstNode node, final AstNodeType.. } /** - * Get the node in {{topNode}} before {{position}}. + * Get the node in {@code topNode} before {@code position}. * @param topNode Top node. * @param position Position for node. * @return Token-Node before position. @@ -125,7 +125,7 @@ public static AstNode nodeBefore(final AstNode topNode, final Position position) } /** - * Get the node in {{topNode}} at {{position}}. + * Get the node in {@code topNode} at {@code position}. * @param topNode Top node. * @param position Position for node. * @return Token-Node at position. @@ -148,7 +148,7 @@ public static AstNode nodeAt(final AstNode topNode, final Position position) { } /** - * Get the (token) node in {{node}} at {{position}} of a specific type. + * Get the (token) node in {@code node} at {@code position} of a specific type. * @param topNode Top node. * @param position Position for node. * @param nodeTypes Node type to look for. @@ -168,7 +168,7 @@ public static AstNode nodeAt(final AstNode topNode, final Position position, fin } /** - * Get the node in {{topNode}} after {{position}}. + * Get the node in {@code topNode} after {@code position}. * @param topNode Top node. * @param position Position for node. * @return Node after position. @@ -190,7 +190,7 @@ public static AstNode nodeAfter(final AstNode topNode, final Position position) } /** - * Get the {{AstNode}} surrounding {{position}}. + * Get the {@link AstNode} surrounding {@code position}. * @param topNode Top node. * @param position Position to look at. * @return 'Finest' node containing position. @@ -225,7 +225,7 @@ public static AstNode nodeSurrounding(final AstNode topNode, final Position posi } /** - * Get the {{AstNode}} surrounding {{position}} of a specific type. + * Get the {@link AstNode} surrounding {@code position} of a specific type. * @param topNode Top node. * @param position Position to look at. * @param nodeTypes Wanted node types. diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstWalker.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstWalker.java index 5054ddc6..ac08c016 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstWalker.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/AstWalker.java @@ -7,8 +7,8 @@ import nl.ramsolutions.sw.magik.api.MagikGrammar; /** - * A {{AstNode}} tree walker with pre- and post-methods to iterate a parse tree. - * Note that this is generated by the {{generate_ast_walker.py}} script, + * A {@link AstNode} tree walker with pre- and post-methods to iterate a parse tree. + * Note that this is generated by the {@code generate_ast_walker.py} script, * do not edit this file manually! */ public abstract class AstWalker { @@ -26,10 +26,10 @@ protected void walkChildren(final AstNode node) { * Walk trivia and tokens of node. */ protected void walkTokens(final AstNode tokenNode) { - tokenNode.getTokens().forEach(token -> { - token.getTrivia().forEach(this::walkTrivia); - this.walkToken(token); - }); + // Assume there can be only one token. + final Token token = tokenNode.getToken(); + token.getTrivia().forEach(this::walkTrivia); + this.walkToken(token); } /** diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/KeyValueAtLineInstructionExtractor.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/KeyValueAtLineInstructionExtractor.java index 39d54464..4c539c8e 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/KeyValueAtLineInstructionExtractor.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/KeyValueAtLineInstructionExtractor.java @@ -60,8 +60,8 @@ private Map> extractInstructions() { } /** - * Get the instructions at line of {{AstNode}}. - * @param searchNode Line of {{AstNode}} to get instructions from. + * Get the instructions at line of {@link AstNode}. + * @param searchNode Line of {@link AstNode} to get instructions from. * @return Map with key/value instructions at node. */ public Map getInstructions(final AstNode searchNode) { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Location.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Location.java index f79f8f87..ade0b9cd 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Location.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Location.java @@ -50,9 +50,9 @@ public Location(final URI uri, final Range range) { } /** - * Constructor from {{AstNode}}. + * Constructor from {@link AstNode}. * @param uri Path to file. - * @param node {{AstNode}} to create {{Location}} from. + * @param node {@link AstNode} to create {@link Location} from. */ public Location(final URI uri, final AstNode node) { this.uri = uri; @@ -60,9 +60,9 @@ public Location(final URI uri, final AstNode node) { } /** - * Constructor from {{AstNode}}. + * Constructor from {@link AstNode}. * @param uri Path to file. - * @param token {{Token}} to create {{Location}} from. + * @param token {@link Token} to create {@link Location} from. */ public Location(final URI uri, final Token token) { this.uri = uri; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Position.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Position.java index fcfc3cea..b5e88fd2 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Position.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/Position.java @@ -12,7 +12,7 @@ public class Position implements Comparable { private static final String NEWLINE_REGEXP = "(?:\\n|\\r\\n|\\r)"; /** - * Comparator for {{Position}}s. + * Comparator for {@link Position}s. */ public class PositionComparator implements Comparator { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/BinaryOperatorDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/BinaryOperatorDefinition.java index a89cd788..abca506a 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/BinaryOperatorDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/BinaryOperatorDefinition.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.definitions; import com.sonar.sslr.api.AstNode; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Binary operator definition. @@ -8,8 +9,8 @@ public class BinaryOperatorDefinition extends Definition { private final String operator; - private final String lhs; - private final String rhs; + private final TypeString lhs; + private final TypeString rhs; /** * Constructor. @@ -23,9 +24,18 @@ public BinaryOperatorDefinition( final AstNode node, final String pakkage, final String operator, - final String lhs, - final String rhs) { - super(node, pakkage, lhs + " " + operator + " " + rhs); + final TypeString lhs, + final TypeString rhs) { + super(node, TypeString.UNDEFINED); + + if (!lhs.isSingle()) { + throw new IllegalStateException(); + } + + if (!rhs.isSingle()) { + throw new IllegalStateException(); + } + this.operator = operator; this.lhs = lhs; this.rhs = rhs; @@ -35,12 +45,17 @@ public String getOperator() { return this.operator; } - public String getLhs() { + public TypeString getLhs() { return this.lhs; } - public String getRhs() { + public TypeString getRhs() { return this.rhs; } + @Override + public String getName() { + return this.lhs.getFullString() + " " + this.operator + " " + this.rhs.getFullString(); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ConditionDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ConditionDefinition.java new file mode 100644 index 00000000..b8f83abd --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ConditionDefinition.java @@ -0,0 +1,48 @@ +package nl.ramsolutions.sw.magik.analysis.definitions; + +import com.sonar.sslr.api.AstNode; +import java.util.Collections; +import java.util.List; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; + +/** + * Condition definition. + */ +public class ConditionDefinition extends Definition { + + private final String name; + private final String parent; + private final List dataNames; + + /** + * Constructor. + * @param node Node. + * @param name Name. + * @param parent Parent. + * @param dataNames Data name list. + */ + protected ConditionDefinition( + final AstNode node, + final String name, + final String parent, + final List dataNames) { + super(node, TypeString.UNDEFINED); + this.name = name; + this.parent = parent; + this.dataNames = dataNames; + } + + @Override + public String getName() { + return this.name; + } + + public String getParent() { + return this.parent; + } + + public List getDataNames() { + return Collections.unmodifiableList(this.dataNames); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefConditionParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefConditionParser.java new file mode 100644 index 00000000..86d5dc10 --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefConditionParser.java @@ -0,0 +1,122 @@ +package nl.ramsolutions.sw.magik.analysis.definitions; + +import com.sonar.sslr.api.AstNode; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; +import nl.ramsolutions.sw.magik.analysis.helpers.ExpressionNodeHelper; +import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; +import nl.ramsolutions.sw.magik.api.MagikGrammar; + +/** + * Condition definition parser. + */ +public class DefConditionParser { + + private static final String DEFINE_CONDITION = "define_condition()"; + private static final String DEFINE_TOP_CONDITION = "define_top_condition()"; + private static final String CONDITION = "condition"; + private static final String SW_CONDITION = "sw:condition"; + + private final AstNode node; + + /** + * Constructor. + * @param node Condition definition node. + */ + public DefConditionParser(final AstNode node) { + if (node.isNot(MagikGrammar.METHOD_INVOCATION)) { + throw new IllegalArgumentException(); + } + + this.node = node; + } + + /** + * Test if node is condition definition. + * @param node Node. + * @return True if is condition definition, false otherwise. + */ + public static boolean isDefineCondition(final AstNode node) { + final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); + if (!helper.isMethodInvocationOf(DEFINE_CONDITION) + && !helper.isMethodInvocationOf(DEFINE_TOP_CONDITION)) { + return false; + } + + // Some sanity. + final AstNode parentNode = node.getParent(); + final AstNode atomNode = parentNode.getFirstChild(); + if (atomNode.isNot(MagikGrammar.ATOM)) { + return false; + } + final String exemplarName = atomNode.getTokenValue(); // Assume this is an exemplar. + if (!exemplarName.equalsIgnoreCase(CONDITION) + && !exemplarName.equalsIgnoreCase(SW_CONDITION)) { + return false; + } + + final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); + final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); + final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); + final AstNode argument1Node = argumentsHelper.getArgument(1, MagikGrammar.SYMBOL); + final AstNode argument2Node = argumentsHelper.getArgument(2, MagikGrammar.SIMPLE_VECTOR); + return argument0Node != null + && argument1Node != null + && argument2Node != null; + } + + /** + * Parse definition. + * @return List of {@link ConditionDefinition}s. + */ + public List parseDefinitions() { + // Some sanity. + final AstNode parentNode = this.node.getParent(); + final AstNode atomNode = parentNode.getFirstChild(); + if (atomNode.isNot(MagikGrammar.ATOM)) { + throw new IllegalStateException(); + } + final String exemplarName = atomNode.getTokenValue(); + if (!exemplarName.equalsIgnoreCase(CONDITION) + && !exemplarName.equalsIgnoreCase(SW_CONDITION)) { + throw new IllegalStateException(); + } + + final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); + final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); + final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); + final AstNode argument1Node = argumentsHelper.getArgument(1, MagikGrammar.SYMBOL); + final AstNode argument2Node = argumentsHelper.getArgument(2, MagikGrammar.SIMPLE_VECTOR); + if (argument0Node == null + || argument1Node == null + || argument2Node == null) { + throw new IllegalStateException(); + } + + // Figure statement node. + final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); + + // Figure definition. + final String nameSymbol = argument0Node.getTokenValue(); + final String name = nameSymbol.substring(1); + final String parentSymbol = argument1Node.getTokenValue(); + final String parent = parentSymbol.substring(1); + final List dataNames = argument2Node.getChildren(MagikGrammar.EXPRESSION).stream() + .map(expressionNode -> { + final ExpressionNodeHelper expressionNodeHelper = new ExpressionNodeHelper(expressionNode); + final String dataName = expressionNodeHelper.getConstant(); + if (dataName.startsWith(":")) { + return dataName.substring(1); + } + return dataName; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return List.of( + new ConditionDefinition(statementNode, name, parent, dataNames)); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefEnumerationParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefEnumerationParser.java index 26064be3..de0a7bba 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefEnumerationParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefEnumerationParser.java @@ -5,6 +5,7 @@ import java.util.List; import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -14,6 +15,8 @@ public class DefEnumerationParser extends TypeDefParser { private static final String DEF_ENUMERATION_FROM = "def_enumeration_from"; private static final String DEF_ENUMERATION = "def_enumeration"; + private static final String SW_DEF_ENUMERATION_FROM = "sw:def_enumeration_from"; + private static final String SW_DEF_ENUMERATION = "sw:def_enumeration"; /** * Constructor. @@ -35,7 +38,9 @@ public static boolean isDefEnumeration(final AstNode node) { final ProcedureInvocationNodeHelper helper = new ProcedureInvocationNodeHelper(node); if (!helper.isProcedureInvocationOf(DEF_ENUMERATION) - && !helper.isProcedureInvocationOf(DEF_ENUMERATION_FROM)) { + && !helper.isProcedureInvocationOf(DEF_ENUMERATION_FROM) + && !helper.isProcedureInvocationOf(SW_DEF_ENUMERATION) + && !helper.isProcedureInvocationOf(SW_DEF_ENUMERATION_FROM)) { return false; } @@ -66,13 +71,14 @@ public List parseDefinitions() { final String pakkage = this.getCurrentPakkage(); // Figure name. - final String name = argument0Node.getTokenValue().substring(1); + final String identifier = argument0Node.getTokenValue().substring(1); + final TypeString name = TypeString.of(identifier, pakkage); // Figure parents. - final List parents = Collections.emptyList(); + final List parents = Collections.emptyList(); final EnumerationDefinition enumerationDefinition = - new EnumerationDefinition(statementNode, pakkage, name, parents); + new EnumerationDefinition(statementNode, name, parents); return List.of(enumerationDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefIndexedExemplarParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefIndexedExemplarParser.java index ec59f146..04b5ed13 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefIndexedExemplarParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefIndexedExemplarParser.java @@ -4,6 +4,7 @@ import java.util.List; import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -12,6 +13,7 @@ public class DefIndexedExemplarParser extends TypeDefParser { private static final String DEF_INDEXED_EXEMPLAR = "def_indexed_exemplar"; + private static final String SW_DEF_INDEXED_EXEMPLAR = "sw:def_indexed_exemplar"; /** * Constructor. @@ -32,7 +34,8 @@ public static boolean isDefIndexedExemplar(final AstNode node) { } final ProcedureInvocationNodeHelper helper = new ProcedureInvocationNodeHelper(node); - if (!helper.isProcedureInvocationOf(DEF_INDEXED_EXEMPLAR)) { + if (!helper.isProcedureInvocationOf(DEF_INDEXED_EXEMPLAR) + && !helper.isProcedureInvocationOf(SW_DEF_INDEXED_EXEMPLAR)) { return false; } @@ -43,8 +46,7 @@ public static boolean isDefIndexedExemplar(final AstNode node) { if (argument0Node == null) { return false; } - final AstNode argument1Node = argumentsHelper.getArgument(1, MagikGrammar.SIMPLE_VECTOR); - return argument1Node != null; + return true; } /** @@ -61,10 +63,6 @@ public List parseDefinitions() { if (argument0Node == null) { throw new IllegalStateException(); } - final AstNode argument1Node = argumentsHelper.getArgument(1, MagikGrammar.SIMPLE_VECTOR); - if (argument1Node == null) { - throw new IllegalStateException(); - } // Figure statement node. final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); @@ -73,14 +71,15 @@ public List parseDefinitions() { final String pakkage = this.getCurrentPakkage(); // Figure name. - final String name = argument0Node.getTokenValue().substring(1); + final String identifier = argument0Node.getTokenValue().substring(1); + final TypeString name = TypeString.of(identifier, pakkage); // Parents. final AstNode argument2Node = argumentsHelper.getArgument(2); - final List parents = this.extractParents(argument2Node); + final List parents = this.extractParents(argument2Node); final IndexedExemplarDefinition indexedExemplarDefinition = - new IndexedExemplarDefinition(statementNode, pakkage, name, parents); + new IndexedExemplarDefinition(statementNode, name, parents); return List.of(indexedExemplarDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefMixinParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefMixinParser.java index b6a6acfc..dc38682d 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefMixinParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefMixinParser.java @@ -4,6 +4,7 @@ import java.util.List; import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -12,6 +13,7 @@ public class DefMixinParser extends TypeDefParser { private static final String DEF_MIXIN = "def_mixin"; + private static final String SW_DEF_MIXIN = "sw:def_mixin"; /** * Constructor. @@ -32,7 +34,8 @@ public static boolean isDefMixin(final AstNode node) { } final ProcedureInvocationNodeHelper helper = new ProcedureInvocationNodeHelper(node); - if (!helper.isProcedureInvocationOf(DEF_MIXIN)) { + if (!helper.isProcedureInvocationOf(DEF_MIXIN) + && !helper.isProcedureInvocationOf(SW_DEF_MIXIN)) { return false; } @@ -66,13 +69,14 @@ public List parseDefinitions() { final String pakkage = this.getCurrentPakkage(); // Figure name. - final String name = argument0Node.getTokenValue().substring(1); + final String identifier = argument0Node.getTokenValue().substring(1); + final TypeString name = TypeString.of(identifier, pakkage); // Parents. final AstNode argument1Node = argumentsHelper.getArgument(1); - final List parents = this.extractParents(argument1Node); + final List parents = this.extractParents(argument1Node); - final MixinDefinition mixinDefinition = new MixinDefinition(statementNode, pakkage, name, parents); + final MixinDefinition mixinDefinition = new MixinDefinition(statementNode, name, parents); return List.of(mixinDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefPackageParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefPackageParser.java index 801ea415..cfd51142 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefPackageParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefPackageParser.java @@ -5,7 +5,6 @@ import java.util.List; import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ExpressionNodeHelper; -import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; import nl.ramsolutions.sw.magik.api.MagikGrammar; @@ -15,6 +14,7 @@ public class DefPackageParser { private static final String DEF_PACKAGE = "def_package"; + private static final String SW_DEF_PACKAGE = "sw:def_package"; private final AstNode node; @@ -41,7 +41,8 @@ public static boolean isDefPackage(final AstNode node) { } final ProcedureInvocationNodeHelper helper = new ProcedureInvocationNodeHelper(node); - if (!helper.isProcedureInvocationOf(DEF_PACKAGE)) { + if (!helper.isProcedureInvocationOf(DEF_PACKAGE) + && !helper.isProcedureInvocationOf(SW_DEF_PACKAGE)) { return false; } @@ -69,9 +70,6 @@ public List parseDefinitions() { // Figure statement node. final AstNode statementNode = this.node.getFirstAncestor(MagikGrammar.STATEMENT); - // Figure package. - final String pakkage = this.getCurrentPakkage(); - // Figure name. final String name = argument0Node.getTokenValue().substring(1); @@ -95,13 +93,8 @@ public List parseDefinitions() { uses.add("sw"); } - final PackageDefinition packageDefinition = new PackageDefinition(statementNode, pakkage, name, uses); + final PackageDefinition packageDefinition = new PackageDefinition(statementNode, name, uses); return List.of(packageDefinition); } - private String getCurrentPakkage() { - final PackageNodeHelper helper = new PackageNodeHelper(this.node); - return helper.getCurrentPackage(); - } - } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefSlottedExemplarParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefSlottedExemplarParser.java index 0af0b572..4cce716e 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefSlottedExemplarParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefSlottedExemplarParser.java @@ -9,6 +9,7 @@ import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.SimpleVectorNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikOperator; @@ -76,16 +77,20 @@ public List parseDefinitions() { final AstNode statementNode = this.node.getFirstAncestor(MagikGrammar.STATEMENT); // Figure pakkage. - final String pakkage = this.getCurrentPakkage(); + final String currentPakkage = this.getCurrentPakkage(); // Figure name. - final String name = argument0Node.getTokenValue().substring(1); + final String identifier = argument0Node.getTokenValue().substring(1); + final TypeString name = TypeString.of(identifier, currentPakkage); // Figure slots. final List slots = new ArrayList<>(); final List methodDefinitions = new ArrayList<>(); for (final AstNode slotDefNode : argument1Node.getChildren(MagikGrammar.EXPRESSION)) { - final SimpleVectorNodeHelper simpleVectorHelper = SimpleVectorNodeHelper.fromExpression(slotDefNode); + final SimpleVectorNodeHelper simpleVectorHelper = SimpleVectorNodeHelper.fromExpressionSafe(slotDefNode); + if (simpleVectorHelper == null) { + continue; + } final AstNode slotNameNode = simpleVectorHelper.getNth(0, MagikGrammar.SYMBOL); if (slotNameNode == null) { continue; @@ -104,18 +109,19 @@ public List parseDefinitions() { && flavorNode != null) { final String flag = flagNode.getTokenValue(); final String flavor = flavorNode.getTokenValue(); + final TypeString exemplarName = TypeString.of(identifier, currentPakkage); final List slotMethodDefinitions = - this.generateSlotMethods(slotDefNode, pakkage, name, slotName, flag, flavor); + this.generateSlotMethods(slotDefNode, exemplarName, slotName, flag, flavor); methodDefinitions.addAll(slotMethodDefinitions); } } // Parents. final AstNode argument2Node = argumentsHelper.getArgument(2); - final List parents = this.extractParents(argument2Node); + final List parents = this.extractParents(argument2Node); final SlottedExemplarDefinition slottedExemplarDefinition = - new SlottedExemplarDefinition(statementNode, pakkage, name, slots, parents); + new SlottedExemplarDefinition(statementNode, name, slots, parents); final List definitions = new ArrayList<>(); definitions.add(slottedExemplarDefinition); @@ -125,8 +131,7 @@ public List parseDefinitions() { private List generateSlotMethods( final AstNode node, - final String pakkage, - final String exemplarName, + final TypeString exemplarName, final String slotName, final String flag, final String flavor) { @@ -141,7 +146,7 @@ private List generateSlotMethods( } final List getParameters = Collections.emptyList(); final MethodDefinition getMethod = new MethodDefinition( - node, pakkage, exemplarName, getName, getModifiers, getParameters, null); + node, exemplarName, getName, getModifiers, getParameters, null); methodDefinitions.add(getMethod); } else if (flag.equals(FLAG_WRITE) || flag.equals(FLAG_WRITABLE)) { @@ -151,8 +156,8 @@ private List generateSlotMethods( getModifiers.add(MethodDefinition.Modifier.PRIVATE); } final List getParameters = Collections.emptyList(); - final MethodDefinition getMethod = - new MethodDefinition(node, pakkage, exemplarName, slotName, getModifiers, getParameters, null); + final MethodDefinition getMethod = new MethodDefinition( + node, exemplarName, slotName, getModifiers, getParameters, null); methodDefinitions.add(getMethod); // set @@ -165,13 +170,13 @@ private List generateSlotMethods( final ParameterDefinition assignmentParam = new ParameterDefinition(node, "val", ParameterDefinition.Modifier.NONE); final MethodDefinition setMethod = new MethodDefinition( - node, pakkage, exemplarName, setName, setModifiers, setParameters, assignmentParam); + node, exemplarName, setName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(setMethod); // boot final String bootName = slotName + MagikOperator.BOOT_CHEVRON.getValue(); final MethodDefinition bootMethod = new MethodDefinition( - node, pakkage, exemplarName, bootName, setModifiers, setParameters, assignmentParam); + node, exemplarName, bootName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(bootMethod); } return methodDefinitions; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineBinaryOperatorCaseParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineBinaryOperatorCaseParser.java index d37e5d5e..70911f69 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineBinaryOperatorCaseParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineBinaryOperatorCaseParser.java @@ -5,6 +5,7 @@ import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureInvocationNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -13,6 +14,7 @@ public class DefineBinaryOperatorCaseParser { private static final String DEFINE_BINARY_OPERATOR_CASE = "define_binary_operator_case"; + private static final String SW_DEFINE_BINARY_OPERATOR_CASE = "sw:define_binary_operator_case"; private final AstNode node; @@ -39,7 +41,8 @@ public static boolean isBinaryOperatorCase(final AstNode node) { } final ProcedureInvocationNodeHelper helper = new ProcedureInvocationNodeHelper(node); - if (!helper.isProcedureInvocationOf(DEFINE_BINARY_OPERATOR_CASE)) { + if (!helper.isProcedureInvocationOf(DEFINE_BINARY_OPERATOR_CASE) + && !helper.isProcedureInvocationOf(SW_DEFINE_BINARY_OPERATOR_CASE)) { return false; } @@ -100,14 +103,16 @@ public List parseDefinitions() { final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); // Figure pakkage. - final String pakkage = this.getCurrentPakkage(); + final String currentPakkage = this.getCurrentPakkage(); // Figure operator & lhs & rhs. final String operator = operatorSymbol.substring(1); - final String lhs = argument1Node.getTokenValue(); - final String rhs = argument2Node.getTokenValue(); + final String lhsName = argument1Node.getTokenValue(); + final TypeString lhs = TypeString.of(lhsName, currentPakkage); + final String rhsName = argument2Node.getTokenValue(); + final TypeString rhs = TypeString.of(rhsName, currentPakkage); final BinaryOperatorDefinition operatorDefinition = - new BinaryOperatorDefinition(statementNode, pakkage, operator, lhs, rhs); + new BinaryOperatorDefinition(statementNode, currentPakkage, operator, lhs, rhs); return List.of(operatorDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedConstantParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedConstantParser.java index ce759b19..c08556ec 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedConstantParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedConstantParser.java @@ -8,6 +8,7 @@ import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -78,8 +79,8 @@ public List parseDefinitions() { if (atomNode.isNot(MagikGrammar.ATOM)) { throw new IllegalStateException(); } - final String exemplarName = atomNode.getTokenValue(); // Assume this is an exemplar. - if (exemplarName == null) { + final String identifier = atomNode.getTokenValue(); // Assume this is an exemplar. + if (identifier == null) { throw new IllegalStateException(); } @@ -108,8 +109,9 @@ public List parseDefinitions() { modifiers.add(MethodDefinition.Modifier.PRIVATE); } final List parameters = Collections.emptyList(); + final TypeString exemplarName = TypeString.of(identifier, pakkage); final MethodDefinition methodDefinition = - new MethodDefinition(statementNode, pakkage, exemplarName, constantName, modifiers, parameters, null); + new MethodDefinition(statementNode, exemplarName, constantName, modifiers, parameters, null); return List.of(methodDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedVariableParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedVariableParser.java index f0233ea1..47f4d376 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedVariableParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSharedVariableParser.java @@ -9,6 +9,7 @@ import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikOperator; @@ -71,13 +72,13 @@ public static boolean isDefineSharedVariable(final AstNode node) { */ public List parseDefinitions() { // Some sanity. - final AstNode parentNode = node.getParent(); + final AstNode parentNode = this.node.getParent(); final AstNode atomNode = parentNode.getFirstChild(); if (atomNode.isNot(MagikGrammar.ATOM)) { throw new IllegalStateException(); } - final String exemplarName = atomNode.getTokenValue(); // Assume this is an exemplar. - if (exemplarName == null) { + final String identifier = atomNode.getTokenValue(); // Assume this is an exemplar. + if (identifier == null) { throw new IllegalStateException(); } @@ -99,15 +100,15 @@ public List parseDefinitions() { final String variableNameSymbol = argument0Node.getTokenValue(); final String variableName = variableNameSymbol.substring(1); final String flavor = argument2Node.getTokenValue(); + final TypeString exemplarName = TypeString.of(identifier, pakkage); final List methodDefinitions = - this.generateVariableMethods(statementNode, pakkage, exemplarName, variableName, flavor); + this.generateVariableMethods(statementNode, exemplarName, variableName, flavor); return List.copyOf(methodDefinitions); } private List generateVariableMethods( final AstNode definitionNode, - final String pakkage, - final String exemplarName, + final TypeString exemplarName, final String variableName, final String flavor) { final List methodDefinitions = new ArrayList<>(); @@ -119,7 +120,7 @@ private List generateVariableMethods( } final List getParameters = Collections.emptyList(); final MethodDefinition getMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, variableName, getModifiers, getParameters, null); + definitionNode, exemplarName, variableName, getModifiers, getParameters, null); methodDefinitions.add(getMethod); // set @@ -132,13 +133,13 @@ private List generateVariableMethods( final ParameterDefinition assignmentParam = new ParameterDefinition(definitionNode, "val", ParameterDefinition.Modifier.NONE); final MethodDefinition setMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, setName, setModifiers, setParameters, assignmentParam); + definitionNode, exemplarName, setName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(setMethod); // boot final String bootName = variableName + MagikOperator.BOOT_CHEVRON.getValue(); final MethodDefinition bootMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, bootName, setModifiers, setParameters, assignmentParam); + definitionNode, exemplarName, bootName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(bootMethod); return methodDefinitions; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSlotAccessParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSlotAccessParser.java index c913dfbb..4a2bcf8a 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSlotAccessParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefineSlotAccessParser.java @@ -9,17 +9,22 @@ import nl.ramsolutions.sw.magik.analysis.helpers.ArgumentsNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikOperator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * {@code define_slot_access()}}parser. + * {@code define_slot_access()} parser. */ public class DefineSlotAccessParser { + private static final Logger LOGGER = LoggerFactory.getLogger(DefineSlotAccessParser.class); + private static final String DEFINE_SLOT_ACCESS = "define_slot_access()"; - // TODO: define_slot_externally_readable() - // TODO: define_slot_externally_writable() + private static final String DEFINE_SLOT_EXTERNALLY_READABLE = "define_slot_externally_readable()"; + private static final String DEFINE_SLOT_EXTERNALLY_WRITABLE = "define_slot_externally_writable()"; private static final String FLAG_READ = ":read"; private static final String FLAG_READABLE = ":readable"; private static final String FLAG_WRITE = ":write"; @@ -52,7 +57,7 @@ public static boolean isDefineSlotAccess(final AstNode node) { } final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); - if (!helper.isMethodInvocationOf(DEFINE_SLOT_ACCESS)) { + if (!helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_ACCESS)) { return false; } @@ -66,6 +71,7 @@ public static boolean isDefineSlotAccess(final AstNode node) { return false; } + // Arguments: name, flag, optional flavour, owner_name final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); @@ -74,6 +80,71 @@ public static boolean isDefineSlotAccess(final AstNode node) { && argument1Node != null; } + /** + * Test if node is a {@code define_slot_externally_readable()}. + * @param node Node to test + * @return True if node is a {@code define_slot_externally_readable()}, false otherwise. + */ + public static boolean isDefineSlotExternallyReadable(final AstNode node) { + if (node.isNot(MagikGrammar.METHOD_INVOCATION)) { + return false; + } + + final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); + if (!helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_EXTERNALLY_READABLE)) { + return false; + } + + final AstNode parentNode = node.getParent(); + final AstNode atomNode = parentNode.getFirstChild(); + if (atomNode.isNot(MagikGrammar.ATOM)) { + return false; + } + final String exemplarName = atomNode.getTokenValue(); + if (exemplarName == null) { + return false; + } + + // Arguments: name, optional private?, owner_name + // `private?` is actually `flavour`. + final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); + final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); + final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); + return argument0Node != null; + } + + /** + * Test if node is a {@code define_slot_externally_writable()}. + * @param node Node to test + * @return True if node is a {@code define_slot_externally_writable()}, false otherwise. + */ + public static boolean isDefineSlotExternallyWritable(final AstNode node) { + if (node.isNot(MagikGrammar.METHOD_INVOCATION)) { + return false; + } + + final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); + if (!helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_EXTERNALLY_WRITABLE)) { + return false; + } + + final AstNode parentNode = node.getParent(); + final AstNode atomNode = parentNode.getFirstChild(); + if (atomNode.isNot(MagikGrammar.ATOM)) { + return false; + } + final String exemplarName = atomNode.getTokenValue(); + if (exemplarName == null) { + return false; + } + + // Arguments: name, optional flavour, owner_name + final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); + final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); + final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); + return argument0Node != null; + } + /** * Parse defitions. * @return List of parsed definitions. @@ -81,24 +152,34 @@ public static boolean isDefineSlotAccess(final AstNode node) { public List parseDefinitions() { final AstNode argumentsNode = node.getFirstChild(MagikGrammar.ARGUMENTS); final ArgumentsNodeHelper argumentsHelper = new ArgumentsNodeHelper(argumentsNode); + final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); // Some sanity. final AstNode parentNode = node.getParent(); final AstNode atomNode = parentNode.getFirstChild(); if (atomNode.isNot(MagikGrammar.ATOM)) { - throw new IllegalStateException(); + LOGGER.warn( + "Unable to read slot access: {}, at line: {}", helper.getMethodName(), this.node.getTokenLine()); + return Collections.emptyList(); } - final String exemplarName = atomNode.getTokenValue(); - if (exemplarName == null) { - throw new IllegalStateException(); + final String identifier = atomNode.getTokenValue(); + if (identifier == null) { + LOGGER.warn( + "Unable to read slot access: {}, at line: {}", helper.getMethodName(), this.node.getTokenLine()); + return Collections.emptyList(); } final AstNode argument0Node = argumentsHelper.getArgument(0, MagikGrammar.SYMBOL); final AstNode argument1Node = argumentsHelper.getArgument(1, MagikGrammar.SYMBOL); final AstNode argument2Node = argumentsHelper.getArgument(2, MagikGrammar.SYMBOL); - if (argument0Node == null - || argument1Node == null) { - throw new IllegalStateException(); + if (argument0Node == null) { + return Collections.emptyList(); + } + if (helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_ACCESS) + && argument1Node == null) { + LOGGER.warn( + "Unable to read slot access: {}, at line: {}", helper.getMethodName(), this.node.getTokenLine()); + return Collections.emptyList(); } // Figure statement node. @@ -107,21 +188,31 @@ public List parseDefinitions() { // Figure pakkage. final String pakkage = this.getCurrentPakkage(); + // Build methods. final String slotNameSymbol = argument0Node.getTokenValue(); final String slotName = slotNameSymbol.substring(1); - final String flag = argument1Node.getTokenValue(); final String flavor = argument2Node != null ? argument2Node.getTokenValue() : FLAVOR_PUBLIC; // Default is public. + final String flag; + if (helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_EXTERNALLY_READABLE)) { + flag = DefineSlotAccessParser.FLAG_READABLE; + } else if (helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_EXTERNALLY_WRITABLE)) { + flag = DefineSlotAccessParser.FLAG_WRITABLE; + } else if (helper.isMethodInvocationOf(DefineSlotAccessParser.DEFINE_SLOT_ACCESS)) { + flag = argument1Node.getTokenValue(); + } else { + throw new IllegalStateException(); + } + final TypeString exemplarName = TypeString.of(identifier, pakkage); final List methodDefinitions = - this.generateSlotMethods(statementNode, pakkage, exemplarName, slotName, flag, flavor); + this.generateSlotMethods(statementNode, exemplarName, slotName, flag, flavor); return List.copyOf(methodDefinitions); } private List generateSlotMethods( final AstNode definitionNode, - final String pakkage, - final String exemplarName, + final TypeString exemplarName, final String slotName, final String flag, final String flavor) { @@ -136,7 +227,7 @@ private List generateSlotMethods( } final List getParameters = Collections.emptyList(); final MethodDefinition getMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, getName, getModifiers, getParameters, null); + definitionNode, exemplarName, getName, getModifiers, getParameters, null); methodDefinitions.add(getMethod); } else if (flag.equals(FLAG_WRITE) || flag.equals(FLAG_WRITABLE)) { @@ -147,7 +238,7 @@ private List generateSlotMethods( } final List getParameters = Collections.emptyList(); final MethodDefinition getMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, slotName, getModifiers, getParameters, null); + definitionNode, exemplarName, slotName, getModifiers, getParameters, null); methodDefinitions.add(getMethod); // set @@ -160,13 +251,13 @@ private List generateSlotMethods( final ParameterDefinition assignmentParam = new ParameterDefinition(definitionNode, "val", ParameterDefinition.Modifier.NONE); final MethodDefinition setMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, setName, setModifiers, setParameters, assignmentParam); + definitionNode, exemplarName, setName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(setMethod); // boot final String bootName = slotName + MagikOperator.BOOT_CHEVRON.getValue(); final MethodDefinition bootMethod = new MethodDefinition( - definitionNode, pakkage, exemplarName, bootName, setModifiers, setParameters, assignmentParam); + definitionNode, exemplarName, bootName, setModifiers, setParameters, assignmentParam); methodDefinitions.add(bootMethod); } return methodDefinitions; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/Definition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/Definition.java index 5d826fea..83bb2a85 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/Definition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/Definition.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.definitions; import com.sonar.sslr.api.AstNode; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Base class for definitions. @@ -8,18 +9,19 @@ public abstract class Definition { private final AstNode node; - private final String pakkage; - private final String name; + private final TypeString name; /** * Constructor. * @param node Node. - * @param pakkage Package of definition. - * @param name Name of definition. + * @param name Name of definition, if applicable. */ - protected Definition(final AstNode node, final String pakkage, final String name) { + protected Definition(final AstNode node, final TypeString name) { + if (!name.isSingle()) { + throw new IllegalStateException(); + } + this.node = node; - this.pakkage = pakkage; this.name = name; } @@ -36,7 +38,7 @@ public AstNode getNode() { * @return Name of definition. */ public String getName() { - return this.name; + return this.name.getIdentifier(); } /** @@ -44,7 +46,7 @@ public String getName() { * @return Package name. */ public String getPackage() { - return this.pakkage; + return this.name.getPakkage(); } @Override diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReader.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReader.java index ef72ebbb..c91fc867 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReader.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReader.java @@ -66,23 +66,34 @@ protected void walkPostProcedureInvocation(final AstNode node) { @Override protected void walkPostMethodInvocation(final AstNode node) { - // Some more sanity: Directly under top level. final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); if (statementNode != null && statementNode.getParent() != null - && statementNode.getParent().isNot(MagikGrammar.MAGIK)) { - return; + && statementNode.getParent().is(MagikGrammar.MAGIK)) { + // Some more sanity: Directly under top level. + if (DefineSlotAccessParser.isDefineSlotAccess(node) + || DefineSlotAccessParser.isDefineSlotExternallyReadable(node) + || DefineSlotAccessParser.isDefineSlotExternallyWritable(node)) { + this.handleDefineSlotAccess(node); + } else if (DefineSharedVariableParser.isDefineSharedVariable(node)) { + this.handleDefineSharedVariable(node); + } else if (DefineSharedConstantParser.isDefineSharedConstant(node)) { + this.handleDefineSharedConstant(node); + } } - if (DefineSlotAccessParser.isDefineSlotAccess(node)) { - this.handleDefineSlotAccess(node); - } else if (DefineSharedVariableParser.isDefineSharedVariable(node)) { - this.handleDefineSharedVariable(node); - } else if (DefineSharedConstantParser.isDefineSharedConstant(node)) { - this.handleDefineSharedConstant(node); + // Anything goes. + if (DefConditionParser.isDefineCondition(node)) { + this.handleDefineCondition(node); } } + private void handleDefineCondition(final AstNode node) { + final DefConditionParser parser = new DefConditionParser(node); + final List parsedDefinitions = parser.parseDefinitions(); + this.definitions.addAll(parsedDefinitions); + } + private void handleDefPackage(final AstNode node) { final DefPackageParser parser = new DefPackageParser(node); final List parsedDefinitions = parser.parseDefinitions(); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/EnumerationDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/EnumerationDefinition.java index c124545e..2fb4e4ca 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/EnumerationDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/EnumerationDefinition.java @@ -3,30 +3,29 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Enumeration definition. */ public class EnumerationDefinition extends Definition { - private final List parents; + private final List parents; public EnumerationDefinition( final AstNode node, - final String pakkage, - final String name, - final List parents) { - super(node, pakkage, name); + final TypeString name, + final List parents) { + super(node, name); this.parents = List.copyOf(parents); } - public List getParents() { + public List getParents() { return Collections.unmodifiableList(this.parents); } - public GlobalReference getGlobalReference() { - return GlobalReference.of(this.getPackage(), this.getName()); + public TypeString getTypeString() { + return TypeString.of(this.getPackage() + ":" + this.getName()); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinition.java index 9bd94d0e..288e2715 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinition.java @@ -1,14 +1,15 @@ package nl.ramsolutions.sw.magik.analysis.definitions; import com.sonar.sslr.api.AstNode; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Definition of a global. */ public class GlobalDefinition extends Definition { - public GlobalDefinition(final AstNode node, final String pakkage, final String name) { - super(node, pakkage, name); + public GlobalDefinition(final AstNode node, final TypeString name) { + super(node, name); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinitionParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinitionParser.java index cbe44357..6af3c5ad 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinitionParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/GlobalDefinitionParser.java @@ -3,6 +3,7 @@ import com.sonar.sslr.api.AstNode; import java.util.List; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; @@ -49,9 +50,9 @@ public List parseDefinitions() { // Figure name. final AstNode variableDefinitionNode = this.node.getFirstChild(MagikGrammar.VARIABLE_DEFINITION); final AstNode identifierNode = variableDefinitionNode.getFirstChild(MagikGrammar.IDENTIFIER); - final String name = identifierNode.getTokenValue(); - - final GlobalDefinition globalDefinition = new GlobalDefinition(node, pakkage, name); + final String identifier = identifierNode.getTokenValue(); + final TypeString name = TypeString.of(identifier, pakkage); + final GlobalDefinition globalDefinition = new GlobalDefinition(node, name); return List.of(globalDefinition); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/IndexedExemplarDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/IndexedExemplarDefinition.java index 7cef2819..d92cf281 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/IndexedExemplarDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/IndexedExemplarDefinition.java @@ -3,27 +3,29 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Indexed exemplar definition. */ public class IndexedExemplarDefinition extends Definition { - private final List parents; + private final List parents; public IndexedExemplarDefinition( - final AstNode node, final String pakkage, final String name, final List parents) { - super(node, pakkage, name); + final AstNode node, + final TypeString name, + final List parents) { + super(node, name); this.parents = List.copyOf(parents); } - public List getParents() { + public List getParents() { return Collections.unmodifiableList(this.parents); } - public GlobalReference getGlobalReference() { - return GlobalReference.of(this.getPackage(), this.getName()); + public TypeString getTypeString() { + return TypeString.of(this.getPackage() + ":" + this.getName()); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinition.java index 499249e0..f8cbaaa8 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinition.java @@ -6,7 +6,8 @@ import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; +import nl.ramsolutions.sw.magik.api.MagikGrammar; /** * Method definition. @@ -24,7 +25,7 @@ public enum Modifier { } private final Set modifiers; - private final String exemplarName; + private final TypeString exemplarName; private final String methodName; private final List parameters; private final ParameterDefinition assignmentParameter; @@ -32,24 +33,22 @@ public enum Modifier { /** * Constructor. * @param node Node for definition. - * @param pakkage Package defined in. * @param exemplarName Name of exemplar. - * @param name Name of method. + * @param methodName Name of method. * @param modifiers Modifiers for method. * @param parameters Parameters for method. * @param assignmentParameter Assignment parameter. */ public MethodDefinition( final AstNode node, - final String pakkage, - final String exemplarName, - final String name, + final TypeString exemplarName, + final String methodName, final Set modifiers, final List parameters, final @Nullable ParameterDefinition assignmentParameter) { - super(node, pakkage, name.startsWith("[") ? exemplarName + name : exemplarName + "." + name); + super(node, exemplarName); this.exemplarName = exemplarName; - this.methodName = name; + this.methodName = methodName; this.modifiers = Set.copyOf(modifiers); this.parameters = List.copyOf(parameters); this.assignmentParameter = assignmentParameter; @@ -59,7 +58,7 @@ public MethodDefinition( * Get exemplar name. * @return Name of exemplar. */ - public String getExemplarName() { + public TypeString getExemplarName() { return this.exemplarName; } @@ -68,7 +67,14 @@ public String getExemplarName() { * @return Name of method. */ public String getMethodName() { - return methodName; + return this.methodName; + } + + @Override + public String getName() { + return this.methodName.startsWith("[") + ? exemplarName.getIdentifier() + methodName + : exemplarName.getIdentifier() + "." + methodName; } /** @@ -87,6 +93,15 @@ public List getParameters() { return Collections.unmodifiableList(this.parameters); } + /** + * Test if method definition is an actual {@code _method ... _endmethod}, or a + * shared constant/variable/slot accessor. + * @return True if actual method, false otherwise. + */ + public boolean isActualMethodDefinition() { + return this.getNode().is(MagikGrammar.METHOD_DEFINITION); + } + /** * Get assignment parameter. * @return Assignment parameter. @@ -96,8 +111,4 @@ public ParameterDefinition getAssignmentParameter() { return this.assignmentParameter; } - public GlobalReference getTypeGlobalReference() { - return GlobalReference.of(this.getPackage(), this.getExemplarName()); - } - } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinitionParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinitionParser.java index e8497dfb..88ab2765 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinitionParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MethodDefinitionParser.java @@ -9,8 +9,8 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper; -import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ParameterNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -43,12 +43,9 @@ public List parseDefinitions() { return List.of(); } - // Figure pakkage. - final String pakkage = this.getCurrentPakkage(); - // Figure exemplar name & method name. final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(this.node); - final String exemplarName = helper.getExemplarName(); + final TypeString exemplarName = helper.getTypeString(); final String methodName = helper.getMethodName(); // Figure modifers. @@ -72,15 +69,10 @@ public List parseDefinitions() { this.createAssignmentParameterDefinition(assignmentParameterNode); final MethodDefinition methodDefinition = new MethodDefinition( - this.node, pakkage, exemplarName, methodName, modifiers, parameters, assignmentParamter); + this.node, exemplarName, methodName, modifiers, parameters, assignmentParamter); return List.of(methodDefinition); } - private String getCurrentPakkage() { - final PackageNodeHelper helper = new PackageNodeHelper(this.node); - return helper.getCurrentPackage(); - } - private List createParameterDefinitions(final @Nullable AstNode parametersNode) { if (parametersNode == null) { return Collections.emptyList(); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MixinDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MixinDefinition.java index c2b67032..fc4829e0 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MixinDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/MixinDefinition.java @@ -3,26 +3,29 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Mixin definition. */ public class MixinDefinition extends Definition { - private final List parents; + private final List parents; - protected MixinDefinition(final AstNode node, final String pakkage, final String name, final List parents) { - super(node, pakkage, name); + protected MixinDefinition( + final AstNode node, + final TypeString name, + final List parents) { + super(node, name); this.parents = List.copyOf(parents); } - public List getParents() { + public List getParents() { return Collections.unmodifiableList(this.parents); } - public GlobalReference getGlobalReference() { - return GlobalReference.of(this.getPackage(), this.getName()); + public TypeString getTypeString() { + return TypeString.of(this.getPackage() + ":" + this.getName()); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/PackageDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/PackageDefinition.java index 0541b921..2ec48356 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/PackageDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/PackageDefinition.java @@ -3,20 +3,33 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Package definition. */ public class PackageDefinition extends Definition { + private String name; private final List uses; - public PackageDefinition( - final AstNode node, final String pakkage, final String name, final List uses) { - super(node, pakkage, name); + /** + * Constructor. + * @param node Node of package definition. + * @param name Name of package. + * @param uses Uses by package. + */ + public PackageDefinition(final AstNode node, final String name, final List uses) { + super(node, TypeString.UNDEFINED); + this.name = name; this.uses = List.copyOf(uses); } + @Override + public String getName() { + return this.name; + } + public List getUses() { return Collections.unmodifiableList(this.uses); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ParameterDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ParameterDefinition.java index 26304b99..9ffd4e86 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ParameterDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/ParameterDefinition.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.definitions; import com.sonar.sslr.api.AstNode; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Parameter definition. @@ -19,10 +20,18 @@ public enum Modifier { GATHER, } + private final String name; private final Modifier modifier; + /** + * Constructor. + * @param node Node of parameter. + * @param name Name of parameter. + * @param modifier Modifier of parameter. + */ protected ParameterDefinition(final AstNode node, final String name, final Modifier modifier) { - super(node, "", name); + super(node, TypeString.UNDEFINED); + this.name = name; this.modifier = modifier; } @@ -30,4 +39,9 @@ public Modifier getModifier() { return this.modifier; } + @Override + public String getName() { + return this.name; + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/SlottedExemplarDefinition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/SlottedExemplarDefinition.java index 26fb3314..fbe93fe9 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/SlottedExemplarDefinition.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/SlottedExemplarDefinition.java @@ -3,7 +3,7 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Slotted exemplar definition. @@ -34,23 +34,21 @@ public String getName() { } private final List slots; - private final List parents; + private final List parents; /** * Constructor. * @param node Node for definition. - * @param pakkage Package defined in. * @param name Name of slotted exemplar. * @param slots Slots of slotted exemplar. * @param parents Parents of slotted exemplar. */ public SlottedExemplarDefinition( final AstNode node, - final String pakkage, - final String name, + final TypeString name, final List slots, - final List parents) { - super(node, pakkage, name); + final List parents) { + super(node, name); this.slots = List.copyOf(slots); this.parents = List.copyOf(parents); } @@ -59,12 +57,12 @@ public List getSlots() { return Collections.unmodifiableList(this.slots); } - public List getParents() { + public List getParents() { return Collections.unmodifiableList(this.parents); } - public GlobalReference getGlobalReference() { - return GlobalReference.of(this.getPackage(), this.getName()); + public TypeString getTypeString() { + return TypeString.of(this.getPackage() + ":" + this.getName()); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/TypeDefParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/TypeDefParser.java index fdb8d376..d2ac5174 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/TypeDefParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/definitions/TypeDefParser.java @@ -9,6 +9,7 @@ import nl.ramsolutions.sw.magik.analysis.AstQuery; import nl.ramsolutions.sw.magik.analysis.helpers.ExpressionNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -53,32 +54,25 @@ protected TypeDefParser(final AstNode node) { * @param definitionNode Definition node. * @return List of parents. */ - protected List extractParents(@Nullable AstNode definitionNode) { + protected List extractParents(final @Nullable AstNode definitionNode) { if (definitionNode == null) { return Collections.emptyList(); } - final List parents = new ArrayList<>(); + final List parents = new ArrayList<>(); final ExpressionNodeHelper expressionHelper = new ExpressionNodeHelper(definitionNode); final String singleParent = expressionHelper.getConstant(); final AstNode multiParentNode = AstQuery.getOnlyFromChain(definitionNode, MagikGrammar.ATOM, MagikGrammar.SIMPLE_VECTOR); if (singleParent != null) { - final String parent = singleParent.startsWith(":") || singleParent.startsWith("@") - ? singleParent.substring(1) - : singleParent; - + final TypeString parent = this.getFullParent(singleParent); parents.add(parent); } else if (multiParentNode != null) { for (final AstNode parentNode : multiParentNode.getChildren(MagikGrammar.EXPRESSION)) { final ExpressionNodeHelper parentExpressionHelper = new ExpressionNodeHelper(parentNode); final String parentStr = parentExpressionHelper.getConstant(); Objects.requireNonNull(parentStr); - - final String parent = parentStr.startsWith(":") || parentStr.startsWith("@") - ? parentStr.substring(1) - : parentStr; - + final TypeString parent = this.getFullParent(parentStr); parents.add(parent); } } @@ -90,4 +84,13 @@ protected String getCurrentPakkage() { return helper.getCurrentPackage(); } + private TypeString getFullParent(final String parentStr) { + final String parent = parentStr.startsWith(":") || parentStr.startsWith("@") + ? parentStr.substring(1) + : parentStr; + return parent.contains(":") + ? TypeString.of(parent) + : TypeString.of(this.getCurrentPakkage() + ":" + parent); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/generate_ast_walker.py b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/generate_ast_walker.py index a1c68c3b..e5d39539 100755 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/generate_ast_walker.py +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/generate_ast_walker.py @@ -32,8 +32,8 @@ def to_java_name(element): import nl.ramsolutions.sw.magik.api.MagikGrammar; /** - * A {{AstNode}} tree walker with pre- and post-methods to iterate a parse tree. - * Note that this is generated by the {{generate_ast_walker.py}} script, + * A {@link AstNode} tree walker with pre- and post-methods to iterate a parse tree. + * Note that this is generated by the {@code generate_ast_walker.py} script, * do not edit this file manually! */ public abstract class AstWalker { @@ -51,10 +51,10 @@ def to_java_name(element): * Walk trivia and tokens of node. */ protected void walkTokens(final AstNode tokenNode) { - tokenNode.getTokens().forEach(token -> { - token.getTrivia().forEach(this::walkTrivia); - this.walkToken(token); - }); + // Assume there can be only one token. + final Token token = tokenNode.getToken(); + token.getTrivia().forEach(this::walkTrivia); + this.walkToken(token); } /** diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodDefinitionNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodDefinitionNodeHelper.java index b7503778..2bddd623 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodDefinitionNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodDefinitionNodeHelper.java @@ -7,7 +7,7 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; import nl.ramsolutions.sw.magik.api.MagikOperator; @@ -42,10 +42,10 @@ public String getMethodName() { ? parametersNode.getChildren(MagikGrammar.PARAMETER) : Collections.emptyList(); - final List identifierNodes = node.getChildren(MagikGrammar.IDENTIFIER); + final AstNode methodNameNode = node.getFirstChild(MagikGrammar.METHOD_NAME); final StringBuilder builder = new StringBuilder(); - if (identifierNodes.size() > 1) { - final String tokenValue = identifierNodes.get(1).getTokenValue(); + if (methodNameNode != null) { + final String tokenValue = methodNameNode.getTokenValue(); builder.append(tokenValue); } if (parametersNode != null) { @@ -70,45 +70,37 @@ public String getMethodName() { return builder.toString(); } - /** - * Get exemplar name. - * @return Exemplar name. - */ - public String getExemplarName() { - final List identifierNodes = this.node.getChildren(MagikGrammar.IDENTIFIER); - return identifierNodes.get(0).getTokenValue(); - } - /** * Get exemplar + method name. * @return Exemplar + method name. */ public String getExemplarMethodName() { - final String exemplarName = this.getExemplarName(); + final TypeString exemplarName = this.getTypeString(); final String methodName = this.getMethodName(); if (methodName.startsWith("[")) { - return exemplarName + methodName; + return exemplarName.getIdentifier() + methodName; } - return exemplarName + "." + methodName; + return exemplarName.getIdentifier() + "." + methodName; } /** * Get global reference to type the method is defined on. - * @return GlobalReference to type. + * @return TypeString to type. */ - public GlobalReference getTypeGlobalReference() { + public TypeString getTypeString() { final PackageNodeHelper packageHelper = new PackageNodeHelper(this.node); - final String pakkageName = packageHelper.getCurrentPackage(); - final String exemplarName = this.getExemplarName(); - return GlobalReference.of(pakkageName, exemplarName); + final String pakkage = packageHelper.getCurrentPackage(); + final AstNode exemplarNameNode = this.node.getFirstChild(MagikGrammar.EXEMPLAR_NAME); + final String exemplarName = exemplarNameNode.getTokenValue(); + return TypeString.of(exemplarName, pakkage); } /** * Get package + exemplar + method name. * @return Package + exemplar + method name. */ - public String getPakkageExemplarMethodName() { + public String getFullExemplarMethodName() { final PackageNodeHelper packageHelper = new PackageNodeHelper(this.node); final String pakkageName = packageHelper.getCurrentPackage(); return pakkageName + ":" + this.getExemplarMethodName(); @@ -166,6 +158,32 @@ public boolean isIterMethod() { .anyMatch(modifierNode -> modifierNode.getTokenValue().equalsIgnoreCase(modifier)); } + /** + * Test if method returns anything. + * @return + */ + public boolean returnsAnything() { + final List returnStatementNodes = node.getDescendants(MagikGrammar.RETURN_STATEMENT); + final boolean hasReturn = returnStatementNodes.stream() + .filter(statementNode -> statementNode.getFirstAncestor(MagikGrammar.PROCEDURE_DEFINITION) == null) + .anyMatch(statementNode -> statementNode.hasDescendant(MagikGrammar.TUPLE)); + + final List emitStatementNodes = + node.getFirstChild(MagikGrammar.BODY).getChildren(MagikGrammar.EMIT_STATEMENT); + final boolean hasEmit = !emitStatementNodes.isEmpty(); + + return hasReturn || hasEmit; + } + + /** + * Test if method has a loopbody statement. + * @return + */ + public boolean hasLoopbody() { + return node.getDescendants(MagikGrammar.LOOPBODY).stream() + .anyMatch(statementNode -> statementNode.getFirstAncestor(MagikGrammar.PROCEDURE_DEFINITION) == null); + } + private boolean anyChildTokenIs(final AstNode parentNode, final MagikOperator magikOperator) { return parentNode.getChildren().stream() .filter(childNode -> childNode.isNot(MagikGrammar.values())) diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodInvocationNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodInvocationNodeHelper.java index 714a11c9..6ea81b75 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodInvocationNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/MethodInvocationNodeHelper.java @@ -3,6 +3,7 @@ import com.sonar.sslr.api.AstNode; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikOperator; import nl.ramsolutions.sw.magik.api.MagikPunctuator; @@ -73,6 +74,21 @@ public String getMethodName() { return methodName; } + /** + * Get ARGUMENTS -> ARGUMENT -> EXPRESSION nodes. + * @return + */ + public List getArgumentExpressionNodes() { + final AstNode argumentsNode = this.node.getFirstChild(MagikGrammar.ARGUMENTS); + if (argumentsNode == null) { + return Collections.emptyList(); + } + + return argumentsNode.getChildren(MagikGrammar.ARGUMENT).stream() + .map(argNode -> argNode.getFirstChild()) + .collect(Collectors.toList()); + } + private boolean anyChildTokenIs(AstNode parentNode, MagikOperator magikOperator) { return parentNode.getChildren().stream() .filter(childNode -> childNode.isNot(MagikGrammar.values())) diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/PackageNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/PackageNodeHelper.java index 322533ee..3056a4ee 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/PackageNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/PackageNodeHelper.java @@ -34,8 +34,8 @@ public String getCurrentPackage() { topNode = topNode.getParent(); } - // Try to find via siblings. - AstNode siblingNode = topNode.getPreviousSibling(); + // Try to find PACKAGE_SPECIFICATION node via previous siblings. + AstNode siblingNode = topNode; while (siblingNode != null) { if (siblingNode.is(MagikGrammar.PACKAGE_SPECIFICATION)) { final AstNode identifierNode = siblingNode.getFirstChild(MagikGrammar.PACKAGE_IDENTIFIER); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ParameterNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ParameterNodeHelper.java index 10e13e11..70db97f5 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ParameterNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ParameterNodeHelper.java @@ -34,6 +34,9 @@ public boolean isOptionalParameter() { if (this.hasModifier(MagikKeyword.GATHER)) { return false; } + if (this.node.getParent().is(MagikGrammar.ASSIGNMENT_PARAMETER)) { + return false; + } final AstNode parametersNode = this.node.getParent(); if (parametersNode.isNot(MagikGrammar.PARAMETERS)) { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ProcedureDefinitionNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ProcedureDefinitionNodeHelper.java index bd73ead5..95fe3626 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ProcedureDefinitionNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/ProcedureDefinitionNodeHelper.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; +import nl.ramsolutions.sw.magik.analysis.typing.types.ProcedureInstance; import nl.ramsolutions.sw.magik.api.MagikGrammar; /** @@ -42,4 +43,17 @@ public Map getParameterNodes() { parameterNode -> parameterNode)); } + /** + * Get procedure name. + * @return Name of the procedure. + */ + public String getProcedureName() { + final AstNode nameNode = node.getFirstChild(MagikGrammar.PROCEDURE_NAME); + if (nameNode == null) { + return ProcedureInstance.ANONYMOUS_PROCEDURE; + } + final AstNode labelNode = nameNode.getFirstChild(MagikGrammar.LABEL); + return labelNode.getLastChild().getTokenOriginalValue(); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/SimpleVectorNodeHelper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/SimpleVectorNodeHelper.java index dc3ba756..3813eddb 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/SimpleVectorNodeHelper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/helpers/SimpleVectorNodeHelper.java @@ -27,13 +27,22 @@ public SimpleVectorNodeHelper(final AstNode node) { /** * Helper method to get helper from EXPRESSION node. */ - public static SimpleVectorNodeHelper fromExpression(final AstNode expressionNode) { + @CheckForNull + public static SimpleVectorNodeHelper fromExpressionSafe(final AstNode expressionNode) { if (!expressionNode.is(MagikGrammar.EXPRESSION)) { - throw new IllegalArgumentException(); + return null; } final AstNode atomNode = expressionNode.getFirstChild(MagikGrammar.ATOM); + if (atomNode == null) { + return null; + } + final AstNode simpleVectorNode = atomNode.getFirstChild(MagikGrammar.SIMPLE_VECTOR); + if (simpleVectorNode == null) { + return null; + } + return new SimpleVectorNodeHelper(simpleVectorNode); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/GlobalScope.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/GlobalScope.java index d65c1233..521c8a7e 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/GlobalScope.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/GlobalScope.java @@ -1,9 +1,11 @@ package nl.ramsolutions.sw.magik.analysis.scope; import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Token; import java.util.HashMap; import java.util.Map; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.AstQuery; import nl.ramsolutions.sw.magik.api.MagikGrammar; @@ -30,7 +32,10 @@ public Scope getGlobalScope() { @Override public ScopeEntry addDeclaration( - final ScopeEntry.Type type, final String identifier, final AstNode node, final ScopeEntry parentEntry) { + final ScopeEntry.Type type, + final String identifier, + final AstNode node, + final @Nullable ScopeEntry parentEntry) { final ScopeEntry scopeEntry = new ScopeEntry(type, identifier, node, parentEntry); this.scopeEntries.put(identifier, scopeEntry); return scopeEntry; @@ -43,6 +48,12 @@ public ScopeEntry addDeclaration( */ @CheckForNull public Scope getScopeForNode(final AstNode node) { + // Try node directly, perhaps we're lucky. + final Scope scope = this.scopeIndex.get(node); + if (scope != null) { + return scope; + } + AstNode searchNode = node; // Do some helping. if (node.is(MagikGrammar.PARAMETER) @@ -81,12 +92,15 @@ public int getStartColumn() { @Override public int getEndLine() { - return Integer.MAX_VALUE; + final AstNode lastChild = this.getNode().getLastChild(); + return lastChild.getTokenLine(); } @Override public int getEndColumn() { - return Integer.MAX_VALUE; + final AstNode lastChild = this.getNode().getLastChild(); + final Token token = lastChild.getToken(); + return token.getColumn() + token.getOriginalValue().length(); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ProcedureScope.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ProcedureScope.java index ebdc30ec..05d99a79 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ProcedureScope.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ProcedureScope.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.scope; import com.sonar.sslr.api.AstNode; +import javax.annotation.Nullable; /** * Procedure-/method-definition scope. @@ -29,7 +30,7 @@ public ScopeEntry addDeclaration( final ScopeEntry.Type type, final String identifier, final AstNode node, - final ScopeEntry parentEntry) { + final @Nullable ScopeEntry parentEntry) { final ScopeEntry scopeEntry = new ScopeEntry(type, identifier, node, parentEntry); this.scopeEntries.put(identifier, scopeEntry); return scopeEntry; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/Scope.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/Scope.java index 6faf9fdc..7a8654d9 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/Scope.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/Scope.java @@ -55,8 +55,8 @@ void addChildScope(final Scope childScope) { } /** - * Get {{AstNode}} where scope begins. - * @return {{AstNode}} where scope begins + * Get {@link AstNode} where scope begins. + * @return {@link AstNode} where scope begins */ public AstNode getNode() { return this.node; @@ -164,6 +164,16 @@ public ScopeEntry addDeclaration( return scopeEntry; } + /** + * Get a ScopeEntry by its identifier. + * @param identifier Identifier of the ScopeEntry. + * @return Scope entry by identifier. + */ + @CheckForNull + public ScopeEntry getLocalScopeEntry(final String identifier) { + return this.scopeEntries.get(identifier); + } + /** * Get a ScopeEntry by its identifier. * @param identifier Identifier of the ScopeEntry. @@ -243,10 +253,10 @@ public int getEndColumn() { } /** - * Get the most specific {{Scope}} at {{line}}/{{column}}. + * Get the most specific {@link Scope} at {@code line}/{@code column}. * @param line Line to target. * @param column Column to target. - * @return Scope, if any, at {{line}}/{{column}}. + * @return Scope, if any, at {@code line}/{@code column}. */ @Nullable public Scope getScopeForLineColumn(final int line, final int column) { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitor.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitor.java index b75fac3f..b63b3aa1 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitor.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitor.java @@ -24,7 +24,7 @@ public class ScopeBuilderVisitor extends MagikVisitor { /** * Current scope. */ - private Scope scope; + private Scope currentScope; /** * Scope index for quick searching. @@ -32,7 +32,7 @@ public class ScopeBuilderVisitor extends MagikVisitor { private final Map scopeIndex = new HashMap<>(); /** - * Get the {{GlobalScope}}. + * Get the {@link GlobalScope}. * @return Global scope */ public GlobalScope getGlobalScope() { @@ -42,7 +42,7 @@ public GlobalScope getGlobalScope() { @Override protected void walkPreMagik(final AstNode node) { this.globalScope = new GlobalScope(this.scopeIndex, node); - this.scope = this.globalScope; + this.currentScope = this.globalScope; this.scopeIndex.put(node, this.globalScope); } @@ -62,16 +62,16 @@ protected void walkPreBody(final AstNode node) { this.walkPreBodyRegular(node); } - this.scopeIndex.put(node, this.scope); + this.scopeIndex.put(node, this.currentScope); } private void walkPreBodyRegular(final AstNode node) { // regular scope - this.scope = new BodyScope(this.scope, node); + this.currentScope = new BodyScope(this.currentScope, node); } private void walkPreBodyLoop(final AstNode node) { - this.scope = new BodyScope(scope, node); + this.currentScope = new BodyScope(this.currentScope, node); // add for-items to scope final AstNode forNode = AstQuery.getParentFromChain( @@ -85,15 +85,15 @@ private void walkPreBodyLoop(final AstNode node) { MagikGrammar.FOR_VARIABLES, MagikGrammar.IDENTIFIERS_WITH_GATHER, MagikGrammar.IDENTIFIER); - for (final AstNode identifierNode: identifierNodes) { + for (final AstNode identifierNode : identifierNodes) { final String identifier = identifierNode.getTokenValue(); - this.scope.addDeclaration(ScopeEntry.Type.LOCAL, identifier, identifierNode, null); + this.currentScope.addDeclaration(ScopeEntry.Type.LOCAL, identifier, identifierNode, null); } } } private void walkPreBodyWhen(final AstNode node, final AstNode parentNode) { - this.scope = new BodyScope(scope, node); + this.currentScope = new BodyScope(currentScope, node); // add _with items to scope final AstNode tryNode = parentNode.getParent(); @@ -101,7 +101,7 @@ private void walkPreBodyWhen(final AstNode node, final AstNode parentNode) { if (tryVariableNode != null) { final AstNode identifierNode = tryVariableNode.getFirstChild(MagikGrammar.IDENTIFIER); final String identifier = identifierNode.getTokenValue(); - this.scope.addDeclaration(ScopeEntry.Type.LOCAL, identifier, identifierNode, null); + this.currentScope.addDeclaration(ScopeEntry.Type.LOCAL, identifier, identifierNode, null); // Don't add identifierNode to scope index, // as this identifier can have multiple scopes (multiple _when). @@ -109,7 +109,7 @@ private void walkPreBodyWhen(final AstNode node, final AstNode parentNode) { } private void walkPreBodyMethodProcDefinition(final AstNode node, final AstNode parentNode) { - this.scope = new ProcedureScope(this.scope, node); + this.currentScope = new ProcedureScope(this.currentScope, node); // Add all parameters to scope. parentNode.getChildren(MagikGrammar.PARAMETERS, MagikGrammar.ASSIGNMENT_PARAMETER).stream() @@ -117,9 +117,9 @@ private void walkPreBodyMethodProcDefinition(final AstNode node, final AstNode p .forEach(parameterNode -> { final AstNode identifierNode = parameterNode.getFirstChild(MagikGrammar.IDENTIFIER); final String identifier = identifierNode.getTokenValue(); - this.scope.addDeclaration(ScopeEntry.Type.PARAMETER, identifier, parameterNode, null); + this.currentScope.addDeclaration(ScopeEntry.Type.PARAMETER, identifier, identifierNode, null); - this.scopeIndex.put(parameterNode, this.scope); + this.scopeIndex.put(identifierNode, this.currentScope); }); } @@ -143,7 +143,7 @@ protected void walkPreVariableDefinitionStatement(final AstNode node) { .filter(identifierNode -> { // Don't overwrite entries. final String identifier = identifierNode.getTokenValue(); - return this.scope.getScopeEntry(identifier) == null; + return this.currentScope.getLocalScopeEntry(identifier) == null; }) .forEach(identifierNode -> { final String identifier = identifierNode.getTokenValue(); @@ -171,10 +171,10 @@ protected void walkPreVariableDefinitionStatement(final AstNode node) { parentEntry = null; } - this.scope.addDeclaration(scopeEntryType, identifier, identifierNode, parentEntry); + this.currentScope.addDeclaration(scopeEntryType, identifier, identifierNode, parentEntry); // ParentEntry gets a usage via the constructor of the added declaration. - this.scopeIndex.put(identifierNode, this.scope); + this.scopeIndex.put(identifierNode, this.currentScope); }); } @@ -189,12 +189,12 @@ protected void walkPreMultipleAssignmentStatement(final AstNode node) { .filter(Objects::nonNull) .forEach(identifierNode -> { final String identifier = identifierNode.getTokenValue(); - if (this.scope.getScopeEntry(identifier) != null) { + if (this.currentScope.getScopeEntry(identifier) != null) { // Don't overwrite entries. return; } - this.scope.addDeclaration(ScopeEntry.Type.DEFINITION, identifier, identifierNode, null); + this.currentScope.addDeclaration(ScopeEntry.Type.DEFINITION, identifier, identifierNode, null); }); } @@ -220,13 +220,13 @@ protected void walkPreAssignmentExpression(final AstNode node) { } final String identifier = identifierNode.getTokenValue(); - if (this.scope.getScopeEntry(identifier) != null) { + if (this.currentScope.getScopeEntry(identifier) != null) { // Don't overwrite entries. return; } // add as definition - this.scope.addDeclaration(ScopeEntry.Type.DEFINITION, identifier, identifierNode, null); + this.currentScope.addDeclaration(ScopeEntry.Type.DEFINITION, identifier, identifierNode, null); } } @@ -238,7 +238,7 @@ protected void walkPreAtom(final AstNode node) { } final String identifier = identifierNode.getTokenValue(); - final ScopeEntry existingScopeEntry = this.scope.getScopeEntry(identifier); + final ScopeEntry existingScopeEntry = this.currentScope.getScopeEntry(identifier); if (existingScopeEntry != null) { if (existingScopeEntry.getNode() != identifierNode) { // Prevent using ourselves. @@ -250,14 +250,15 @@ protected void walkPreAtom(final AstNode node) { } // Add as global, and use directly. - final ScopeEntry entry = this.globalScope.addDeclaration(ScopeEntry.Type.GLOBAL, identifier, node, null); + final ScopeEntry entry = + this.currentScope.addDeclaration(ScopeEntry.Type.GLOBAL, identifier, identifierNode, null); entry.addUsage(node); } @Override protected void walkPostBody(final AstNode node) { // pop current scope - this.scope = this.scope.getParentScope(); + this.currentScope = this.currentScope.getParentScope(); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeEntry.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeEntry.java index 0961ab05..66098b06 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeEntry.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeEntry.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -68,8 +69,9 @@ public Type getType() { return this.type; } - public boolean isType(final Type isType) { - return this.type == isType; + public boolean isType(final Type... isType) { + return Stream.of(isType) + .anyMatch(wantedType -> wantedType == this.type); } public String getIdentifier() { @@ -119,15 +121,10 @@ public int hashCode() { @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder - .append(this.getClass().getSimpleName(), '@', System.identityHashCode(this)) - .append('(') - .append(this.getIdentifier()) - .append(',') - .append(this.getType()) - .append(')'); - return builder.toString(); + return String.format( + "%s@%s(%s,%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getIdentifier(), this.getType()); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/BinaryOperator.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/BinaryOperator.java index 73653db0..f7562483 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/BinaryOperator.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/BinaryOperator.java @@ -1,7 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.typing; import java.util.Objects; -import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Binary operator. @@ -71,25 +71,25 @@ public static Operator valueFor(final String value) { } private final Operator operator; - private final AbstractType leftType; - private final AbstractType rightType; - private final AbstractType resultType; + private final TypeString leftRef; + private final TypeString rightRef; + private final TypeString resultRef; /** * Constructor. * @param operator Operator name. - * @param leftType Left {{MagikType}}. - * @param rightType Right {{MagikType}}. + * @param leftRef Left {@link MagikType}. + * @param rightRef Right {@link MagikType}. */ public BinaryOperator( final Operator operator, - final AbstractType leftType, - final AbstractType rightType, - final AbstractType resultType) { + final TypeString leftRef, + final TypeString rightRef, + final TypeString resultRef) { this.operator = operator; - this.leftType = leftType; - this.rightType = rightType; - this.resultType = resultType; + this.leftRef = leftRef; + this.rightRef = rightRef; + this.resultRef = resultRef; } /** @@ -104,24 +104,24 @@ public Operator getOperator() { * Get left type. * @return Left type. */ - public AbstractType getLeftType() { - return this.leftType; + public TypeString getLeftType() { + return this.leftRef; } /** * Get right type. * @return Right type. */ - public AbstractType getRightType() { - return this.rightType; + public TypeString getRightType() { + return this.rightRef; } /** * Get result type. * @return Result type. */ - public AbstractType getResultType() { - return resultType; + public TypeString getResultType() { + return resultRef; } @Override @@ -140,14 +140,14 @@ public boolean equals(final Object obj) { final BinaryOperator other = (BinaryOperator) obj; return Objects.equals(this.operator, other.operator) - && Objects.equals(this.leftType, other.leftType) - && Objects.equals(this.rightType, other.rightType) - && Objects.equals(this.resultType, other.resultType); + && Objects.equals(this.leftRef, other.leftRef) + && Objects.equals(this.rightRef, other.rightRef) + && Objects.equals(this.resultRef, other.resultRef); } @Override public int hashCode() { - return Objects.hash(this.operator, this.leftType, this.rightType, this.resultType); + return Objects.hash(this.operator, this.leftRef, this.rightRef, this.resultRef); } @Override @@ -155,7 +155,7 @@ public String toString() { return String.format( "%s@%s(%s, %s, %s, %s)", this.getClass().getName(), System.identityHashCode(this), - this.operator, this.leftType, this.rightType, this.resultType); + this.operator, this.leftRef, this.rightRef, this.resultRef); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ClassInfoTypeKeeperReader.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ClassInfoTypeKeeperReader.java index 767a77b3..0f5ba9c9 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ClassInfoTypeKeeperReader.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ClassInfoTypeKeeperReader.java @@ -14,9 +14,9 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,12 +84,12 @@ private void parseClassInfo(final InputStreamReader streamReader) throws IOExcep } private void readMethod(final String line, final BufferedReader reader) throws IOException { - // - 1 : "method" <-- possibly no assignment parameter - // 2 : n ["private"/"classconst"/"classvar"/"iter"]* - // ["basic"/"restricted"/"internal"/pragma]* source_file - // 3+: + // 1 : "method" <-- possibly no assignment parameter + // 2 : n ["private"/"classconst"/"classvar"/"iter"]* + // ["basic"/"restricted"/"internal"/pragma]* source_file + // 3+: // Line 1 - // ignore and for now + // Ignore and for now. final MagikType type; final String methodName; try (Scanner scanner = new Scanner(line)) { @@ -98,15 +98,17 @@ private void readMethod(final String line, final BufferedReader reader) throws I final String className = scanner.next(); if (!"".equals(className) && !"".equals(className)) { - final GlobalReference globalRef = GlobalReference.of(className); + final TypeString typeString = TypeString.of(className); methodName = scanner.next(); - if (!this.typeKeeper.hasType(globalRef)) { - // LOGGER.debug("Type not found: {}, for method: {}", pakkageName, methodName); + if (!this.typeKeeper.hasType(typeString)) { + LOGGER.debug("Type not found: {}, for method: {}", className, methodName); type = null; } else { - type = (MagikType) this.typeKeeper.getType(globalRef); + type = (MagikType) this.typeKeeper.getType(typeString); } - scanner.nextLine(); // + + // Skip parameters. + scanner.nextLine(); } else { scanner.nextLine(); // Skip line type = null; @@ -118,6 +120,8 @@ private void readMethod(final String line, final BufferedReader reader) throws I int commentLineCount = 0; try (Scanner scanner = new Scanner(reader.readLine())) { commentLineCount = scanner.nextInt(); + + // Read pragmas. final List pragmas = new ArrayList<>(); final List skipList = List.of("private", "classconst", "classvar", "iter"); while (scanner.hasNext("[^/]+")) { @@ -127,7 +131,9 @@ private void readMethod(final String line, final BufferedReader reader) throws I } pragmas.add(pragma); } - scanner.nextLine(); // Skip path + + // Skip path. + scanner.nextLine(); } // Line 3+ @@ -148,22 +154,22 @@ private void readMethod(final String line, final BufferedReader reader) throws I } private void readSlottedClass(final String line, final BufferedReader reader) throws IOException { - // - 1 : "slotted_class" <-- also slots of inherited exemplars - // 2 : - // 3 : n pragma source_file - // 4+: + // 1 : "slotted_class" <-- also slots of inherited exemplars + // 2 : + // 3 : n pragma source_file + // 4+: // Line 1 final MagikType type; try (Scanner scanner = new Scanner(line)) { scanner.next(); // "slotted_class" final String identifier = scanner.next(); - final GlobalReference globalRef = GlobalReference.of(identifier); - if (!this.typeKeeper.hasType(globalRef)) { + final TypeString typeString = TypeString.of(identifier); + if (!this.typeKeeper.hasType(typeString)) { LOGGER.debug("Type not found: {}", identifier); type = null; } else { - type = (MagikType) this.typeKeeper.getType(globalRef); + type = (MagikType) this.typeKeeper.getType(typeString); } scanner.nextLine(); // skip slots. @@ -173,8 +179,8 @@ private void readSlottedClass(final String line, final BufferedReader reader) th final String parentClassesLine = reader.readLine(); final String[] parentClasses = parentClassesLine.split(" "); for (final String parentClass : parentClasses) { - final GlobalReference parentGlobalRef = GlobalReference.of(parentClass); - if (!this.typeKeeper.hasType(parentGlobalRef)) { + final TypeString parentTypeString = TypeString.of(parentClass); + if (!this.typeKeeper.hasType(parentTypeString)) { LOGGER.debug("Type not found: {}", parentClass); } } @@ -183,12 +189,16 @@ private void readSlottedClass(final String line, final BufferedReader reader) th int commentLineCount = 0; try (Scanner scanner = new Scanner(reader.readLine())) { commentLineCount = scanner.nextInt(); + + // Pragmas. final List pragmas = new ArrayList<>(); while (scanner.hasNext("[^/]+")) { final String pragma = scanner.next(); pragmas.add(pragma); } - scanner.nextLine(); // Skip path + + // Skip path + scanner.nextLine(); } // Line 4+ @@ -207,22 +217,22 @@ private void readSlottedClass(final String line, final BufferedReader reader) th } private void readMixin(final String line, final BufferedReader reader) throws IOException { - // - 1 : "mixin" - // 2 : "." - // 3 : n pragma source_file - // 4+: + // 1 : "mixin" + // 2 : "." + // 3 : n pragma source_file + // 4+: // Line 1 final MagikType type; try (Scanner scanner = new Scanner(line)) { scanner.next(); // "mixin" final String identifier = scanner.next(); - final GlobalReference globalRef = GlobalReference.of(identifier); - if (!this.typeKeeper.hasType(globalRef)) { + final TypeString typeString = TypeString.of(identifier); + if (!this.typeKeeper.hasType(typeString)) { LOGGER.debug("Type not found: {}", identifier); type = null; } else { - type = (MagikType) this.typeKeeper.getType(globalRef); + type = (MagikType) this.typeKeeper.getType(typeString); } scanner.nextLine(); @@ -235,12 +245,16 @@ private void readMixin(final String line, final BufferedReader reader) throws IO final int commentLineCount; try (Scanner scanner = new Scanner(reader.readLine())) { commentLineCount = scanner.nextInt(); + + // Read pragmas. final List pragmas = new ArrayList<>(); while (scanner.hasNext("[^/]+")) { final String pragma = scanner.next(); pragmas.add(pragma); } - scanner.nextLine(); // Skip path + + // Skip path + scanner.nextLine(); } // Line 4+ @@ -262,7 +276,7 @@ private void readMixin(final String line, final BufferedReader reader) throws IO * Read types from a jar/class_info file. * * @param path Path to jar file. - * @param typeKeeper {{TypeKeeper}} to fill. + * @param typeKeeper {@link TypeKeeper} to fill. * @throws IOException - */ public static void readTypes(final Path path, final ITypeKeeper typeKeeper) throws IOException { @@ -273,7 +287,7 @@ public static void readTypes(final Path path, final ITypeKeeper typeKeeper) thro /** * Read libs directory. * @param libsPath Path to libs directory. - * @param typeKeeper {{TypeKeeper}} to fill. + * @param typeKeeper {@link TypeKeeper} to fill. * @throws IOException - */ public static void readLibsDirectory(final Path libsPath, final ITypeKeeper typeKeeper) throws IOException { diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ITypeKeeper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ITypeKeeper.java index cc75e43a..d3289ae4 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ITypeKeeper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ITypeKeeper.java @@ -4,8 +4,9 @@ import java.util.Set; import javax.annotation.CheckForNull; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; import nl.ramsolutions.sw.magik.analysis.typing.types.Package; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Type keeper to hold types. @@ -14,6 +15,9 @@ public interface ITypeKeeper { /** * Add a new package. + * If the package already exists, then: + * - the types are copied. + * - the location is updated. * @param pakkage New package. */ void addPackage(Package pakkage); @@ -31,6 +35,7 @@ public interface ITypeKeeper { * @param pakkageName Name of package. * @return Package with name. */ + @CheckForNull Package getPackage(String pakkageName); /** @@ -45,11 +50,11 @@ public interface ITypeKeeper { Set getPackages(); /** - * Test if we have a type with name. - * @param globalReference Reference. + * Test if the type reference can be resolved. + * @param typeString Reference. * @return True if reference points to something. */ - boolean hasType(GlobalReference globalReference); + boolean hasType(TypeString typeString); /** * Add a global type, such as an exemplar. @@ -59,10 +64,10 @@ public interface ITypeKeeper { /** * Get a global type, such as an exemplar. - * @param globalReference Reference. + * @param typeString Reference. * @return Type, or UndefinedType if not found. */ - AbstractType getType(GlobalReference globalReference); + AbstractType getType(TypeString typeString); /** * Remove a type. Searches all packages. @@ -71,43 +76,46 @@ public interface ITypeKeeper { void removeType(AbstractType type); /** - * Get all {{AbstractType}}s that we know of. - * @return All {{AbstractTypes}} + * Get all {@link AbstractType}s that we know of. + * @return All {@link AbstractType}s. */ Collection getTypes(); - // region: Operators. - // region: Unary operators. /** - * Add a unary operator. - * @param unaryOperator Unary operator. + * Add a condition. + * @param condition Condition to add. */ - void addUnaryOperator(UnaryOperator unaryOperator); + void addCondition(Condition condition); /** - * Get the resulting {{MagikTYpe}} for a unary operator. - * @param operator Operator name. - * @param type Type operator is applied to. + * Get condition by name. + * @param name Name of condition. + * @return Condition, if found. */ @CheckForNull - UnaryOperator getUnaryOperator(UnaryOperator.Operator operator, AbstractType type); + Condition getCondition(String name); /** - * Remove a unary operator. - * @param unaryOperator Unary operator. + * Get all {@link Condition}s that we know of. + * @return All {@link Condition}s. */ - void removeUnaryOperator(UnaryOperator unaryOperator); - // endregion + Collection getConditions(); + + /** + * Remove a condition. + * @param condition Condition to remove. + */ + void removeCondition(Condition condition); // region: Binary operators. /** * Add a binary operator. - * @param binaryOpeartor Operator. + * @param binaryOperator Operator. */ - void addBinaryOperator(BinaryOperator binaryOpeartor); + void addBinaryOperator(BinaryOperator binaryOperator); /** - * Get the resulting {{MagikType}} for a binary operator. + * Get the resulting {@link MagikType} for a binary operator. * @param operator Operator name. * @param leftType Left type. * @param rightType Right type. @@ -122,7 +130,6 @@ public interface ITypeKeeper { */ void removeBinaryOperator(BinaryOperator binaryOperator); // endregion - // endregion /** * Clear all packages/types/operators. diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperReader.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperReader.java index 2f28af1b..c9d35104 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperReader.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperReader.java @@ -16,19 +16,19 @@ import nl.ramsolutions.sw.magik.analysis.Location; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.AliasType; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IndexedType; -import nl.ramsolutions.sw.magik.analysis.typing.types.IntrinsicType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.analysis.typing.types.Package; import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; import nl.ramsolutions.sw.magik.analysis.typing.types.ProcedureInstance; import nl.ramsolutions.sw.magik.analysis.typing.types.Slot; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.slf4j.Logger; @@ -43,30 +43,33 @@ public final class JsonTypeKeeperReader { private static final String SW_PAKKAGE = "sw"; - private final Path path; private final ITypeKeeper typeKeeper; private final TypeParser typeParser; private final Map typeParents = new HashMap<>(); - private final Map slotTypeNames = new HashMap<>(); + private final Map slotTypeNames = new HashMap<>(); - private JsonTypeKeeperReader(final Path path, final ITypeKeeper typeKeeper) { - this.path = path; + private JsonTypeKeeperReader(final ITypeKeeper typeKeeper) { this.typeKeeper = typeKeeper; this.typeParser = new TypeParser(this.typeKeeper); } - private void run() throws IOException { - LOGGER.debug("Reading type database from path: {}", this.path); + private void run(final Path path) throws IOException { + LOGGER.debug("Reading type database from path: {}", path); - final File file = this.path.toFile(); + final File file = path.toFile(); + int lineNo = 1; try (FileReader fileReader = new FileReader(file, StandardCharsets.ISO_8859_1); BufferedReader bufferedReader = new BufferedReader(fileReader)) { String line = bufferedReader.readLine(); while (line != null) { this.processLine(line); + ++lineNo; line = bufferedReader.readLine(); } + } catch (final JSONException exception) { + LOGGER.error("JSON Error reading line no: {}", lineNo); + throw new IllegalStateException(exception); } this.postProcess(); @@ -102,39 +105,43 @@ private void processLine(final String line) { this.handleProcedure(obj); break; + case "condition": + this.handleCondition(obj); + break; + + case "binary_operator": + this.handleBinaryOperator(obj); + break; + default: break; } } private void handlePackage(final JSONObject instruction) { - final String name = instruction.getString("name"); - if (!this.typeKeeper.hasPackage(name)) { - final Package pakkage = new Package(name); - this.typeKeeper.addPackage(pakkage); - } + final String pakkageName = instruction.getString("name"); + final Package pakkage = this.ensurePackage(pakkageName); - final Package pakkage = this.typeKeeper.getPackage(name); instruction.getJSONArray("uses").forEach(useObj -> { final String use = (String) useObj; - final Package usePackage = this.typeKeeper.getPackage(use); - pakkage.addUse(usePackage); + this.ensurePackage(use); + pakkage.addUse(use); }); } private void handleType(final JSONObject instruction) { final String typeFormat = instruction.getString("type_format"); final String name = instruction.getString("type_name"); - final GlobalReference globalRef = GlobalReference.of(name); + final TypeString typeString = TypeString.of(name); final MagikType type; - if (this.typeKeeper.getType(globalRef) != UndefinedType.INSTANCE) { - type = (MagikType) this.typeKeeper.getType(globalRef); + if (this.typeKeeper.getType(typeString) != UndefinedType.INSTANCE) { + type = (MagikType) this.typeKeeper.getType(typeString); } else if ("intrinsic".equals(typeFormat)) { - type = new IntrinsicType(globalRef); + type = new MagikType(this.typeKeeper, Sort.INTRINSIC, typeString); } else if ("slotted".equals(typeFormat)) { - type = new SlottedType(globalRef); + type = new MagikType(this.typeKeeper, Sort.SLOTTED, typeString); } else if ("indexed".equals(typeFormat)) { - type = new IndexedType(globalRef); + type = new MagikType(this.typeKeeper, Sort.INDEXED, typeString); } else { throw new InvalidParameterException("Unknown type: " + typeFormat); } @@ -149,14 +156,13 @@ private void handleType(final JSONObject instruction) { final String slotName = slot.getString("name"); final Slot typeSlot = type.addSlot(null, slotName); final String slotTypeName = slot.getString("type_name"); - this.slotTypeNames.put(typeSlot, slotTypeName); + final TypeString slotTypeString = TypeString.of(slotTypeName); + this.slotTypeNames.put(typeSlot, slotTypeString); }); // Save inheritance for later... final JSONArray parents = instruction.getJSONArray("parents"); this.typeParents.put(type, parents); - - this.typeKeeper.addType(type); } private void postProcess() { @@ -166,51 +172,60 @@ private void postProcess() { final JSONArray parentsArray = entry.getValue(); parentsArray.forEach(parentObj -> { final String parentStr = (String) parentObj; - final MagikType parentType = (MagikType) this.typeParser.parseTypeString(parentStr, SW_PAKKAGE); - type.addParent(parentType); + final TypeString parentRef = TypeString.of(parentStr, SW_PAKKAGE); + type.addParent(parentRef); }); }); // Slot types. this.slotTypeNames.entrySet().forEach(entry -> { final Slot slot = entry.getKey(); - final String typeName = entry.getValue(); - final AbstractType type = this.typeParser.parseTypeString(typeName, SW_PAKKAGE); + final TypeString typeString = entry.getValue(); + final AbstractType type = this.typeParser.parseTypeString(typeString); slot.setType(type); }); } private void handleGlobal(final JSONObject instruction) { final String name = instruction.getString("name"); - final GlobalReference globalRef = GlobalReference.of(name); + final TypeString typeString = TypeString.of(name); final String typeName = instruction.getString("type_name"); - final ExpressionResult parsedResult = this.typeParser.parseExpressionResultString(typeName, SW_PAKKAGE); - final AbstractType type = parsedResult.get(0, UndefinedType.INSTANCE); + final TypeString aliasedRef = TypeString.of(typeName); - final AliasType global = new AliasType(globalRef, type); + final AliasType global = new AliasType(this.typeKeeper, typeString, aliasedRef); this.typeKeeper.addType(global); } private void handleMethod(final JSONObject instruction) { final String typeName = instruction.getString("type_name"); - final ExpressionResult parsedResult = this.typeParser.parseExpressionResultString(typeName, SW_PAKKAGE); - final AbstractType abstractType = (AbstractType) parsedResult.get(0, UndefinedType.INSTANCE); + final TypeString typeRef = TypeString.of(typeName); + final AbstractType abstractType = this.typeKeeper.getType(typeRef); if (abstractType == UndefinedType.INSTANCE) { - throw new InvalidParameterException("Unknown type: " + typeName); + throw new IllegalStateException("Unknown type: " + typeName); } final MagikType type = (MagikType) abstractType; + // What if method already exists? Might be improved by taking the line into account as well. + final String methodName = instruction.getString("method_name"); + final Location location = instruction.get("source_file") != JSONObject.NULL + ? new Location(Path.of(instruction.getString("source_file")).toUri()) + : null; + final boolean isAlreadyKnown = type.getLocalMethods(methodName).stream() + .anyMatch(method -> + method.getLocation() == null && location == null + || method.getLocation() != null && method.getLocation().equals(location)); + if (isAlreadyKnown) { + LOGGER.debug("Skipping already known method: {}.{}", typeName, methodName); + return; + } + final JSONArray modifiersArray = (JSONArray) instruction.getJSONArray("modifiers"); final EnumSet modifiers = StreamSupport.stream(modifiersArray.spliterator(), false) .map(String.class::cast) .map(String::toUpperCase) .map(Method.Modifier::valueOf) .collect(Collectors.toCollection(() -> EnumSet.noneOf(Method.Modifier.class))); - final Location location = instruction.get("source_file") != JSONObject.NULL - ? new Location(Path.of(instruction.getString("source_file")).toUri()) - : null; - final String methodName = instruction.getString("method_name"); final List parameters = this.parseParameters(instruction.getJSONArray("parameters")); final Parameter assignmentParameter; if (methodName.contains("<<")) { @@ -220,15 +235,37 @@ private void handleMethod(final JSONObject instruction) { } else { assignmentParameter = null; } - final ExpressionResult result = this.parseExpressionResult(instruction.get("return_types")); - final ExpressionResult loopResult = this.parseExpressionResult(instruction.get("loop_types")); - final Method method = - type.addMethod(modifiers, location, methodName, parameters, assignmentParameter, result, loopResult); + final ExpressionResultString result = this.parseExpressionResultString(instruction.get("return_types")); + final ExpressionResultString loopResult = this.parseExpressionResultString(instruction.get("loop_types")); + final String methodDoc = instruction.get("doc") != JSONObject.NULL + ? instruction.getString("doc") + : null; + type.addMethod(location, modifiers, methodName, parameters, assignmentParameter, methodDoc, result, loopResult); + } - if (instruction.get("doc") != JSONObject.NULL) { - final String doc = instruction.getString("doc"); - method.setDoc(doc); - } + private void handleCondition(final JSONObject instruction) { + final String name = instruction.getString("name"); + final String doc = instruction.getString("doc"); + final String parent = instruction.getString("parent"); + final Location location = instruction.get("source_file") != JSONObject.NULL + ? new Location(Path.of(instruction.getString("source_file")).toUri()) + : null; + final JSONArray dataNameListArray = (JSONArray) instruction.getJSONArray("data_name_list"); + final List dataNameList = StreamSupport.stream(dataNameListArray.spliterator(), false) + .map(String.class::cast) + .collect(Collectors.toList()); + final Condition condition = new Condition(location, name, parent, dataNameList, doc); + this.typeKeeper.addCondition(condition); + } + + private void handleBinaryOperator(final JSONObject instruction) { + final BinaryOperator.Operator operator = + BinaryOperator.Operator.valueFor(instruction.getString("operator")); + final TypeString lhsTypeRef = TypeString.of(instruction.getString("lhs_type")); + final TypeString rhsTypeRef = TypeString.of(instruction.getString("rhs_type")); + final TypeString resultTypeRef = TypeString.of(instruction.getString("return_type")); + final BinaryOperator binaryOperator = new BinaryOperator(operator, lhsTypeRef, rhsTypeRef, resultTypeRef); + this.typeKeeper.addBinaryOperator(binaryOperator); } private List parseParameters(final JSONArray parametersArray) { @@ -239,29 +276,31 @@ private List parseParameters(final JSONArray parametersArray) { final Parameter.Modifier modifier = parameterObj.get("modifier") != JSONObject.NULL ? Parameter.Modifier.valueOf(parameterObj.getString("modifier").toUpperCase()) : Parameter.Modifier.NONE; - final String typeNameStr = parameterObj.getString("type_name"); - final AbstractType type = this.typeParser.parseTypeString(typeNameStr, SW_PAKKAGE); + final String typeName = parameterObj.getString("type_name"); + final TypeString typeString = TypeString.of(typeName); + final AbstractType type = this.typeParser.parseTypeString(typeString); return new Parameter(name, modifier, type); }) .collect(Collectors.toList()); } - private ExpressionResult parseExpressionResult(final Object obj) { + private ExpressionResultString parseExpressionResultString(final Object obj) { if (obj == JSONObject.NULL) { - return ExpressionResult.UNDEFINED; + return ExpressionResultString.UNDEFINED; } if (obj instanceof String) { - final String expressionResultStr = (String) obj; - return this.typeParser.parseExpressionResultString(expressionResultStr, SW_PAKKAGE); + final String expressionResultString = (String) obj; + final TypeString typeString = TypeString.of(expressionResultString); + return new ExpressionResultString(typeString); } if (obj instanceof JSONArray) { final JSONArray array = (JSONArray) obj; return StreamSupport.stream(array.spliterator(), false) .map(String.class::cast) - .map(typeStr -> this.typeParser.parseTypeString(typeStr, SW_PAKKAGE)) - .collect(ExpressionResult.COLLECTOR); + .map(TypeString::new) + .collect(ExpressionResultString.COLLECTOR); } throw new InvalidParameterException("Don't know what to do with: " + obj); @@ -278,32 +317,46 @@ private void handleProcedure(final JSONObject instruction) { ? new Location(Path.of(instruction.getString("source_file")).toUri()) : null; final List parameters = this.parseParameters(instruction.getJSONArray("parameters")); - final ExpressionResult result = this.parseExpressionResult(instruction.get("return_types")); - final ExpressionResult loopResult = this.parseExpressionResult(instruction.get("loop_types")); + final ExpressionResultString resultStr = this.parseExpressionResultString(instruction.get("return_types")); + final ExpressionResultString loopResultStr = this.parseExpressionResultString(instruction.get("loop_types")); - final String name = instruction.getString("name"); - final GlobalReference globalRef = GlobalReference.of(name); final String procedureName = instruction.getString("procedure_name"); - final ProcedureInstance instance = new ProcedureInstance(globalRef, procedureName); - final Method method = instance.addMethod( - modifiers, location, "invoke()", parameters, null, result, loopResult); - this.typeKeeper.addType(instance); + final MagikType procedureType = (MagikType) this.typeKeeper.getType(TypeString.of("sw:procedure")); + final String methodDoc = instruction.get("doc") != JSONObject.NULL + ? instruction.getString("doc") + : null; + final ProcedureInstance instance = new ProcedureInstance( + procedureType, + location, + procedureName, + modifiers, + parameters, + methodDoc, + resultStr, + loopResultStr); + + // Create alias to instance. + final String name = instruction.getString("name"); + final TypeString typeString = TypeString.of(name); + new AliasType(this.typeKeeper, typeString, instance); + } - if (instruction.get("doc") != JSONObject.NULL) { - final String doc = instruction.getString("doc"); - method.setDoc(doc); + private Package ensurePackage(final String pakkageName) { + if (!this.typeKeeper.hasPackage(pakkageName)) { + new Package(this.typeKeeper, pakkageName); } + return this.typeKeeper.getPackage(pakkageName); } /** * Read types from a JSON-line file. * @param path Path to JSON-line file. - * @param typeKeeper {{TypeKeeper}} to fill. + * @param typeKeeper {@link TypeKeeper} to fill. * @throws IOException - */ public static void readTypes(final Path path, final ITypeKeeper typeKeeper) throws IOException { - final JsonTypeKeeperReader reader = new JsonTypeKeeperReader(path, typeKeeper); - reader.run(); + final JsonTypeKeeperReader reader = new JsonTypeKeeperReader(typeKeeper); + reader.run(path); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperWriter.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperWriter.java new file mode 100644 index 00000000..d081695b --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/JsonTypeKeeperWriter.java @@ -0,0 +1,213 @@ +package nl.ramsolutions.sw.magik.analysis.typing; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.AliasType; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; +import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.Package; +import nl.ramsolutions.sw.magik.analysis.typing.types.Slot; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JSON-line TypeKeeper writer. + */ +public final class JsonTypeKeeperWriter { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonTypeKeeperWriter.class); + + private final ITypeKeeper typeKeeper; + + private JsonTypeKeeperWriter(final ITypeKeeper typeKeeper) { + this.typeKeeper = typeKeeper; + } + + private void run(final Path path) throws IOException { + LOGGER.debug("Writing type database to path: {}", path); + + final File file = path.toFile(); + try (FileWriter fileReader = new FileWriter(file, StandardCharsets.ISO_8859_1); + BufferedWriter bufferedWriter = new BufferedWriter(fileReader)) { + this.writePackages(bufferedWriter); + this.writeTypes1(bufferedWriter); + this.writeGlobals(bufferedWriter); + this.writeMethods(bufferedWriter); + this.writeProcedures(bufferedWriter); + } + } + + private void writeInstruction(final Writer writer, final JSONObject instruction) { + instruction.write(writer); + + try { + writer.write("\n"); + } catch (final IOException exception) { + LOGGER.error("Caught exception writing instruction", exception); + } + } + + private void writePackages(final Writer writer) { + final Comparator packageNameComparer = Comparator.comparing(Package::getName); + this.typeKeeper.getPackages().stream() + .sorted(packageNameComparer) + .forEach(pakkage -> { + final JSONObject instruction = new JSONObject(); + instruction.put("instruction", "package"); + instruction.put("name", pakkage.getName()); + final List pakkageUses = pakkage.getUses().stream() + .map(usedPakkage -> usedPakkage.getName()) + .collect(Collectors.toList()); + instruction.put("uses", pakkageUses); + this.writeInstruction(writer, instruction); + }); + } + + private void writeTypes1(final Writer writer) { + final Comparator typeNameComparer = Comparator.comparing(AbstractType::getFullName); + final Comparator slotNameComparer = Comparator.comparing(Slot::getName); + this.typeKeeper.getTypes().stream() + .filter(type -> !(type instanceof AliasType)) + .sorted(typeNameComparer) + .forEach(type -> { + final List parents = type.getParents().stream() + .map(AbstractType::getFullName) + .sorted() + .collect(Collectors.toList()); + final List slots = type.getSlots().stream() + .sorted(slotNameComparer) + .map(slot -> { + final JSONObject slotObject = new JSONObject(); + slotObject.put("name", slot.getName()); + slotObject.put("type_name", slot.getType().getFullName()); + return slotObject; + }) + .collect(Collectors.toList()); + + final JSONObject instruction = new JSONObject(); + instruction.put("instruction", "type"); + instruction.put("type_name", type.getFullName()); + instruction.put("type_format", this.getTypeFormat(type)); + instruction.put("parents", parents); + instruction.put("slots", slots); + instruction.put("doc", type.getDoc()); + this.writeInstruction(writer, instruction); + }); + } + + private String getTypeFormat(final AbstractType type) { + if (!(type instanceof MagikType)) { + throw new IllegalArgumentException("Unknown type: " + type); + } + + final MagikType magikType = (MagikType) type; + if (magikType.getSort() == Sort.INTRINSIC) { + return "intrinsic"; + } else if (magikType.getSort() == Sort.SLOTTED) { + return "slotted"; + } else if (magikType.getSort() == Sort.INDEXED) { + return "indexed"; + } else if (magikType.getSort() == Sort.OBJECT) { + return "object"; + } + + throw new IllegalArgumentException("Unknown type: " + type); + } + + private void writeGlobals(final Writer writer) { + this.typeKeeper.getTypes().stream() + .filter(AliasType.class::isInstance) + .map(AliasType.class::cast) + .forEach(type -> { + final JSONObject instruction = new JSONObject(); + instruction.put("instruction", "global"); + instruction.put("name", type.getFullName()); + instruction.put("type_name", type.getAliasedType().getFullName()); + this.writeInstruction(writer, instruction); + }); + } + + private void writeMethods(final Writer writer) { + final Comparator typeNameComparer = Comparator.comparing(AbstractType::getFullName); + final Comparator methodNameComparer = Comparator.comparing(Method::getName); + this.typeKeeper.getTypes().stream() + .sorted(typeNameComparer) + .filter(MagikType.class::isInstance) + .flatMap(type -> type.getLocalMethods().stream().sorted(methodNameComparer)) + .forEach(method -> { + final List parameters = method.getParameters().stream() + .map(parameter -> { + final JSONObject parameterObject = new JSONObject(); + parameterObject.put("name", parameter.getName()); + parameterObject.put("modifier", parameter.getModifier()); + parameterObject.put("type_name", parameter.getType().getFullName()); + return parameterObject; + }) + .collect(Collectors.toList()); + final Path sourceFile = method.getLocation() != null + ? method.getLocation().getPath() + : null; + + final JSONObject instruction = new JSONObject(); + instruction.put("instruction", "method"); + instruction.put("type_name", method.getOwner().getFullName()); + instruction.put("method_name", method.getName()); + instruction.put("modifiers", method.getModifiers()); + instruction.put("parameters", parameters); + if (method.getCallResult() != ExpressionResultString.UNDEFINED) { + final ExpressionResultString callResult = method.getCallResult(); + instruction.put("return_types", callResult); + } else { + instruction.put("return_types", ExpressionResultString.UNDEFINED_SERIALIZED_NAME); + } + if (method.getLoopbodyResult() != ExpressionResultString.UNDEFINED) { + final ExpressionResultString loopTypes = method.getLoopbodyResult(); + instruction.put("loop_types", loopTypes); + } else { + instruction.put("return_types", ExpressionResult.UNDEFINED_SERIALIZED_NAME); + } + instruction.put("source_file", sourceFile); + instruction.put("doc", method.getDoc()); + this.writeInstruction(writer, instruction); + }); + } + + private void writeProcedures(final Writer writer) { + // { + // "instruction":"procedure", + // "name":"sw:sys!ds_record_cf", + // "procedure_name":"sys!ds_record_cf", + // "modifiers":[], + // "parameters":[], + // "return_types":"__UNDEFINED_RESULT__", + // "loop_types":[], + // "source_file":null, + // "doc":"" + // } + } + + /** + * Write types to a JSON-line file. + * @param path Path to JSON-line file. + * @param typeKeeper {@link TypeKeeper} to dump. + * @throws IOException - + */ + public static void writeTypes(final Path path, final ITypeKeeper typeKeeper) throws IOException { + final JsonTypeKeeperWriter reader = new JsonTypeKeeperWriter(typeKeeper); + reader.run(path); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasoner.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasoner.java index 59f2bfec..781897db 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasoner.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasoner.java @@ -2,6 +2,7 @@ import com.sonar.sslr.api.AstNode; import java.net.URI; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -10,6 +11,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.MagikTypedFile; @@ -19,24 +23,28 @@ import nl.ramsolutions.sw.magik.analysis.helpers.LeaveStatementNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; -import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.ParameterNodeHelper; +import nl.ramsolutions.sw.magik.analysis.helpers.ProcedureDefinitionNodeHelper; import nl.ramsolutions.sw.magik.analysis.scope.GlobalScope; import nl.ramsolutions.sw.magik.analysis.scope.Scope; import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; -import nl.ramsolutions.sw.magik.analysis.typing.types.Package; import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; +import nl.ramsolutions.sw.magik.analysis.typing.types.ParameterReferenceType; import nl.ramsolutions.sw.magik.analysis.typing.types.ProcedureInstance; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; import nl.ramsolutions.sw.magik.analysis.typing.types.Slot; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.MagikKeyword; +import nl.ramsolutions.sw.magik.api.MagikOperator; +import nl.ramsolutions.sw.magik.parser.CommentInstructionReader; import nl.ramsolutions.sw.magik.parser.NewDocParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,47 +61,60 @@ *

* *

- * If a type cannot be determined, the {{UndefinedType}} is used instead. + * If a type cannot be determined, the {@code UndefinedType} is used instead. *

* *

- * If {{_self}} or {{_clone}} is returned in a method, the {{SelfType}} is used. - * It is up to the user to determine the real type. I.e., in case of {{sw:date_time_mixin}}, + * If {@code _self} or {@code _clone} is returned in a method, the {@code SelfType} is used. + * It is up to the user to determine the real type. I.e., in case of {@code sw:date_time_mixin}, * none of the methods are called on that class directly, but always through an inherited class. - * On declaration the inheriting classes are unknown, thus if {{_self}} is returned from a mixin, + * On declaration the inheriting classes are unknown, thus if {@code _self} is returned from a mixin, * we need to proxy the type. *

*/ public class LocalTypeReasoner extends AstWalker { - private static final GlobalReference SW_UNSET = GlobalReference.of("sw:unset"); - private static final GlobalReference SW_SIMPLE_VECTOR = GlobalReference.of("sw:simple_vector"); - private static final GlobalReference SW_FALSE = GlobalReference.of("sw:false"); - private static final GlobalReference SW_MAYBE = GlobalReference.of("sw:maybe"); - private static final GlobalReference SW_HEAVY_THREAD = GlobalReference.of("sw:heavy_thread"); - private static final GlobalReference SW_LIGHT_THREAD = GlobalReference.of("sw:light_thread"); - private static final GlobalReference SW_GLOBAL_VARIABLE = GlobalReference.of("sw:global_variable"); - private static final GlobalReference SW_BIGNUM = GlobalReference.of("sw:bignum"); - private static final GlobalReference SW_INTEGER = GlobalReference.of("sw:integer"); - private static final GlobalReference SW_FLOAT = GlobalReference.of("sw:float"); - private static final GlobalReference SW_CHARACTER = GlobalReference.of("sw:character"); - private static final GlobalReference SW_SW_REGEXP = GlobalReference.of("sw:sw_regexp"); - private static final GlobalReference SW_CHAR16_VECTOR = GlobalReference.of("sw:char16_vector"); - private static final GlobalReference SW_SYMBOL = GlobalReference.of("sw:symbol"); - private static final GlobalReference SW_CONDITION = GlobalReference.of("sw:condition"); - private static final GlobalReference SW_PROCEDURE = GlobalReference.of("sw:procedure"); + private static final TypeString SW_UNSET = TypeString.of("sw:unset"); + private static final TypeString SW_SIMPLE_VECTOR = TypeString.of("sw:simple_vector"); + private static final TypeString SW_FALSE = TypeString.of("sw:false"); + private static final TypeString SW_MAYBE = TypeString.of("sw:maybe"); + private static final TypeString SW_HEAVY_THREAD = TypeString.of("sw:heavy_thread"); + private static final TypeString SW_LIGHT_THREAD = TypeString.of("sw:light_thread"); + private static final TypeString SW_GLOBAL_VARIABLE = TypeString.of("sw:global_variable"); + private static final TypeString SW_BIGNUM = TypeString.of("sw:bignum"); + private static final TypeString SW_INTEGER = TypeString.of("sw:integer"); + private static final TypeString SW_FLOAT = TypeString.of("sw:float"); + private static final TypeString SW_CHARACTER = TypeString.of("sw:character"); + private static final TypeString SW_SW_REGEXP = TypeString.of("sw:sw_regexp"); + private static final TypeString SW_CHAR16_VECTOR = TypeString.of("sw:char16_vector"); + private static final TypeString SW_SYMBOL = TypeString.of("sw:symbol"); + private static final TypeString SW_CONDITION = TypeString.of("sw:condition"); + private static final TypeString SW_PROCEDURE = TypeString.of("sw:procedure"); + + private static final Map UNARY_OPERATOR_METHODS = Map.of( + MagikOperator.NOT.getValue(), "not", + MagikKeyword.NOT.getValue(), "not", + MagikOperator.MINUS.getValue(), "negated", + MagikOperator.PLUS.getValue(), "unary_plus"); private static final Logger LOGGER = LoggerFactory.getLogger(LocalTypeReasoner.class); @SuppressWarnings("checkstyle:MagicNumber") private static final long BIGNUM_START = 1 << 29; + private static final String DEFAULT_PACKAGE = "user"; + private static final CommentInstructionReader.InstructionType TYPE_INSTRUCTION = + CommentInstructionReader.InstructionType.createInstructionType("type"); + private static final CommentInstructionReader.InstructionType ITER_TYPE_INSTRUCTION = + CommentInstructionReader.InstructionType.createInstructionType("iter-type"); + private final AstNode topNode; private final ITypeKeeper typeKeeper; private final TypeParser typeParser; private final GlobalScope globalScope; + private final CommentInstructionReader instructionReader; private final Map nodeTypes = new HashMap<>(); private final Map loopbodyNodeTypes = new HashMap<>(); private final Map currentScopeEntryNodes = new HashMap<>(); - private Package currentPackage; + private String currentPackage; private ExpressionResult iteratorType; /** @@ -101,32 +122,26 @@ public class LocalTypeReasoner extends AstWalker { * @param magikFile Magik file to reason on. */ public LocalTypeReasoner(final MagikTypedFile magikFile) { - this(magikFile.getTypeKeeper(), magikFile.getGlobalScope()); - } - - /** - * Constructor. - * @param typeKeeper TypeKeeper to use. - * @param globalScope Scope to use for analysis. - */ - public LocalTypeReasoner(final ITypeKeeper typeKeeper, final GlobalScope globalScope) { - this.typeKeeper = typeKeeper; + this.topNode = magikFile.getTopNode(); + this.typeKeeper = magikFile.getTypeKeeper(); this.typeParser = new TypeParser(this.typeKeeper); - this.globalScope = globalScope; - this.currentPackage = this.typeKeeper.getPackage("sw"); + this.globalScope = magikFile.getGlobalScope(); + this.instructionReader = new CommentInstructionReader( + magikFile, Set.of(TYPE_INSTRUCTION, ITER_TYPE_INSTRUCTION)); + this.currentPackage = DEFAULT_PACKAGE; } /** - * Evaluate the given top {{AstNode}}. + * Evaluate the given top {@link AstNode}. */ - public void run(final AstNode node) { + public void run() { // Start walking. LOGGER.debug("Start walking"); - this.walkAst(node); + this.walkAst(this.topNode); } /** - * Get the type for a {{AstNode}}. + * Get the type for a {@link AstNode}. * @param node AstNode. * @return Resulting type. */ @@ -140,7 +155,7 @@ public ExpressionResult getNodeType(final AstNode node) { } /** - * Get the type for a {{AstNode}}. + * Get the type for a {@link AstNode}. * @param node AstNode. * @return Resulting type. */ @@ -150,7 +165,7 @@ public ExpressionResult getNodeTypeSilent(final AstNode node) { } /** - * Test if the type for a {{AstNode}} is known. + * Test if the type for a {@link AstNode} is known. * @param node AstNode. * @return True if known, false otherwise. */ @@ -159,7 +174,7 @@ private boolean hasNodeType(final AstNode node) { } /** - * Set a type for a {{AstNode}}. + * Set a type for a {@link AstNode}. * @param node AstNode. * @param result ExpressionResult. */ @@ -178,12 +193,12 @@ private void setIteratorType(final @Nullable ExpressionResult result) { @CheckForNull private ExpressionResult getIteratorType() { - // XXX TODO: Return undefined instead of null. + // TODO: Return undefined instead of null. return this.iteratorType; } /** - * Add a type for a {{AstNode}}. Combines type if a type is already known. + * Add a type for a {@link AstNode}. Combines type if a type is already known. * @param node AstNode. * @param result ExpressionResult. */ @@ -200,11 +215,11 @@ private void addNodeType(final AstNode node, final ExpressionResult result) { } /** - * Get the loopbody type for a {{AstNode}}. + * Get the loopbody type for a {@link AstNode}. * @param node AstNode. * @return Resulting type. */ - private ExpressionResult getLoopbodyNodeType(final AstNode node) { + public ExpressionResult getLoopbodyNodeType(final AstNode node) { final ExpressionResult result = this.loopbodyNodeTypes.get(node); if (result == null) { LOGGER.debug("Node without type: {}", node); @@ -214,7 +229,17 @@ private ExpressionResult getLoopbodyNodeType(final AstNode node) { } /** - * Set a loopbody type for a {{AstNode}}. + * Get the loopbody type for a {@link AstNode}. + * @param node AstNode. + * @return Resulting type. + */ + public ExpressionResult getLoopbodyNodeTypeSilent(final AstNode node) { + final ExpressionResult result = this.loopbodyNodeTypes.get(node); + return result; + } + + /** + * Set a loopbody type for a {@link AstNode}. * @param node AstNode. * @param result Type. */ @@ -224,15 +249,13 @@ private void setLoopbodyNodeType(final AstNode node, final ExpressionResult resu @Override protected void walkPostPackageSpecification(final AstNode node) { - final PackageNodeHelper helper = new PackageNodeHelper(node); - final String name = helper.getCurrentPackage(); + final String packageName = node.getFirstChild(MagikGrammar.PACKAGE_IDENTIFIER).getTokenValue(); - if (!this.typeKeeper.hasPackage(name)) { - LOGGER.debug("Package not found: {}", name); + if (!this.typeKeeper.hasPackage(packageName)) { + LOGGER.debug("Package not found: {}", packageName); } - // Package is created on demand. - this.currentPackage = this.typeKeeper.getPackage(name); + this.currentPackage = packageName; } @Override @@ -277,7 +300,17 @@ protected void walkPostIterableExpression(final AstNode node) { final AstNode forNode = overNode.getParent(); if (forNode.is(MagikGrammar.FOR)) { final AstNode loopNode = overNode.getFirstChild(MagikGrammar.LOOP); + if (loopNode == null) { + LOGGER.debug("Unexpected: LOOP node is null"); + return; + } + final AstNode bodyNode = loopNode.getFirstChild(MagikGrammar.BODY); + if (bodyNode == null) { + LOGGER.debug("Unexpected: BODY node is null"); + return; + } + final List identifierNodes = AstQuery.getChildrenFromChain( forNode, MagikGrammar.FOR_VARIABLES, @@ -312,19 +345,16 @@ protected void walkPostParameter(final AstNode node) { final AstNode definitionNode = node.getFirstAncestor(MagikGrammar.METHOD_DEFINITION, MagikGrammar.PROCEDURE_DEFINITION); final NewDocParser docParser = new NewDocParser(definitionNode); - final Map parameterTypes = docParser.getParameterTypes(); - final String parameterTypeStr = parameterTypes.get(identifier); + final Map parameterTypes = docParser.getParameterTypes(); + final TypeString parameterTypeString = parameterTypes.get(identifier); final ExpressionResult result; final ParameterNodeHelper helper = new ParameterNodeHelper(node); if (helper.isGatherParameter()) { final AbstractType simpleVectorType = this.typeKeeper.getType(SW_SIMPLE_VECTOR); result = new ExpressionResult(simpleVectorType); - } else if (parameterTypeStr != null && !parameterTypeStr.isBlank()) { - final PackageNodeHelper packageHelper = new PackageNodeHelper(node); - final String pakkage = packageHelper.getCurrentPackage(); - final AbstractType type = this.typeParser.parseTypeString(parameterTypeStr, pakkage); - + } else if (parameterTypeString != null && !parameterTypeString.isUndefined()) { + final AbstractType type = this.typeParser.parseTypeString(parameterTypeString); if (helper.isOptionalParameter()) { final AbstractType unsetType = this.typeKeeper.getType(SW_UNSET); final AbstractType optionalType = new CombinedType(type, unsetType); @@ -336,7 +366,7 @@ protected void walkPostParameter(final AstNode node) { result = ExpressionResult.UNDEFINED; } - this.setNodeType(node, result); + this.setNodeType(identifierNode, result); final Scope scope = this.globalScope.getScopeForNode(node); Objects.requireNonNull(scope); @@ -381,16 +411,13 @@ protected void walkPostIdentifier(final AstNode node) { final Scope scope = this.globalScope.getScopeForNode(node); Objects.requireNonNull(scope); - String identifier = node.getTokenValue(); + final String identifier = node.getTokenValue(); final ScopeEntry scopeEntry = scope.getScopeEntry(identifier); Objects.requireNonNull(scopeEntry); if (scopeEntry.isType(ScopeEntry.Type.GLOBAL) || scopeEntry.isType(ScopeEntry.Type.DYNAMIC)) { - final String packageName = this.currentPackage.getName(); - final GlobalReference globalRef = identifier.contains(":") - ? GlobalReference.of(identifier) - : GlobalReference.of(packageName, identifier); - this.assignAtom(node, globalRef); + final TypeString typeString = TypeString.of(identifier, this.currentPackage); + this.assignAtom(node, typeString); } else if (scopeEntry.isType(ScopeEntry.Type.IMPORT)) { final ScopeEntry parentScopeEntry = scopeEntry.getImportedEntry(); final AstNode lastNodeType = this.currentScopeEntryNodes.get(parentScopeEntry); @@ -546,8 +573,8 @@ protected void walkPostThisthread(final AstNode node) { this.assignAtom(node, threadType); } - private void assignAtom(final AstNode node, final GlobalReference globalRef) { - final AbstractType type = this.typeKeeper.getType(globalRef); + private void assignAtom(final AstNode node, final TypeString typeString) { + final AbstractType type = this.typeKeeper.getType(typeString); this.assignAtom(node, type); } @@ -567,16 +594,15 @@ private void assignAtom(final AstNode node, final ExpressionResult result) { protected void walkPostReturnStatement(final AstNode node) { // Get results. final AstNode tupleNode = node.getFirstChild(MagikGrammar.TUPLE); - if (tupleNode == null) { - return; - } + final ExpressionResult result = tupleNode != null + ? this.getNodeType(tupleNode) + : new ExpressionResult(); - // Find related node. + // Find related node to store on. final AstNode definitionNode = node.getFirstAncestor(MagikGrammar.METHOD_DEFINITION, MagikGrammar.PROCEDURE_DEFINITION); // Save results at returned node. - final ExpressionResult result = this.getNodeType(tupleNode); this.addNodeType(definitionNode, result); } @@ -700,26 +726,28 @@ protected void walkPostLoopbody(final AstNode node) { @Override protected void walkPostProcedureDefinition(final AstNode node) { // Get name of procedure. - String procName = ProcedureInstance.ANONYMOUS_PROCEDURE; - final AstNode labelNode = node.getFirstChild(MagikGrammar.LABEL); - if (labelNode != null) { - final List labelNodeChildren = labelNode.getChildren(); - procName = labelNodeChildren.get(1).getTokenValue(); - } + final ProcedureDefinitionNodeHelper helper = new ProcedureDefinitionNodeHelper(node); + final String procedureName = helper.getProcedureName(); // Parameters. final AstNode parametersNode = node.getFirstChild(MagikGrammar.PARAMETERS); + if (parametersNode == null) { + // Robustness, in case of a syntax error in the procedure definition. + return; + } + + // TODO: Can we move this somewhere else? final List parameters = new ArrayList<>(); final List parameterNodes = parametersNode.getChildren(MagikGrammar.PARAMETER); for (final AstNode parameterNode : parameterNodes) { final AstNode identifierNode = parameterNode.getFirstChild(MagikGrammar.IDENTIFIER); final String identifier = identifierNode.getTokenValue(); - final ParameterNodeHelper helper = new ParameterNodeHelper(parameterNode); + final ParameterNodeHelper parameterHelper = new ParameterNodeHelper(parameterNode); final Parameter.Modifier modifier; - if (helper.isOptionalParameter()) { + if (parameterHelper.isOptionalParameter()) { modifier = Parameter.Modifier.OPTIONAL; - } else if (helper.isGatherParameter()) { + } else if (parameterHelper.isGatherParameter()) { modifier = Parameter.Modifier.GATHER; } else { modifier = Parameter.Modifier.NONE; @@ -732,17 +760,26 @@ protected void walkPostProcedureDefinition(final AstNode node) { // Result. final ExpressionResult procResult = this.getNodeType(node); + final ExpressionResultString procResultStr = TypeParser.unparseExpressionResult(procResult); // Loopbody result. final ExpressionResult loopbodyResult = this.getLoopbodyNodeType(node); + final ExpressionResultString loopbodyResultStr = TypeParser.unparseExpressionResult(loopbodyResult); // Create procedure instance. final EnumSet modifiers = EnumSet.noneOf(Method.Modifier.class); final URI uri = node.getToken().getURI(); final Location location = new Location(uri, node); - final GlobalReference globalRef = GlobalReference.of(ProcedureInstance.ANONYMOUS_PROCEDURE); - final ProcedureInstance procType = new ProcedureInstance(globalRef, procName); - procType.addMethod(modifiers, location, "invoke()", parameters, null, procResult, loopbodyResult); + final MagikType procedureType = (MagikType) this.typeKeeper.getType(TypeString.of("sw:procedure")); + final ProcedureInstance procType = new ProcedureInstance( + procedureType, + location, + procedureName, + modifiers, + parameters, + null, + procResultStr, + loopbodyResultStr); // Store result. final ExpressionResult result = new ExpressionResult(procType); @@ -806,10 +843,10 @@ protected void walkPostTryVariable(final AstNode node) { @Override protected void walkPostExpression(final AstNode node) { // Check for type annotations, those overrule normal operations. - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(node); + final String typeAnnotation = this.instructionReader.getInstructionForNode(node, TYPE_INSTRUCTION); if (typeAnnotation != null) { - final String pakkageName = this.currentPackage.getName(); - final ExpressionResult result = this.typeParser.parseExpressionResultString(typeAnnotation, pakkageName); + final ExpressionResultString resultStr = ExpressionResultString.of(typeAnnotation, this.currentPackage); + final ExpressionResult result = this.typeParser.parseExpressionResultString(resultStr); this.setNodeType(node, result); } else { // Normal operations apply. @@ -822,11 +859,11 @@ protected void walkPostExpression(final AstNode node) { } // Check for iter type annotations. - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(node); + final String iterTypeAnnotation = this.instructionReader.getInstructionForNode(node, ITER_TYPE_INSTRUCTION); if (iterTypeAnnotation != null) { - final String pakkageName = this.currentPackage.getName(); - final ExpressionResult iterResult = - this.typeParser.parseExpressionResultString(iterTypeAnnotation, pakkageName); + final ExpressionResultString iterResultStr = + ExpressionResultString.of(iterTypeAnnotation, this.currentPackage); + final ExpressionResult iterResult = this.typeParser.parseExpressionResultString(iterResultStr); this.setIteratorType(iterResult); LOGGER.trace("{} is of iter-type: {}", node, iterResult); } @@ -874,8 +911,12 @@ protected void walkPostAssignmentExpression(final AstNode node) { this.currentScopeEntryNodes.put(scopeEntry, assignedNode); this.setNodeType(assignedNode, result); - } else if (assignedNode.is(MagikGrammar.SLOT)) { - throw new UnsupportedOperationException(); + } else if (assignedNode.is(MagikGrammar.ATOM) + && assignedNode.getFirstChild(MagikGrammar.SLOT) != null) { + // Store slot. + this.setNodeType(assignedNode, result); + } else { + LOGGER.debug("Unsupported construct!"); // TODO } } @@ -918,9 +959,10 @@ protected void walkPostAugmentedAssignmentExpression(final AstNode node) { default: final BinaryOperator.Operator operator = BinaryOperator.Operator.valueFor(operatorStr); final BinaryOperator binaryOperator = this.typeKeeper.getBinaryOperator(operator, leftType, rightType); - final AbstractType resultingType = binaryOperator != null + final TypeString resultingTypeRef = binaryOperator != null ? binaryOperator.getResultType() - : UndefinedType.INSTANCE; + : TypeString.UNDEFINED; + final AbstractType resultingType = this.typeKeeper.getType(resultingTypeRef); result = new ExpressionResult(resultingType); break; } @@ -1040,9 +1082,10 @@ private void applyBinaryOperator(final AstNode node) { final BinaryOperator.Operator operator = BinaryOperator.Operator.valueFor(operatorStr); final BinaryOperator binaryOperator = this.typeKeeper.getBinaryOperator(operator, leftType, rightType); - final AbstractType resultingType = binaryOperator != null + final TypeString resultingTypeRef = binaryOperator != null ? binaryOperator.getResultType() - : UndefinedType.INSTANCE; + : TypeString.UNDEFINED; + final AbstractType resultingType = this.typeKeeper.getType(resultingTypeRef); result = new ExpressionResult(resultingType); break; } @@ -1077,15 +1120,13 @@ private void applyUnaryOperator(final AstNode node) { final AbstractType type = operatedResult.get(0, unsetType); // Get operator. - final String operatorStr = node.getTokenValue(); - final UnaryOperator.Operator operator = UnaryOperator.Operator.valueFor(operatorStr); - final UnaryOperator unaryOperator = this.typeKeeper.getUnaryOperator(operator, type); + final String operatorStr = node.getTokenValue().toLowerCase(); + final String operatorMethod = UNARY_OPERATOR_METHODS.get(operatorStr); // Apply opertor to operand and store result. - final AbstractType resultingType = unaryOperator != null - ? unaryOperator.getResultType() - : UndefinedType.INSTANCE; - final ExpressionResult result = new ExpressionResult(resultingType); + final ExpressionResult result = this.getMethodInvocationResult(type, operatorMethod) + .substituteType(SelfType.INSTANCE, type); + this.setNodeType(node, result); } @@ -1122,20 +1163,49 @@ protected void walkPostMethodInvocation(final AstNode node) { callResult = ExpressionResult.UNDEFINED; this.setIteratorType(ExpressionResult.UNDEFINED); } else { + final List argumentExpressionNodes = helper.getArgumentExpressionNodes(); + final List argumentTypes = argumentExpressionNodes.stream() + .map(exprNode -> this.getNodeType(exprNode).get(0, unsetType)) + .collect(Collectors.toList()); for (final Method method : methods) { // Call. - if (callResult == null) { - callResult = method.getCallResult(); - } else { - final ExpressionResult methodResult = method.getCallResult(); - callResult = new ExpressionResult(callResult, methodResult, unsetType); - } - if (originalCalledType != SelfType.INSTANCE) { - callResult = callResult.substituteType(SelfType.INSTANCE, calledType); + final ExpressionResultString methodCallResultStr = method.getCallResult(); + final ExpressionResult methodCallResultBare = + this.typeParser.parseExpressionResultString(methodCallResultStr); + ExpressionResult methodCallResult = originalCalledType != SelfType.INSTANCE + ? methodCallResultBare.substituteType(SelfType.INSTANCE, calledType) + : methodCallResultBare; + + final Map paramRefTypeMap = IntStream + .range(0, method.getParameters().size()) + .mapToObj(i -> { + final Parameter param = method.getParameters().get(i); + final String paramName = param.getName(); + final ParameterReferenceType paramRefType = new ParameterReferenceType(paramName); + final AbstractType argType = i < argumentTypes.size() + ? argumentTypes.get(i) + : unsetType; // TODO: What about gather parameters? + return new AbstractMap.SimpleEntry<>(paramRefType, argType); + }) + .collect(Collectors.toMap( + entry -> entry.getKey(), + entry -> entry.getValue())); + for (final Map.Entry entry : paramRefTypeMap.entrySet()) { + final ParameterReferenceType paramRefType = entry.getKey(); + final AbstractType argType = entry.getValue(); + methodCallResult = methodCallResult.substituteType(paramRefType, argType); } + callResult = callResult == null + ? methodCallResult + : new ExpressionResult(callResult, methodCallResult, unsetType); // Iterator. - final ExpressionResult loopbodyResult = method.getLoopbodyResult(); + final ExpressionResultString loopbodyResultStr = method.getLoopbodyResult(); + final ExpressionResult loopbodyResultBare = + this.typeParser.parseExpressionResultString(loopbodyResultStr); + final ExpressionResult loopbodyResult = originalCalledType != SelfType.INSTANCE + ? loopbodyResultBare.substituteType(SelfType.INSTANCE, calledType) + : loopbodyResultBare; if (this.getIteratorType() == null) { this.setIteratorType(loopbodyResult); } else { @@ -1143,12 +1213,6 @@ protected void walkPostMethodInvocation(final AstNode node) { new ExpressionResult(this.iteratorType, loopbodyResult, unsetType); this.setIteratorType(iterResult); } - - if (originalCalledType != SelfType.INSTANCE) { - final ExpressionResult subbedResult = - this.getIteratorType().substituteType(SelfType.INSTANCE, calledType); - this.setIteratorType(subbedResult); - } } } @@ -1161,6 +1225,8 @@ protected void walkPostMethodInvocation(final AstNode node) { protected void walkPostProcedureInvocation(final AstNode node) { final AbstractType unsetType = this.typeKeeper.getType(SW_UNSET); + // TODO: Handle sw:obj/sw:prototype. + // Get called type for invocation. final AstNode calledNode = node.getPreviousSibling(); final ExpressionResult calledResult = this.getNodeType(calledNode); @@ -1182,9 +1248,11 @@ protected void walkPostProcedureInvocation(final AstNode node) { final Collection methods = procedureType.getMethods("invoke()"); final Method method = methods.stream().findAny().orElse(null); Objects.requireNonNull(method); - callResult = method.getCallResult(); + final ExpressionResultString callResultStr = method.getCallResult(); + callResult = this.typeParser.parseExpressionResultString(callResultStr); - final ExpressionResult loopbodyResult = method.getLoopbodyResult(); + final ExpressionResultString loopbodyResultStr = method.getLoopbodyResult(); + final ExpressionResult loopbodyResult = this.typeParser.parseExpressionResultString(loopbodyResultStr); this.setIteratorType(loopbodyResult); if (originalCalledType == SelfType.INSTANCE) { @@ -1212,12 +1280,12 @@ private AbstractType getMethodOwnerType(final AstNode node) { } final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(methodDefNode); - final GlobalReference globalRef = helper.getTypeGlobalReference(); - return this.typeKeeper.getType(globalRef); + final TypeString typeString = helper.getTypeString(); + return this.typeKeeper.getType(typeString); } /** - * Get the resulting {{ExpressionResult}} from a method invocation. + * Get the resulting {@link ExpressionResult} from a method invocation. * @param calledType Type method is invoked on. * @param methodName Name of method to invoke. * @return Result of invocation. @@ -1226,6 +1294,7 @@ private ExpressionResult getMethodInvocationResult(final AbstractType calledType final AbstractType unsetType = this.typeKeeper.getType(SW_UNSET); return calledType.getMethods(methodName).stream() .map(method -> method.getCallResult()) + .map(expressionResultString -> this.typeParser.parseExpressionResultString(expressionResultString)) .reduce((result, element) -> new ExpressionResult(result, element, unsetType)) .orElse(ExpressionResult.UNDEFINED); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ReadOnlyTypeKeeperAdapter.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ReadOnlyTypeKeeperAdapter.java index 554ad6b2..6f1472bd 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ReadOnlyTypeKeeperAdapter.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/ReadOnlyTypeKeeperAdapter.java @@ -4,8 +4,9 @@ import java.util.Set; import javax.annotation.CheckForNull; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; import nl.ramsolutions.sw.magik.analysis.typing.types.Package; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; /** * Readonly wrapper for TypeKeeper. @@ -44,8 +45,8 @@ public Set getPackages() { } @Override - public boolean hasType(final GlobalReference globalReference) { - return this.typeKeeper.hasType(globalReference); + public boolean hasType(final TypeString typeString) { + return this.typeKeeper.hasType(typeString); } @Override @@ -54,8 +55,8 @@ public void addType(final AbstractType type) { } @Override - public AbstractType getType(final GlobalReference globalReference) { - return this.typeKeeper.getType(globalReference); + public AbstractType getType(final TypeString typeString) { + return this.typeKeeper.getType(typeString); } @Override @@ -68,43 +69,46 @@ public Collection getTypes() { return this.typeKeeper.getTypes(); } - @CheckForNull @Override - public void addUnaryOperator(final UnaryOperator unaryOperator) { + public void addBinaryOperator(final BinaryOperator binaryOperator) { // Do nothing. } + @CheckForNull @Override - public UnaryOperator getUnaryOperator(final UnaryOperator.Operator operator, final AbstractType type) { - return this.typeKeeper.getUnaryOperator(operator, type); + public BinaryOperator getBinaryOperator( + final BinaryOperator.Operator operator, + final AbstractType leftType, + final AbstractType rightType) { + return this.typeKeeper.getBinaryOperator(operator, leftType, rightType); } @Override - public void removeUnaryOperator(final UnaryOperator unaryOperator) { + public void removeBinaryOperator(final BinaryOperator binaryOperator) { // Do nothing. } @Override - public void addBinaryOperator(final BinaryOperator binaryOperator) { + public void clear() { // Do nothing. } - @CheckForNull @Override - public BinaryOperator getBinaryOperator( - final BinaryOperator.Operator operator, - final AbstractType leftType, - final AbstractType rightType) { - return this.typeKeeper.getBinaryOperator(operator, leftType, rightType); + public void addCondition(final Condition condition) { + // Do nothing. } @Override - public void removeBinaryOperator(final BinaryOperator binaryOperator) { - // Do nothing. + public Condition getCondition(final String name) { + return this.typeKeeper.getCondition(name); + } + + public Collection getConditions() { + return this.typeKeeper.getConditions(); } @Override - public void clear() { + public void removeCondition(final Condition condition) { // Do nothing. } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandler.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandler.java deleted file mode 100644 index 5484d6fa..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing; - -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.Token; -import com.sonar.sslr.api.Trivia; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.CheckForNull; -import nl.ramsolutions.sw.magik.api.MagikGrammar; - -/** - * Read type providing/overriding from lines. - */ -public final class TypeAnnotationHandler { - - private static final Pattern TYPE_PATTERN_EOL = Pattern.compile(".*# *type: *(.*)"); - private static final Pattern ITER_TYPE_PATTERN_EOL = Pattern.compile(".*# *iter-type: *(.*)"); - - private TypeAnnotationHandler() { - } - - /** - * Get the type annotaiton for a given EXPRESSION node. - * This finds a comment in the form of "# type: exemplar". - * @param expressionNode {{EXPRESSION}} node. - * @return Type annotation. - */ - @CheckForNull - public static String typeAnnotationForExpression(final AstNode expressionNode) { - final String comment = TypeAnnotationHandler.getCommentForNode(expressionNode); - if (comment == null) { - return null; - } - - return TypeAnnotationHandler.extractPattern(comment, TypeAnnotationHandler.TYPE_PATTERN_EOL); - } - - /** - * Get the type annotaiton for a given EXPRESSION node. - * This finds a comment in the form of "# iter-type: exemplar". - * @param expressionNode {{EXPRESSION}} node. - * @return Type annotation. - */ - @CheckForNull - public static String iterTypeAnnotationForExpression(final AstNode expressionNode) { - final String comment = TypeAnnotationHandler.getCommentForNode(expressionNode); - if (comment == null) { - return null; - } - - return TypeAnnotationHandler.extractPattern(comment, TypeAnnotationHandler.ITER_TYPE_PATTERN_EOL); - } - - @CheckForNull - private static String extractPattern(final String comment, final Pattern pattern) { - final Matcher matcher = pattern.matcher(comment); - if (!matcher.matches()) { - return null; - } - - return matcher.group(1); - } - - @CheckForNull - private static String getCommentForNode(final AstNode expressionNode) { - // Try to speed up getting comments: limit number of nodes to extract comments from. - final AstNode node = expressionNode.getFirstAncestor( - MagikGrammar.METHOD_DEFINITION, - MagikGrammar.PROCEDURE_DEFINITION, - MagikGrammar.MAGIK); - final int line = expressionNode.getTokenLine(); - return node.getTokens().stream() - .flatMap(token -> token.getTrivia().stream()) - .filter(Trivia::isComment) - .map(Trivia::getToken) - .filter(token -> token.getLine() == line) - .map(Token::getValue) - .findAny() - .orElse(null); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeKeeper.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeKeeper.java index fe5b1543..80c30f45 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeKeeper.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeKeeper.java @@ -1,6 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.typing; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -8,12 +9,12 @@ import java.util.Set; import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IndexedType; -import nl.ramsolutions.sw.magik.analysis.typing.types.IntrinsicType; -import nl.ramsolutions.sw.magik.analysis.typing.types.ObjectType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Package; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.ProcedureInstance; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +27,8 @@ public class TypeKeeper implements ITypeKeeper { private static final Logger LOGGER = LoggerFactory.getLogger(TypeKeeper.class); private final Map packages = new HashMap<>(); - private final Set unaryOperators = new HashSet<>(); private final Set binaryOperators = new HashSet<>(); + private final Map conditions = new HashMap<>(); /** * Constructor. @@ -39,7 +40,6 @@ public TypeKeeper() { @Override public void clear() { this.packages.clear(); - this.unaryOperators.clear(); this.binaryOperators.clear(); this.registerRequiredPackages(); @@ -50,40 +50,56 @@ public void clear() { * Register required packages (sw and user). */ public void registerRequiredPackages() { - final Package swPackage = new Package("sw"); - this.addPackage(swPackage); + new Package(this, "sw"); - final Package userPackage = new Package("user"); - userPackage.addUse(swPackage); - this.addPackage(userPackage); + final Package userPackage = new Package(this, "user"); + userPackage.addUse("sw"); } private void registerRequiredTypes() { final Package swPakkage = this.getPackage("sw"); - swPakkage.put("object", new ObjectType(GlobalReference.of("sw:object"))); - swPakkage.put("unset", new IntrinsicType(GlobalReference.of("sw:unset"))); - swPakkage.put("false", new IntrinsicType(GlobalReference.of("sw:false"))); - swPakkage.put("maybe", new IntrinsicType(GlobalReference.of("sw:maybe"))); - swPakkage.put("integer", new IntrinsicType(GlobalReference.of("sw:integer"))); - swPakkage.put("bignum", new IntrinsicType(GlobalReference.of("sw:bignum"))); - swPakkage.put("float", new IntrinsicType(GlobalReference.of("sw:float"))); - swPakkage.put("symbol", new IndexedType(GlobalReference.of("sw:symbol"))); - swPakkage.put("character", new IntrinsicType(GlobalReference.of("sw:character"))); - swPakkage.put("sw_regexp", new IntrinsicType(GlobalReference.of("sw:sw_regexp"))); - swPakkage.put("char16_vector", new IndexedType(GlobalReference.of("sw:char16_vector"))); - swPakkage.put("simple_vector", new IndexedType(GlobalReference.of("sw:simple_vector"))); - swPakkage.put("heavy_thread", new IntrinsicType(GlobalReference.of("sw:heavy_thread"))); - swPakkage.put("light_thread", new IntrinsicType(GlobalReference.of("sw:light_thread"))); - swPakkage.put("condition", new SlottedType(GlobalReference.of("sw:condition"))); - swPakkage.put("enumeration_value", new SlottedType(GlobalReference.of("sw:enumeration_value"))); - swPakkage.put("indexed_format_mixin", new IntrinsicType(GlobalReference.of("sw:indexed_format_mixin"))); - swPakkage.put("slotted_format_mixin", new IntrinsicType(GlobalReference.of("sw:slotted_format_mixin"))); + swPakkage.put("object", new MagikType(this, Sort.OBJECT, TypeString.of("sw:object"))); + swPakkage.put("unset", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:unset"))); + swPakkage.put("false", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:false"))); + swPakkage.put("maybe", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:maybe"))); + swPakkage.put("integer", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:integer"))); + swPakkage.put("bignum", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:bignum"))); + swPakkage.put("float", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:float"))); + swPakkage.put("symbol", new MagikType(this, Sort.INDEXED, TypeString.of("sw:symbol"))); + swPakkage.put("character", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:character"))); + swPakkage.put("sw_regexp", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:sw_regexp"))); + swPakkage.put("procedure", new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:procedure"))); + swPakkage.put( + "char16_vector", new MagikType(this, Sort.INDEXED, TypeString.of("sw:char16_vector"))); + swPakkage.put( + "simple_vector", new MagikType(this, Sort.INDEXED, TypeString.of("sw:simple_vector"))); + swPakkage.put( + "heavy_thread", + new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:heavy_thread"))); + swPakkage.put( + "light_thread", + new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:light_thread"))); + swPakkage.put("condition", new MagikType(this, Sort.SLOTTED, TypeString.of("sw:condition"))); + swPakkage.put( + "enumeration_value", + new MagikType(this, Sort.SLOTTED, TypeString.of("sw:enumeration_value"))); + swPakkage.put( + "indexed_format_mixin", + new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:indexed_format_mixin"))); + swPakkage.put( + "slotted_format_mixin", + new MagikType(this, Sort.INTRINSIC, TypeString.of("sw:slotted_format_mixin"))); } @Override public void addPackage(final Package pakkage) { - this.packages.put(pakkage.getName(), pakkage); + final String pakkageName = pakkage.getName(); + if (this.hasPackage(pakkageName)) { + throw new IllegalStateException(); + } + + this.packages.put(pakkageName, pakkage); } public boolean hasPackage(final String pakkageName) { @@ -95,7 +111,8 @@ public Package getPackage(final String pakkageName) { if (!this.packages.containsKey(pakkageName)) { LOGGER.debug("Undefined package: {}", pakkageName); } - return this.packages.computeIfAbsent(pakkageName, Package::new); + + return this.packages.get(pakkageName); } @Override @@ -111,32 +128,61 @@ public Set getPackages() { } @Override - public boolean hasType(final GlobalReference globalReference) { - final String pakkageName = globalReference.getPakkage(); + public boolean hasType(final TypeString typeString) { + if (!typeString.isSingle()) { + throw new IllegalStateException(); + } + + final String pakkageName = typeString.getPakkage(); final Package pakkage = this.getPackage(pakkageName); - final String identifier = globalReference.getIdentifier(); + final String identifier = typeString.getIdentifier(); return pakkage != null && pakkage.containsKey(identifier); } @Override public void addType(final AbstractType type) { - final GlobalReference globalRef = GlobalReference.of(type.getFullName()); - final String pakkageName = globalRef.getPakkage(); + if (type instanceof ProcedureInstance) { + // A procedure instance is never added/globally referrable, + // unless it is assigned to a global through a AliasType. + return; + } + + final TypeString typeString = type.getTypeString(); + final String pakkageName = typeString.getPakkage(); + if (!this.hasPackage(pakkageName)) { + // Not allowed to add packages. + throw new IllegalStateException("Unknown package: " + pakkageName); + } + final Package pakkage = this.getPackage(pakkageName); - final String identifier = globalRef.getIdentifier(); + final String identifier = typeString.getIdentifier(); + if (pakkage.containsKey(identifier)) { + // Not allowed to overwrite types. + throw new IllegalStateException("Type already defined: " + typeString.getFullString()); + } + pakkage.put(identifier, type); } @Override - public AbstractType getType(final GlobalReference globalReference) { - final String pakkageName = globalReference.getPakkage(); + public AbstractType getType(final TypeString typeString) { + final String pakkageName = typeString.getPakkage(); + if (!this.hasPackage(pakkageName)) { + return UndefinedType.INSTANCE; + } + + if (!typeString.isSingle()) { + throw new IllegalStateException(); + } + final Package pakkage = this.getPackage(pakkageName); - final String identifier = globalReference.getIdentifier(); - if (!pakkage.containsKey(identifier)) { + final String identifier = typeString.getIdentifier(); + final AbstractType type = pakkage.get(identifier); + if (type == null) { return UndefinedType.INSTANCE; } - return pakkage.get(identifier); + return type; } @Override @@ -156,29 +202,6 @@ public Collection getTypes() { .collect(Collectors.toUnmodifiableSet()); } - // region: Operators - // region: Unary operators - @Override - public void addUnaryOperator(final UnaryOperator unaryOperator) { - this.unaryOperators.add(unaryOperator); - } - - @Override - public UnaryOperator getUnaryOperator(final UnaryOperator.Operator operator, final AbstractType type) { - return this.unaryOperators.stream() - .filter(unaryOperator -> - unaryOperator.getOperator() == operator - && unaryOperator.getType().equals(type)) - .findAny() - .orElse(null); - } - - @Override - public void removeUnaryOperator(final UnaryOperator unaryOperator) { - this.unaryOperators.remove(unaryOperator); - } - // endregion - // region: Binary operators @Override public void addBinaryOperator(final BinaryOperator binaryOperator) { @@ -193,8 +216,8 @@ public BinaryOperator getBinaryOperator( return this.binaryOperators.stream() .filter(binaryOperator -> binaryOperator.getOperator() == operator - && binaryOperator.getLeftType().equals(leftType) - && binaryOperator.getRightType().equals(rightType)) + && binaryOperator.getLeftType().equals(leftType.getTypeString()) + && binaryOperator.getRightType().equals(rightType.getTypeString())) .findAny() .orElse(null); } @@ -204,6 +227,26 @@ public void removeBinaryOperator(final BinaryOperator binaryOperator) { this.binaryOperators.remove(binaryOperator); } // endregion - // endregion + + @Override + public void addCondition(final Condition condition) { + final String name = condition.getName(); + this.conditions.put(name, condition); + } + + @Override + public Condition getCondition(final String name) { + return this.conditions.get(name); + } + + public Collection getConditions() { + return Collections.unmodifiableCollection(this.conditions.values()); + } + + @Override + public void removeCondition(final Condition condition) { + final String name = condition.getName(); + this.conditions.remove(name); + } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParser.java index 78dea91c..e7cb3fc7 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParser.java @@ -1,25 +1,20 @@ package nl.ramsolutions.sw.magik.analysis.typing; -import java.util.regex.Pattern; -import java.util.stream.Stream; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; +import nl.ramsolutions.sw.magik.analysis.typing.types.ParameterReferenceType; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; -import nl.ramsolutions.sw.magik.api.MagikKeyword; -import nl.ramsolutions.sw.magik.api.NewDocGrammar; /** * Type parser. */ public final class TypeParser { - private static final String TYPE_COMBINATOR_RE = Pattern.quote(NewDocGrammar.Punctuator.TYPE_COMBINATOR.getValue()); - private static final String TYPE_SEPARATOR_RE = Pattern.quote(NewDocGrammar.Punctuator.TYPE_SEPARATOR.getValue()); - private static final String TYPE_SEPARATOR = NewDocGrammar.Punctuator.TYPE_SEPARATOR.getValue(); private final ITypeKeeper typeKeeper; /** @@ -34,74 +29,57 @@ public TypeParser(final ITypeKeeper typeKeeper) { * Parse a type string and return the type. The result can be a {@Link CombinedType} type when types are combined * with a {@code |}-sign. * @param typeString String to parse. - * @param currentPakkage Package in context. * @return Parsed type. */ - public AbstractType parseTypeString(final @Nullable String typeString, final String currentPakkage) { + public AbstractType parseTypeString(final @Nullable TypeString typeString) { if (typeString == null - || typeString.isBlank()) { + || typeString.isUndefined()) { return UndefinedType.INSTANCE; } - return Stream.of(typeString.split(TYPE_COMBINATOR_RE)) + return typeString.parts().stream() .map(typeStr -> { - if (typeStr.equalsIgnoreCase(SelfType.SERIALIZED_NAME) - || typeStr.equalsIgnoreCase(MagikKeyword.SELF.getValue()) - || typeStr.equalsIgnoreCase(MagikKeyword.CLONE.getValue())) { + if (typeStr.isSelf()) { return SelfType.INSTANCE; - } else if (typeStr.equalsIgnoreCase(UndefinedType.SERIALIZED_NAME)) { + } else if (typeStr.isUndefined()) { return UndefinedType.INSTANCE; + } else if (typeStr.isParameterReference()) { + final String paramName = typeStr.referencedParameter(); + return new ParameterReferenceType(paramName); } - final GlobalReference globalRef = this.getGlobalRefeference(typeStr, currentPakkage); - return this.typeKeeper.getType(globalRef); + return this.typeKeeper.getType(typeStr); }) .reduce(CombinedType::combine) .orElse(UndefinedType.INSTANCE); } /** - * Parse `identifier`. - * @param typeString Identifier, may be prefixed with `:`. - * @param currentPakkage Current package. - * @return Global reference. - */ - public GlobalReference getGlobalRefeference(final String typeString, final String currentPakkage) { - final int index = typeString.indexOf(':'); - final String pakkage = index != -1 - ? typeString.substring(0, index).trim() - : currentPakkage; - final String identifier = index != -1 - ? typeString.substring(index + 1).trim() - : typeString; - return GlobalReference.of(pakkage, identifier); - } - - /** - * Parse {@link ExpressionResult} from string. - * @param expressionResultString String to parse. - * @param currentPackage Package in context. + * Parse {@link ExpressionResult} from {@link ExpressionResultString}. + * @param expressionResultString {@link ExpressionResultString} to parse. * @return Parsed result. */ public ExpressionResult parseExpressionResultString( - final @Nullable String expressionResultString, final String currentPackage) { - if (expressionResultString == null - || expressionResultString.isBlank() - || expressionResultString.equalsIgnoreCase(ExpressionResult.UNDEFINED_SERIALIZED_NAME)) { + final @Nullable ExpressionResultString expressionResultString) { + if (expressionResultString == null) { return ExpressionResult.UNDEFINED; } - return Stream.of(expressionResultString.split(TYPE_SEPARATOR_RE)) - .map(typeString -> this.parseTypeString(typeString, currentPackage)) + return expressionResultString.stream() + .map(typeString -> this.parseTypeString(typeString)) .collect(ExpressionResult.COLLECTOR); } - public static String stringifyType(final AbstractType type) { - return type.getFullName(); - } - - public static String stringifyExpressionResult(final ExpressionResult result) { - return result.getTypeNames(TypeParser.TYPE_SEPARATOR); + /** + * Unparse an {@link ExpressionResult} to an {@link ExpressionResultString}. + * @param expressionResult + * @return Expression result string. + */ + public static ExpressionResultString unparseExpressionResult(final ExpressionResult expressionResult) { + return expressionResult.stream() + .map(AbstractType::getFullName) + .map(TypeString::new) + .collect(ExpressionResultString.COLLECTOR); } } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/UnaryOperator.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/UnaryOperator.java deleted file mode 100644 index a73223fd..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/UnaryOperator.java +++ /dev/null @@ -1,125 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing; - -import java.util.Objects; -import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; - -/** - * A key type to be used in a Map. - */ -public class UnaryOperator { - - /** - * Operator. - */ - @SuppressWarnings("checkstyle:JavadocVariable") - public enum Operator { - - NOT("~"), - PLUS("+"), - MINUS("-"), - - ALLRESULTS_KEYWORD("_ALLRESULTS"), - NOT_KEYWORD("_not"), - SCATTER_KEYWORD("_scatter"); - - private final String value; - - Operator(final String value) { - this.value = value.toLowerCase(); - } - - public String getValue() { - return this.value; - } - - /** - * Get Operator for value. - * @param value Value to get Operator for. - * @return Operator. - */ - public static Operator valueFor(final String value) { - final String valueLower = value.toLowerCase(); - for (final Operator operator : Operator.values()) { - if (operator.getValue().equals(valueLower)) { - return operator; - } - } - - throw new IllegalStateException("Unknown operator: " + valueLower); - } - - } - - private final Operator operator; - private final AbstractType type; - private final AbstractType resultType; - - /** - * Constructor. - * @param operator Operator name. - * @param type MagikType operator is applied to. - */ - public UnaryOperator(final Operator operator, final AbstractType type, final AbstractType resultType) { - this.operator = operator; - this.resultType = resultType; - this.type = type; - } - - /** - * Get operator. - * @return Operator. - */ - public Operator getOperator() { - return this.operator; - } - - /** - * Get type. - * @return Type. - */ - public AbstractType getType() { - return this.type; - } - - /** - * Get result type. - * @return Type. - */ - public AbstractType getResultType() { - return this.resultType; - } - - @Override - public int hashCode() { - return Objects.hash(this.operator, this.type, this.resultType); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final UnaryOperator other = (UnaryOperator) obj; - return Objects.equals(this.operator, other.operator) - && Objects.equals(this.type, other.type) - && Objects.equals(this.resultType, other.resultType); - } - - @Override - public String toString() { - return String.format( - "%s@%s(%s, %s, %s)", - this.getClass().getName(), System.identityHashCode(this), - this.operator, this.type, this.resultType); - } - -} diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikIndexer.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/MagikIndexer.java similarity index 59% rename from magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikIndexer.java rename to magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/MagikIndexer.java index 7aaf39dd..8f322e78 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/MagikIndexer.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/MagikIndexer.java @@ -1,4 +1,4 @@ -package nl.ramsolutions.sw.magik.languageserver.indexer; +package nl.ramsolutions.sw.magik.analysis.typing.indexer; import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.Token; @@ -20,6 +20,7 @@ import nl.ramsolutions.sw.magik.MagikFile; import nl.ramsolutions.sw.magik.analysis.Location; import nl.ramsolutions.sw.magik.analysis.definitions.BinaryOperatorDefinition; +import nl.ramsolutions.sw.magik.analysis.definitions.ConditionDefinition; import nl.ramsolutions.sw.magik.analysis.definitions.Definition; import nl.ramsolutions.sw.magik.analysis.definitions.EnumerationDefinition; import nl.ramsolutions.sw.magik.analysis.definitions.GlobalDefinition; @@ -29,6 +30,7 @@ import nl.ramsolutions.sw.magik.analysis.definitions.PackageDefinition; import nl.ramsolutions.sw.magik.analysis.definitions.ParameterDefinition; import nl.ramsolutions.sw.magik.analysis.definitions.SlottedExemplarDefinition; +import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.MethodInvocationNodeHelper; import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.scope.GlobalScope; @@ -36,23 +38,22 @@ import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry; import nl.ramsolutions.sw.magik.analysis.typing.BinaryOperator; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.TypeAnnotationHandler; import nl.ramsolutions.sw.magik.analysis.typing.TypeParser; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.AliasType; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IndexedType; -import nl.ramsolutions.sw.magik.analysis.typing.types.IntrinsicType; +import nl.ramsolutions.sw.magik.analysis.typing.types.Condition; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.analysis.typing.types.Package; import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; import nl.ramsolutions.sw.magik.analysis.typing.types.Slot; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; +import nl.ramsolutions.sw.magik.parser.CommentInstructionReader; import nl.ramsolutions.sw.magik.parser.MagikCommentExtractor; import nl.ramsolutions.sw.magik.parser.NewDocParser; import org.slf4j.Logger; @@ -70,6 +71,13 @@ */ public class MagikIndexer { + // TODO: Now that we no longer have the MagikPreIndexer, will every method definition land at the right type? + // I.e., in the case of these events, in order: + // - indexer sees type.method(), no _package, resolved to to user:type + // - indexer sees def_slotted_exemplar(:type, {}), _package sw, creates type sw:type + // Now there are two types `type`, one is from the method, other is from the real definition. + // Fix this by adding a post-process step? Or back to the MagikPreIndexer? + private static final Logger LOGGER = LoggerFactory.getLogger(MagikIndexer.class); private static final Map PARAMETER_MODIFIER_MAPPING = Map.of( @@ -81,6 +89,9 @@ public class MagikIndexer { MethodDefinition.Modifier.ITER, Method.Modifier.ITER, MethodDefinition.Modifier.PRIVATE, Method.Modifier.PRIVATE); + private static final CommentInstructionReader.InstructionType TYPE_INSTRUCTION = + CommentInstructionReader.InstructionType.createInstructionType("type"); + private final ITypeKeeper typeKeeper; private final TypeParser typeParser; private final Map> indexedPackages = new HashMap<>(); @@ -88,6 +99,7 @@ public class MagikIndexer { private final Map> indexedMethods = new HashMap<>(); private final Map> indexedGlobals = new HashMap<>(); private final Map> indexedBinaryOperators = new HashMap<>(); + private final Map> indexedConditions = new HashMap<>(); public MagikIndexer(final ITypeKeeper typeKeeper) { this.typeKeeper = typeKeeper; @@ -175,142 +187,141 @@ private void handleDefinition(final Path path, final MagikFile magikFile, final } else if (definition instanceof BinaryOperatorDefinition) { final BinaryOperatorDefinition binaryOperatorDefinition = (BinaryOperatorDefinition) definition; this.handleDefinition(magikFile, binaryOperatorDefinition); + } else if (definition instanceof ConditionDefinition) { + final ConditionDefinition conditionDefinition = (ConditionDefinition) definition; + this.handleDefinition(magikFile, conditionDefinition); } } @SuppressWarnings("checkstyle:NestedIfDepth") private void handleDefinition(final MagikFile magikFile, final PackageDefinition definition) { final String name = definition.getName(); + final Package pakkage; if (!this.typeKeeper.hasPackage(name)) { // Create new package. - final Package pakkage = new Package(name); - final AstNode node = definition.getNode(); - final URI uri = magikFile.getUri(); - final Location location = new Location(uri, node); - pakkage.setLocation(location); - this.typeKeeper.addPackage(pakkage); - - // Add uses. - definition.getUses().stream() - .forEach(uses -> { - final Package usesPakkage = this.typeKeeper.getPackage(uses); - if (usesPakkage != null) { - pakkage.addUse(usesPakkage); - } - }); - - LOGGER.debug("Indexed package: {}", pakkage); + pakkage = new Package(this.typeKeeper, name); + } else { + pakkage = this.typeKeeper.getPackage(name); } - final Path path = Paths.get(magikFile.getUri()); - final Package pakkage = this.typeKeeper.getPackage(name); + final AstNode node = definition.getNode(); + final URI uri = magikFile.getUri(); + final Location location = new Location(uri, node); + pakkage.setLocation(location); + + // Add uses. + pakkage.clearUses(); + definition.getUses() + .forEach(pakkage::addUse); + + LOGGER.debug("Indexed package: {}", pakkage); + + final Path path = Paths.get(uri); this.indexedPackages.get(path).add(pakkage); } private void handleDefinition(final MagikFile magikFile, final IndexedExemplarDefinition definition) { + this.ensurePackageExists(definition); + final AstNode node = definition.getNode(); - final GlobalReference globalRef = definition.getGlobalReference(); - final MagikType type = this.typeKeeper.getType(globalRef) != UndefinedType.INSTANCE - ? (MagikType) this.typeKeeper.getType(globalRef) - : new IndexedType(globalRef); - this.typeKeeper.addType(type); - - final Map slots = Collections.emptyMap(); - final List parents = definition.getParents(); - final MagikType defaultParentType = - (MagikType) this.typeKeeper.getType(GlobalReference.of("sw:indexed_format_mixin")); - this.fillType(type, magikFile, node, globalRef.getPakkage(), slots, parents, defaultParentType); + final TypeString typeString = definition.getTypeString(); + final MagikType magikType = this.findType(typeString, MagikType.Sort.INDEXED); + + final Map slots = Collections.emptyMap(); + final List parents = definition.getParents(); + final TypeString defaultParentRef = TypeString.of("sw:indexed_format_mixin"); + this.fillType(magikType, magikFile, node, typeString.getPakkage(), slots, parents, defaultParentRef); final Path path = Paths.get(magikFile.getUri()); - this.indexedTypes.get(path).add(type); + this.indexedTypes.get(path).add(magikType); - LOGGER.debug("Indexed type: {}", type); + LOGGER.debug("Indexed type: {}", magikType); } private void handleDefinition(final MagikFile magikFile, final EnumerationDefinition definition) { + this.ensurePackageExists(definition); + final AstNode node = definition.getNode(); - final GlobalReference globalRef = definition.getGlobalReference(); - final MagikType type = this.typeKeeper.getType(globalRef) != UndefinedType.INSTANCE - ? (MagikType) this.typeKeeper.getType(globalRef) - : new SlottedType(globalRef); - this.typeKeeper.addType(type); - - final Map slots = Collections.emptyMap(); - final List parents = definition.getParents(); - final MagikType defaultParentType = - (MagikType) this.typeKeeper.getType(GlobalReference.of("sw:enumeration_value")); - this.fillType(type, magikFile, node, globalRef.getPakkage(), slots, parents, defaultParentType); + final TypeString typeString = definition.getTypeString(); + final MagikType magikType = this.findType(typeString, MagikType.Sort.SLOTTED); + + final Map slots = Collections.emptyMap(); + final List parents = definition.getParents(); + final TypeString defaultParentRef = TypeString.of("sw:enumeration_value"); + this.fillType(magikType, magikFile, node, typeString.getPakkage(), slots, parents, defaultParentRef); final Path path = Paths.get(magikFile.getUri()); - this.indexedTypes.get(path).add(type); + this.indexedTypes.get(path).add(magikType); - LOGGER.debug("Indexed type: {}", type); + LOGGER.debug("Indexed type: {}", magikType); } private void handleDefinition(final MagikFile magikFile, final SlottedExemplarDefinition definition) { + this.ensurePackageExists(definition); + final AstNode node = definition.getNode(); - final GlobalReference globalRef = definition.getGlobalReference(); - final MagikType type = this.typeKeeper.getType(globalRef) instanceof SlottedType - ? (MagikType) this.typeKeeper.getType(globalRef) - : new SlottedType(globalRef); - this.typeKeeper.addType(type); + final TypeString typeString = definition.getTypeString(); + final MagikType magikType = this.findType(typeString, MagikType.Sort.SLOTTED); final NewDocParser docParser = new NewDocParser(node); - final Map slotTypes = docParser.getSlotTypes(); + final Map slotTypes = docParser.getSlotTypes(); // This needs a default value ("") due to https://bugs.openjdk.java.net/browse/JDK-8148463 - final Map slots = definition.getSlots().stream() + final Map slots = definition.getSlots().stream() .map(SlottedExemplarDefinition.Slot::getName) .collect(Collectors.toMap( slotName -> slotName, - slotName -> slotTypes.getOrDefault(slotName, ""))); - final List parents = definition.getParents(); - final MagikType defaultParentType = - (MagikType) this.typeKeeper.getType(GlobalReference.of("sw:slotted_format_mixin")); - this.fillType(type, magikFile, node, globalRef.getPakkage(), slots, parents, defaultParentType); + slotName -> slotTypes.getOrDefault(slotName, TypeString.UNDEFINED))); + final List parents = definition.getParents(); + final TypeString defaultParentRef = TypeString.of("sw:slotted_format_mixin"); + this.fillType(magikType, magikFile, node, typeString.getPakkage(), slots, parents, defaultParentRef); final Path path = Paths.get(magikFile.getUri()); - this.indexedTypes.get(path).add(type); + this.indexedTypes.get(path).add(magikType); - LOGGER.debug("Indexed type: {}", type); + LOGGER.debug("Indexed type: {}", magikType); } private void handleDefinition(final MagikFile magikFile, final MixinDefinition definition) { + this.ensurePackageExists(definition); + final AstNode node = definition.getNode(); - final GlobalReference globalRef = definition.getGlobalReference(); - final MagikType type = this.typeKeeper.getType(globalRef) != UndefinedType.INSTANCE - ? (MagikType) this.typeKeeper.getType(globalRef) - : new IntrinsicType(globalRef); - this.typeKeeper.addType(type); + final TypeString typeString = definition.getTypeString(); + final MagikType magikType = this.findType(typeString, MagikType.Sort.INTRINSIC); - final Map slots = Collections.emptyMap(); - final List parents = definition.getParents(); - this.fillType(type, magikFile, node, globalRef.getPakkage(), slots, parents, null); + final Map slots = Collections.emptyMap(); + final List parents = definition.getParents(); + this.fillType(magikType, magikFile, node, typeString.getPakkage(), slots, parents, null); final Path path = Paths.get(magikFile.getUri()); - this.indexedTypes.get(path).add(type); + this.indexedTypes.get(path).add(magikType); - LOGGER.debug("Indexed type: {}", type); + LOGGER.debug("Indexed type: {}", magikType); } private void handleDefinition(final MagikFile magikFile, final MethodDefinition definition) { + this.ensurePackageExists(definition); + final AstNode node = definition.getNode(); - if (node.isNot(MagikGrammar.METHOD_DEFINITION)) { + if (!definition.isActualMethodDefinition()) { // No slot accessors, shared variables, shared constants. this.handleMethodDefinitionOther(magikFile, definition); return; } // Get exemplar. - final GlobalReference globalRef = definition.getTypeGlobalReference(); - final AbstractType exemplarType = this.typeKeeper.getType(globalRef); - if (exemplarType == UndefinedType.INSTANCE) { - LOGGER.warn("Unknown type: {}", globalRef); - return; + final TypeString typeString = definition.getExemplarName(); + final AbstractType exemplarType; + if (this.typeKeeper.hasType(typeString)) { + exemplarType = this.typeKeeper.getType(typeString); + } else { + // Create a new "temporary" type, to be updated later when the + // definition of that type is found. + exemplarType = new MagikType(this.typeKeeper, MagikType.Sort.UNDEFINED, typeString); } // Combine parameter types with method docs. final NewDocParser newDocParser = new NewDocParser(node); - final Map parameterTypes = newDocParser.getParameterTypes(); + final Map parameterTypes = newDocParser.getParameterTypes(); final List parameters = definition.getParameters().stream() .map(parameterDefinition -> { final String name = parameterDefinition.getName(); @@ -318,8 +329,8 @@ private void handleDefinition(final MagikFile magikFile, final MethodDefinition if (!parameterTypes.containsKey(name)) { type = UndefinedType.INSTANCE; } else { - final String parameterType = parameterTypes.get(name); - type = this.typeParser.parseTypeString(parameterType, globalRef.getPakkage()); + final TypeString parameterType = parameterTypes.get(name); + type = this.typeParser.parseTypeString(parameterType); } final Parameter.Modifier modifier = @@ -334,15 +345,32 @@ private void handleDefinition(final MagikFile magikFile, final MethodDefinition MagikIndexer.PARAMETER_MODIFIER_MAPPING.get(assignParamDef.getModifier())) : null; - // Combine iterator types with method docs. - final ExpressionResult loopResult = newDocParser.getLoopTypes().stream() - .map(type -> this.typeParser.parseTypeString(type, globalRef.getPakkage())) - .collect(ExpressionResult.COLLECTOR); + // Get return types from method docs. + final List callResultDocs = newDocParser.getReturnTypes(); + // Ensure we can believe the docs, sort of. + final MethodDefinitionNodeHelper helper = new MethodDefinitionNodeHelper(node); + final boolean returnsAnything = helper.returnsAnything(); + final ExpressionResultString callResult = + !callResultDocs.isEmpty() + || callResultDocs.isEmpty() && !returnsAnything + ? new ExpressionResultString(callResultDocs) + : ExpressionResultString.UNDEFINED; + + // Get iterator types from method docs. + final List loopResultDocs = newDocParser.getLoopTypes(); + // Ensure method docs match actual loopbody, sort of. + final boolean hasLoopbody = helper.hasLoopbody(); + final ExpressionResultString loopResult = + !loopResultDocs.isEmpty() + || loopResultDocs.isEmpty() && !hasLoopbody + ? new ExpressionResultString(loopResultDocs) + : ExpressionResultString.UNDEFINED; - // Combine return types with method docs. - final ExpressionResult callResult = newDocParser.getReturnTypes().stream() - .map(type -> this.typeParser.parseTypeString(type, globalRef.getPakkage())) - .collect(ExpressionResult.COLLECTOR); + // Method doc. + final String methodDoc = MagikCommentExtractor.extractDocComments(node) + .map(Token::getValue) + .map(line -> line.substring(2)) // Strip '##' + .collect(Collectors.joining("\n")); // Create method. final MagikType magikType = (MagikType) exemplarType; @@ -353,34 +381,31 @@ private void handleDefinition(final MagikFile magikFile, final MethodDefinition final Location location = new Location(uri, node); final String methodName = definition.getMethodName(); final Method method = magikType.addMethod( - modifiers, location, methodName, parameters, assignmentParameter, callResult, loopResult); - - // Method doc. - final String methodDoc = MagikCommentExtractor.extractDocComments(node) - .map(Token::getValue) - .map(line -> line.substring(2)) // Strip '##' - .collect(Collectors.joining("\n")); - method.setDoc(methodDoc); + location, modifiers, methodName, parameters, assignmentParameter, methodDoc, callResult, loopResult); // Save used types. final AstNode bodyNode = node.getFirstChild(MagikGrammar.BODY); final GlobalScope globalScope = magikFile.getGlobalScope(); final Scope bodyScope = globalScope.getScopeForNode(bodyNode); + final PackageNodeHelper packageNodeHelper = new PackageNodeHelper(node); + final String currentPakkage = packageNodeHelper.getCurrentPackage(); Objects.requireNonNull(bodyScope); bodyScope.getSelfAndDescendantScopes().stream() .flatMap(scope -> scope.getScopeEntriesInScope().stream()) - .filter(scopeEntry -> scopeEntry.isType(ScopeEntry.Type.GLOBAL) - || scopeEntry.isType(ScopeEntry.Type.DYNAMIC)) - .map(ScopeEntry::getIdentifier) - .map(identifier -> { - AbstractType type = this.typeKeeper.getType(globalRef); + .filter(scopeEntry -> scopeEntry.isType(ScopeEntry.Type.GLOBAL, ScopeEntry.Type.DYNAMIC)) + .map(scopeEntry -> { + final String identifier = scopeEntry.getIdentifier(); + final TypeString ref = TypeString.of(identifier, currentPakkage); + AbstractType type = this.typeKeeper.getType(ref); if (type == UndefinedType.INSTANCE) { return null; } else if (type == SelfType.INSTANCE) { - // TODO: Does this actually happen? - type = this.typeKeeper.getType(globalRef); + type = magikType; } - return type; + final AstNode usageNode = scopeEntry.getNode(); + final Location usageLocation = new Location(uri, usageNode); + final Method.GlobalUsage globalUsage = new Method.GlobalUsage(ref, usageLocation); + return globalUsage; }) .filter(Objects::nonNull) .forEach(method::addUsedType); @@ -389,15 +414,31 @@ private void handleDefinition(final MagikFile magikFile, final MethodDefinition node.getDescendants(MagikGrammar.METHOD_INVOCATION).stream() .map(invocationNode -> { final MethodInvocationNodeHelper invocationHelper = new MethodInvocationNodeHelper(invocationNode); - return invocationHelper.getMethodName(); + final TypeString usedTypeRef = TypeString.of(UndefinedType.SERIALIZED_NAME); + final String usedMethodName = invocationHelper.getMethodName(); + final Location methodUseLocation = new Location(uri, invocationNode); + return new Method.MethodUsage(usedTypeRef, usedMethodName, methodUseLocation); }) .forEach(method::addCalledMethod); - // Save used slot names. + // Save used slots. node.getDescendants(MagikGrammar.SLOT).stream() - .map(slotNode -> slotNode.getFirstChild(MagikGrammar.IDENTIFIER).getTokenValue()) + .map(slotNode -> { + final String slotName = slotNode.getFirstChild(MagikGrammar.IDENTIFIER).getTokenValue(); + final Location slotUseLocation = new Location(uri, slotNode); + return new Method.SlotUsage(slotName, slotUseLocation); + }) .forEach(method::addUsedSlot); + // Save used conditions. + node.getDescendants(MagikGrammar.CONDITION_NAME).stream() + .map(conditionNameNode -> { + final String conditionName = conditionNameNode.getTokenValue(); + final Location conditionUseLocation = new Location(uri, conditionNameNode); + return new Method.ConditionUsage(conditionName, conditionUseLocation); + }) + .forEach(method::addUsedCondition); + final Path path = Paths.get(magikFile.getUri()); this.indexedMethods.get(path).add(method); @@ -406,20 +447,25 @@ private void handleDefinition(final MagikFile magikFile, final MethodDefinition @SuppressWarnings("java:S1172") private void handleDefinition(final MagikFile magikFile, final GlobalDefinition globalDefinition) { + this.ensurePackageExists(globalDefinition); + final String pakkage = globalDefinition.getPackage(); final String identifier = globalDefinition.getName(); - final GlobalReference globalRef = typeParser.getGlobalRefeference(identifier, pakkage); + final TypeString typeStr = TypeString.of(identifier, pakkage); - if (this.typeKeeper.getType(globalRef) != null) { + if (this.typeKeeper.getType(typeStr) != null) { // Don't overwrite any existing types with a AliasType. return; } - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(globalDefinition.getNode()); - final AbstractType aliasedType = typeAnnotation != null - ? this.typeParser.parseTypeString(typeAnnotation, globalDefinition.getPackage()) - : UndefinedType.INSTANCE; - final AbstractType globalType = new AliasType(globalRef, aliasedType); + final AstNode node = globalDefinition.getNode(); + final CommentInstructionReader instructionReader = + new CommentInstructionReader(magikFile, Set.of(TYPE_INSTRUCTION)); + final String typeAnnotation = instructionReader.getInstructionForNode(node, TYPE_INSTRUCTION); + final TypeString aliasedRef = typeAnnotation != null + ? TypeString.of(typeAnnotation, globalDefinition.getPackage()) + : TypeString.UNDEFINED; + final AbstractType globalType = new AliasType(this.typeKeeper, typeStr, aliasedRef); this.typeKeeper.addType(globalType); final Path path = Paths.get(magikFile.getUri()); @@ -428,38 +474,43 @@ private void handleDefinition(final MagikFile magikFile, final GlobalDefinition @SuppressWarnings("java:S1172") private void handleDefinition(final MagikFile magikFile, final BinaryOperatorDefinition binaryOperatorDefinition) { + this.ensurePackageExists(binaryOperatorDefinition); + final BinaryOperator.Operator operator = BinaryOperator.Operator.valueFor(binaryOperatorDefinition.getOperator()); - final String pakkage = binaryOperatorDefinition.getPackage(); - final String leftTypeStr = binaryOperatorDefinition.getLhs(); - final AbstractType leftType = this.typeParser.parseTypeString(leftTypeStr, pakkage); - final String rightTypeStr = binaryOperatorDefinition.getRhs(); - final AbstractType rightType = this.typeParser.parseTypeString(rightTypeStr, pakkage); - if (leftType == UndefinedType.INSTANCE) { - LOGGER.warn("Unknown lhs type: {}", leftType); - return; - } - if (rightType == UndefinedType.INSTANCE) { - LOGGER.warn("Unknown rhs type: {}", rightType); - return; - } - - final AbstractType resultType = UndefinedType.INSTANCE; // TODO: Determine type. - final BinaryOperator binaryOperator = new BinaryOperator(operator, leftType, rightType, resultType); + final TypeString lhsRef = binaryOperatorDefinition.getLhs(); + final TypeString rhsRef = binaryOperatorDefinition.getRhs(); + final TypeString resultRef = TypeString.UNDEFINED; // TODO: Determine type. + final BinaryOperator binaryOperator = new BinaryOperator(operator, lhsRef, rhsRef, resultRef); this.typeKeeper.addBinaryOperator(binaryOperator); final Path path = Paths.get(magikFile.getUri()); this.indexedBinaryOperators.get(path).add(binaryOperator); } + private void handleDefinition(final MagikFile magikFile, final ConditionDefinition definition) { + final AstNode node = definition.getNode(); + final URI uri = magikFile.getUri(); + final Location location = new Location(uri, node); + final String name = definition.getName(); + final String parent = definition.getParent(); + final List dataNameList = definition.getDataNames(); + final String doc = null; + final Condition condition = new Condition(location, name, parent, dataNameList, doc); + this.typeKeeper.addCondition(condition); + + final Path path = Paths.get(magikFile.getUri()); + this.indexedConditions.get(path).add(condition); + } + private void fillType( final MagikType magikType, final MagikFile magikFile, final AstNode node, final String packageName, - final Map slots, - final List parents, - final @Nullable AbstractType defaultParentType) { + final Map slots, + final List parents, + final @Nullable TypeString defaultParentRef) { // Set location. final URI uri = magikFile.getUri(); final Location location = new Location(uri, node); @@ -470,36 +521,25 @@ private void fillType( slots.entrySet() .forEach(entry -> { final String slotName = entry.getKey(); - final String slotTypeName = entry.getValue(); - final AbstractType slotType = this.typeParser.parseTypeString(slotTypeName, packageName); + final TypeString slotTypeString = entry.getValue(); + final AbstractType slotType = this.typeParser.parseTypeString(slotTypeString); final Slot slot = magikType.addSlot(null, slotName); slot.setType(slotType); }); // Parents. magikType.clearParents(); - final PackageNodeHelper packageHelper = new PackageNodeHelper(node); - final String pakkageName = packageHelper.getCurrentPackage(); - parents.stream() - .forEach(parent -> { - final GlobalReference parentGlobalRef = parent.indexOf(':') != -1 - ? GlobalReference.of(parent) - : GlobalReference.of(pakkageName, parent); - final AbstractType parentType = this.typeKeeper.getType(parentGlobalRef); - if (parentType == UndefinedType.INSTANCE) { - LOGGER.warn("Parent type not found: {} from package: {}", parent, pakkageName); - } else { - magikType.addParent(parentType); - } - }); + parents.forEach(magikType::addParent); - // Default parent types () + // Default parent types. boolean parentNonIntrinsicType = magikType.getParents().stream() - .anyMatch(parentType -> parentType instanceof SlottedType - || parentType instanceof IndexedType); - if (defaultParentType != null + .filter(MagikType.class::isInstance) + .map(MagikType.class::cast) + .anyMatch(parentType -> parentType.getSort() == Sort.SLOTTED + || parentType.getSort() == Sort.INDEXED); + if (defaultParentRef != null && !parentNonIntrinsicType) { - magikType.addParent(defaultParentType); + magikType.addParent(defaultParentRef); } // Type doc. @@ -512,36 +552,43 @@ private void fillType( private void handleMethodDefinitionOther(final MagikFile magikFile, final MethodDefinition definition) { // Slot accessors, shared variables, shared constants. - final GlobalReference globalRef = definition.getTypeGlobalReference(); - final AbstractType exemplarType = this.typeKeeper.getType(globalRef); - if (exemplarType == UndefinedType.INSTANCE) { - LOGGER.warn("Unknown type: {}", globalRef); - return; + final TypeString typeString = definition.getExemplarName(); + final AbstractType exemplarType; + if (this.typeKeeper.hasType(typeString)) { + exemplarType = this.typeKeeper.getType(typeString); + } else { + // Create a new "temporary" type, to be updated later when the + // definition of that type is found. + exemplarType = new MagikType(this.typeKeeper, MagikType.Sort.UNDEFINED, typeString); } // Get method return types from docs, if any. final AstNode node = definition.getNode(); final NewDocParser methodDocParser = new NewDocParser(node); - final List methodReturnTypes = methodDocParser.getReturnTypes().stream() - .map(typeStr -> this.typeParser.parseTypeString(typeStr, globalRef.getPakkage())) - .collect(Collectors.toList()); + final List methodReturnTypes = methodDocParser.getReturnTypes(); // Get slot type from docs, if any. final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); - final AbstractType slotType; + final TypeString slotType; if (statementNode != null) { final NewDocParser exemplarDocParser = new NewDocParser(statementNode); final String slotName = definition.getMethodName(); - final String slotTypeStr = exemplarDocParser.getSlotTypes().get(slotName); - slotType = this.typeParser.parseTypeString(slotTypeStr, globalRef.getPakkage()); + final Map slotTypes = exemplarDocParser.getSlotTypes(); + slotType = slotTypes.getOrDefault(slotName, TypeString.UNDEFINED); } else { - slotType = UndefinedType.INSTANCE; + slotType = TypeString.UNDEFINED; } // Determine the result to use. - final ExpressionResult result = !methodReturnTypes.isEmpty() - ? new ExpressionResult(methodReturnTypes) - : new ExpressionResult(slotType); + final ExpressionResultString result = !methodReturnTypes.isEmpty() + ? new ExpressionResultString(methodReturnTypes) + : new ExpressionResultString(slotType); + + // TODO: Will this work? + // TODO: Ensure we don't pick up class comment of def_slotted_exemplar. + final String methodDoc = MagikCommentExtractor.extractDocComments(node) + .map(Token::getValue) + .collect(Collectors.joining("\n")); // Create method. final MagikType magikType = (MagikType) exemplarType; @@ -553,15 +600,9 @@ private void handleMethodDefinitionOther(final MagikFile magikFile, final Method final String methodName = definition.getMethodName(); final List parameters = Collections.emptyList(); final Parameter assignmentParameter = null; + final ExpressionResultString loopbodyResult = new ExpressionResultString(); final Method method = magikType.addMethod( - modifiers, location, methodName, parameters, assignmentParameter, result); - - // TODO: Will this work? - // TODO: Ensure we don't pick up class comment of def_slotted_exemplar. - final String methodDoc = MagikCommentExtractor.extractDocComments(node) - .map(Token::getValue) - .collect(Collectors.joining("\n")); - method.setDoc(methodDoc); + location, modifiers, methodName, parameters, assignmentParameter, methodDoc, result, loopbodyResult); final Path path = Paths.get(magikFile.getUri()); this.indexedMethods.get(path).add(method); @@ -569,6 +610,31 @@ private void handleMethodDefinitionOther(final MagikFile magikFile, final Method LOGGER.debug("Indexed method: {}", method); } + private void ensurePackageExists(final Definition definition) { + final String pakkageName = definition.getPackage(); + if (!this.typeKeeper.hasPackage(pakkageName)) { + new Package(this.typeKeeper, pakkageName); + } + } + + private MagikType findType(final TypeString typeString, final MagikType.Sort sort) { + final AbstractType type = this.typeKeeper.getType(typeString); + if (type == UndefinedType.INSTANCE) { + return new MagikType(this.typeKeeper, sort, typeString); + } else if (type instanceof MagikType) { + final MagikType magikType = (MagikType) type; + if (magikType.getSort() != MagikType.Sort.UNDEFINED + && magikType.getSort() != sort) { + throw new IllegalStateException(); + } + + magikType.setSort(sort); + return magikType; + } + + throw new IllegalStateException(); + } + /** * Read definitions from path. * @param path Path to magik file. @@ -579,6 +645,7 @@ private void readDefinitions(final Path path) { this.indexedBinaryOperators.put(path, new HashSet<>()); this.indexedPackages.put(path, new HashSet<>()); this.indexedTypes.put(path, new HashSet<>()); + this.indexedConditions.put(path, new HashSet<>()); try { final MagikFile magikFile = new MagikFile(path); @@ -615,6 +682,10 @@ private void scrubDefinitions(final Path path) { .filter(pakkage -> pakkage.getTypes().isEmpty()) .forEach(pakkage -> this.typeKeeper.removePackage(pakkage)); this.indexedPackages.remove(path); + + this.indexedConditions.getOrDefault(path, Collections.emptySet()) + .forEach(condition -> this.typeKeeper.removeCondition(condition)); + this.indexedConditions.remove(path); } } diff --git a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/package-info.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/package-info.java similarity index 54% rename from magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/package-info.java rename to magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/package-info.java index a98a08ee..8c3bd1f2 100644 --- a/magik-language-server/src/main/java/nl/ramsolutions/sw/magik/languageserver/indexer/package-info.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/indexer/package-info.java @@ -2,4 +2,4 @@ * Defaults. */ @javax.annotation.ParametersAreNonnullByDefault -package nl.ramsolutions.sw.magik.languageserver.indexer; +package nl.ramsolutions.sw.magik.analysis.typing.indexer; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AbstractType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AbstractType.java index f0f7f441..43f43944 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AbstractType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AbstractType.java @@ -4,7 +4,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.CheckForNull; -import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.Location; /** @@ -15,11 +14,17 @@ public abstract class AbstractType { private Location location; private String doc; + /** + * Get the global reference to this type. + * @return Global reference to this type. + */ + public abstract TypeString getTypeString(); + /** * Get the full name of this type, including package. * @return Name of this type, including package. */ - public abstract String getFullName(); + public abstract String getFullName(); // TODO: Remove this, get via TypeString. /** * Get the name of this type. @@ -40,7 +45,7 @@ public abstract class AbstractType { public abstract Collection getMethods(); /** - * Get all {{Method}}s for this type responds to by name. + * Get all {@link Method}s for this type responds to by name. * @param methodName Name of method(s). * @return Collection of methods for this type/these types with this name. */ @@ -90,13 +95,13 @@ public Collection getAncestors() { } /** - * Test if this type is kind of {{otherType}}. + * Test if this type is kind of {@code otherType}. * *

- * Note that this does not work when testing against a {{@code CombinedType}}. + * Note that this does not work when testing against a {@link CombinedType}. *

* @param otherType Type to test against. - * @return True if kind of {{otherType}}, false otherwise. + * @return True if kind of {@code otherType}, false otherwise. */ public boolean isKindOf(final AbstractType otherType) { return this.equals(otherType) || this.getAncestors().contains(otherType); @@ -136,8 +141,8 @@ public Slot getSlot(final String name) { } /** - * Get {{Location}} for exemplar. - * @return {{Location}} where exemplar is defined. + * Get {@link Location} for exemplar. + * @return {@link Location} where exemplar is defined. */ @CheckForNull public Location getLocation() { @@ -145,10 +150,10 @@ public Location getLocation() { } /** - * Set {{Location}} for exemplar. - * @param location Set {{Location}} where exemplar is defined. + * Set {@link Location} for exemplar. + * @param location Set {@link Location} where exemplar is defined. */ - public void setLocation(final @Nullable Location location) { + public void setLocation(final Location location) { this.location = location; } @@ -156,7 +161,7 @@ public void setLocation(final @Nullable Location location) { * Set method documentation. * @param comment Method doc. */ - public void setDoc(final @Nullable String comment) { + public void setDoc(final String comment) { this.doc = comment; } @@ -169,4 +174,18 @@ public String getDoc() { return this.doc; } + /** + * Substitute {@code from} type for {@code to} type, if {@code this} is equal to {@code from}. + * @param from From type. + * @param to To type. + * @return To type or self. + */ + public AbstractType substituteType(final AbstractType from, final AbstractType to) { + if (from.equals(this)) { + return to; + } + + return this; + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AliasType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AliasType.java index ac8fbcf6..f17e0f33 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AliasType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/AliasType.java @@ -1,67 +1,111 @@ package nl.ramsolutions.sw.magik.analysis.typing.types; import java.util.Collection; +import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; /** * Alias type, refering to another type. */ public class AliasType extends AbstractType { - private final GlobalReference globalReference; + private final ITypeKeeper typeKeeper; + private final TypeString typeString; + private final TypeString aliasedTypeString; private final AbstractType aliasedType; /** * Constructor. - * @param globalReference Reference to type. + * @param typeString Reference to type. + * @param aliasedTypeString Aliased type. + */ + public AliasType( + final ITypeKeeper typeKeeper, + final TypeString typeString, + final TypeString aliasedTypeString) { + this.typeKeeper = typeKeeper; + this.typeString = typeString; + this.aliasedTypeString = aliasedTypeString; + this.aliasedType = null; + } + + /** + * Constructor. + * @param typeKeeper Type keeper. + * @param typeString Reference to type. * @param aliasedType Aliased type. */ - public AliasType(final GlobalReference globalReference, final AbstractType aliasedType) { - this.globalReference = globalReference; + public AliasType( + final ITypeKeeper typeKeeper, + final TypeString typeString, + final AbstractType aliasedType) { + this.typeKeeper = typeKeeper; + this.typeString = typeString; + this.aliasedTypeString = null; this.aliasedType = aliasedType; + + this.typeKeeper.addType(this); } + @Override + public TypeString getTypeString() { + return this.typeString; + } + + /** + * Get aliased type. + * @return Aliased type. + */ public AbstractType getAliasedType() { + if (this.aliasedTypeString != null) { + return this.typeKeeper.getType(this.aliasedTypeString); + } + return this.aliasedType; } @Override public String getFullName() { - return this.globalReference.getFullName(); + return this.typeString.getFullString(); } @Override public String getName() { - return this.globalReference.getIdentifier(); + return this.typeString.getString(); } @Override public Collection getMethods() { - return this.aliasedType.getMethods(); + return this.getAliasedType().getMethods(); } @Override public Collection getLocalMethods() { - return this.aliasedType.getLocalMethods(); + return this.getAliasedType().getLocalMethods(); } @Override public Collection getParents() { - return this.aliasedType.getParents(); + return this.getAliasedType().getParents(); } @Override public Collection getSuperMethods(String methodName) { - return this.aliasedType.getSuperMethods(methodName); + return this.getAliasedType().getSuperMethods(methodName); } @Override public Collection getSuperMethods(String methodName, String superName) { - return this.aliasedType.getSuperMethods(methodName, superName); + return this.getAliasedType().getSuperMethods(methodName, superName); } @Override public Collection getSlots() { - return this.aliasedType.getSlots(); + return this.getAliasedType().getSlots(); + } + + @Override + public String getDoc() { + return this.getAliasedType().getDoc(); } @Override diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/CombinedType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/CombinedType.java index 04499c4d..4f5d0821 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/CombinedType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/CombinedType.java @@ -2,11 +2,10 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import java.util.stream.Stream; import nl.ramsolutions.sw.magik.analysis.Location; /** @@ -18,10 +17,18 @@ public class CombinedType extends AbstractType { private final Set types; + /** + * Constructor. + * @param types Combined types. + */ public CombinedType(final AbstractType... types) { this(Set.of(types)); } + /** + * Constructor. + * @param types Combined types. + */ public CombinedType(final Collection types) { this.types = Collections.unmodifiableSet(Set.copyOf(types)); } @@ -32,7 +39,7 @@ public Collection getTypes() { @Override public String getFullName() { - return this.types.stream() + return this.getTypes().stream() .map(AbstractType::getFullName) .sorted() .collect(Collectors.joining(VALUE_COMBINATOR)); @@ -40,116 +47,79 @@ public String getFullName() { @Override public String getName() { - return this.types.stream() + return this.getTypes().stream() .map(AbstractType::getName) .sorted() .collect(Collectors.joining(VALUE_COMBINATOR)); } /** - * Combine two {{MagikType}}s. - * @param type1 First {{MagikType}} to combine. - * @param type2 Second {{MagikType}} to combine. - * @return {{CombinedMagikType}} representing both types. + * Combine {@link AbstractType}s. + * @param types Types {@link AbstractType} to combine. + * @return {@link CombinedType} representing both types, or {@link AbstractType} if singular. */ - public static AbstractType combine(final AbstractType type1, final @Nullable AbstractType type2) { - if (type2 == null) { - return type1; + public static AbstractType combine(final AbstractType... types) { + if (types.length == 0) { + return null; } - // Cases: a. 1 = SingleMagikType, 2 = SingleMagikType - // b. 1 = SingleMagikType, 2 = CombinedMagikType - // c. 1 = CombinedMagikType, 2 = SingleMagikType - // d. 1 = CombinedMagikType, 2 = CombinedMagikType - final Set types = new HashSet<>(); - - // 1. - if (type1 instanceof CombinedType) { - final CombinedType combinedType = (CombinedType) type1; - types.addAll(combinedType.getTypes()); - } else { - types.add(type1); - } + final Set combinedTypes = Stream.of(types) + .flatMap(type -> { + if (type instanceof CombinedType) { + final CombinedType combinedType = (CombinedType) type; + return combinedType.getTypes().stream(); + } - // 2. - if (type2 instanceof CombinedType) { - final CombinedType combinedType = (CombinedType) type2; - types.addAll(combinedType.getTypes()); - } else { - types.add(type2); - } - - if (types.size() == 1) { - return types.stream() - .findAny() + return Stream.of(type); + }) + .collect(Collectors.toUnmodifiableSet()); + if (combinedTypes.size() == 1) { + return combinedTypes.stream() + .findFirst() .orElseThrow(); } - return new CombinedType(types); + return new CombinedType(combinedTypes); } @Override public Collection getSlots() { - return this.types.stream() + return this.getTypes().stream() .flatMap(type -> type.getSlots().stream()) .collect(Collectors.toUnmodifiableSet()); } @Override public Collection getMethods() { - return this.types.stream() + return this.getTypes().stream() .flatMap(type -> type.getMethods().stream()) .collect(Collectors.toUnmodifiableSet()); } @Override public Collection getLocalMethods() { - return this.types.stream() + return this.getTypes().stream() .flatMap(type -> type.getLocalMethods().stream()) .collect(Collectors.toUnmodifiableSet()); } - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final CombinedType other = (CombinedType) obj; - return Objects.equals(this.types.size(), other.types.size()) - && Objects.equals(this.getFullName(), other.getFullName()); - } - - @Override - public int hashCode() { - return Objects.hash(this.types.toArray()); - } - @Override public Collection getParents() { - return this.types.stream() + return this.getTypes().stream() .flatMap(type -> type.getParents().stream()) .collect(Collectors.toSet()); } @Override public Collection getSuperMethods(final String methodName) { - return this.types.stream() + return this.getTypes().stream() .flatMap(type -> type.getSuperMethods(methodName).stream()) .collect(Collectors.toSet()); } @Override public Collection getSuperMethods(final String methodName, final String superName) { - return this.types.stream() + return this.getTypes().stream() .filter(type -> type.getFullName().equals(superName)) .flatMap(type -> type.getMethods(methodName).stream()) .collect(Collectors.toSet()); @@ -175,4 +145,54 @@ public void setDoc(final String comment) { throw new IllegalStateException(); } + @Override + public String toString() { + return String.format( + "%s@%s(%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getFullName()); + } + + @Override + public int hashCode() { + return Objects.hash(this.types.toArray()); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final CombinedType other = (CombinedType) obj; + return Objects.equals(this.getFullName(), other.getFullName()); + } + + @Override + public TypeString getTypeString() { + return this.types.stream() + .map(AbstractType::getTypeString) + .collect(TypeString.COLLECTOR); + } + + @Override + public AbstractType substituteType(final AbstractType from, final AbstractType to) { + final Set substitutedTypes = this.types.stream() + .map(type -> type.substituteType(from, to)) + .collect(Collectors.toUnmodifiableSet()); + if (substitutedTypes.equals(this.types)) { + return this; + } + + return new CombinedType(substitutedTypes); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Condition.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Condition.java new file mode 100644 index 00000000..1dc3266a --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Condition.java @@ -0,0 +1,78 @@ +package nl.ramsolutions.sw.magik.analysis.typing.types; + +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import nl.ramsolutions.sw.magik.analysis.Location; + +/** + * Condition. + */ +public class Condition { + + private final Location location; + private final String name; + private final List dataNameList; + private final String parent; + private final String doc; + + /** + * Constructor. + * @param location Location of condition. + * @param name Name of condition. + * @param parent Parent of the condition. + * @param dataNameList Data names. + * @param doc Doc. + */ + public Condition( + final @Nullable Location location, + final String name, + final String parent, + final List dataNameList, + final @Nullable String doc) { + this.location = location; + this.name = name; + this.dataNameList = dataNameList; + this.parent = parent; + this.doc = doc; + } + + @CheckForNull + public Location getLocation() { + return this.location; + } + + /** + * Get name of the condition. + * @return Name. + */ + public String getName() { + return this.name; + } + + /** + * Get data name list. + * @return Data name list. + */ + public List getDataNameList() { + return this.dataNameList; + } + + /** + * Get parent. + * @return Parent. + */ + public String getParent() { + return this.parent; + } + + /** + * Get doc. + * @return Doc. + */ + @CheckForNull + public String getDoc() { + return this.doc; + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResult.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResult.java index f576a8cb..904523d6 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResult.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResult.java @@ -6,12 +6,14 @@ import java.util.Objects; import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; /** - * Container to hold resulting {{MagikType}}s for an EXPRESSION node. - * Acts like an unmodifiable {{List}}. + * Container to hold resulting {@link AbstractType}s for an EXPRESSION node. */ +@Immutable public class ExpressionResult { /** @@ -19,23 +21,21 @@ public class ExpressionResult { */ public static final Collector COLLECTOR = Collector.of( ArrayList::new, - // () -> new ArrayList(), - // ArrayList::addAll, (list, value) -> list.add(value), - (left, right) -> { - left.addAll(right); - return left; + (list, values) -> { + list.addAll(values); + return list; }, ExpressionResult::new); /** - * Instance of {{ExpressionResult}} to be used in all cases of undefined expression results. + * Instance of {@link ExpressionResult} to be used in all cases of undefined expression results. */ public static final ExpressionResult UNDEFINED = new ExpressionResult( Collections.nCopies(1024, UndefinedType.INSTANCE)); // 1024 is max for _scatter /** - * Serialized name of {{ExpressionResult.UNDEFINED}}. + * Serialized name of {@code ExpressionResult.UNDEFINED}. */ public static final String UNDEFINED_SERIALIZED_NAME = "__UNDEFINED_RESULT__"; @@ -59,23 +59,16 @@ public ExpressionResult(final AbstractType... types) { /** * List constructor. - * @param types Types this {{ExpressionResult}} represents. + * @param types Types this {@link ExpressionResult} represents. */ public ExpressionResult(final List types) { this.types = Collections.unmodifiableList(types); } - /** - * Copy constructor. - */ - public ExpressionResult(final ExpressionResult existing) { - this(existing.getTypes()); - } - /** * Combine constructor. - * @param result1 First {{ExpressionResult}}. - * @param result2 Second {{ExpressionResult}}. + * @param result1 First {@link ExpressionResult}. + * @param result2 Second {@link ExpressionResult}. * @param unsetType Unset type, used for filler. */ public ExpressionResult( @@ -94,6 +87,18 @@ public ExpressionResult( this.types = Collections.unmodifiableList(combinedTypes); } + /** + * Test if is empty. + * @return True if empty, false otherwise. + */ + public boolean isEmpty() { + return this.types.isEmpty(); + } + + /** + * Get types. + * @return + */ public List getTypes() { return Collections.unmodifiableList(this.types); } @@ -117,56 +122,18 @@ public int size() { } /** - * Substitue {{from}} by {{to}} in a copy of self. - * Used to replace the {{SelfType}}. + * Substitue {@code from} by {@code to} in a copy of self. + * Used to replace the {@link SelfType}. * @param from To substitute. * @param to To substitute with. - * @return New {{ExpressionResult}}. + * @return New {@link ExpressionResult}. */ public ExpressionResult substituteType(final AbstractType from, final AbstractType to) { return this.types.stream() - .map(type -> { - if (from.equals(type)) { - return to; - } - - return type; - }) + .map(type -> type.substituteType(from, to)) .collect(ExpressionResult.COLLECTOR); } - @Override - @SuppressWarnings("checkstyle:NestedIfDepth") - public String toString() { - return String.format( - "%s@%s(%s)", - this.getClass().getName(), Integer.toHexString(this.hashCode()), - this.getTypeNames(",")); - } - - @Override - public int hashCode() { - return Objects.hash(this.types.toArray()); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final ExpressionResult other = (ExpressionResult) obj; - return Objects.equals(this.types, other.types); - } - /** * Get type names of all items of the result. * @return Type names of items of the result. @@ -211,4 +178,44 @@ public String getTypeNames(final String separator) { return builder.toString(); } + /** + * Get stream of types contained by this {@link ExpressionResult}. + * @return Stream of types. + */ + public Stream stream() { + return this.types.stream(); + } + + @Override + @SuppressWarnings("checkstyle:NestedIfDepth") + public String toString() { + return String.format( + "%s@%s(%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getTypeNames(",")); + } + + @Override + public int hashCode() { + return Objects.hash(this.types.toArray()); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final ExpressionResult other = (ExpressionResult) obj; + return Objects.equals(this.types, other.types); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultString.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultString.java new file mode 100644 index 00000000..88df9ae8 --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultString.java @@ -0,0 +1,202 @@ +package nl.ramsolutions.sw.magik.analysis.typing.types; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import nl.ramsolutions.sw.magik.api.NewDocGrammar; + +/** + * Container to hold resulting unresolved {@link AbstractType}s. + */ +@Immutable +public class ExpressionResultString { + + /** + * Stream collector. + */ + public static final Collector COLLECTOR = Collector.of( + ArrayList::new, + (list, value) -> list.add(value), + (list, values) -> { + list.addAll(values); + return list; + }, + ExpressionResultString::new); + + /** + * Instance of {@link ExpressionResult} to be used in all cases of undefined expression results. + */ + public static final ExpressionResultString UNDEFINED = new ExpressionResultString( + Collections.nCopies(1024, TypeString.UNDEFINED)); // 1024 is max for _scatter + + /** + * Serialized name of {@code ExpressionResult.UNDEFINED}. + */ + public static final String UNDEFINED_SERIALIZED_NAME = "__UNDEFINED_RESULT__"; + + private static final String TYPE_SEPARATOR = NewDocGrammar.Punctuator.TYPE_SEPARATOR.getValue(); + private static final String TYPE_SEPARATOR_RE = Pattern.quote(TYPE_SEPARATOR); + + private static final int MAX_ITEMS = 1024; + + private final List types; + + /** + * Empty result constructor. + */ + public ExpressionResultString() { + this(Collections.emptyList()); + } + + /** + * Array/utility constructor. + */ + public ExpressionResultString(final TypeString... types) { + this(List.of(types)); + } + + /** + * List constructor. + * @param types Types this {@link ExpressionResult} represents. + */ + public ExpressionResultString(final List types) { + this.types = Collections.unmodifiableList(types); + } + + /** + * Get types. + * @return Type strings. + */ + public List getTypes() { + return Collections.unmodifiableList(this.types); + } + + /** + * Get type at index. + * @param index Index of type. + * @return Type at index. + */ + public TypeString get(final int index, final @Nullable TypeString unsetType) { + if (this.types.isEmpty() + || index >= this.types.size()) { + return unsetType; + } + + return this.types.get(index); + } + + public int size() { + return this.types.size(); + } + + /** + * Get type names of all items of the result. + * @return Type names of items of the result. + */ + public String getTypeNames(final String separator) { + if (this == ExpressionResultString.UNDEFINED) { + return "UNDEFINED..."; + } + + // Determine first index of trailing homogenous sequence. + int firstRepeatingIndex = MAX_ITEMS; + TypeString lastType = null; + if (this.types.size() == MAX_ITEMS) { + lastType = this.get(firstRepeatingIndex - 1, null); + for (int i = this.types.size() - 1; i > -1; --i) { + TypeString type = this.types.get(i); + if (type.equals(lastType)) { + firstRepeatingIndex = i; + } else { + break; + } + } + } + + final StringBuilder builder = new StringBuilder(); + final String typesStr = this.types.stream() + .limit(firstRepeatingIndex) + .map(TypeString::getString) + .collect(Collectors.joining(separator)); + builder.append(typesStr); + + // If a trailing sequence was found, append one with three dots. + if (lastType != null) { + if (!typesStr.isEmpty()) { + builder.append(separator); + } + + builder + .append(lastType) + .append("..."); + } + return builder.toString(); + } + + /** + * Get stream of types contained by this {@link ExpressionResult}. + * @return Stream of types. + */ + public Stream stream() { + return this.types.stream(); + } + + /** + * Get {@link ExpressionResultString} from string and package. + * @param expressionResultString String to parse. + * @param currentPackage Current package. + * @return Parsed expression result string. + */ + public static ExpressionResultString of( + final @Nullable String expressionResultString, final String currentPackage) { + if (expressionResultString == null + || expressionResultString.isBlank() + || expressionResultString.equalsIgnoreCase(ExpressionResult.UNDEFINED_SERIALIZED_NAME)) { + return ExpressionResultString.UNDEFINED; + } + + return Stream.of(expressionResultString.split(TYPE_SEPARATOR_RE)) + .map(str -> TypeString.of(str, currentPackage)) + .collect(ExpressionResultString.COLLECTOR); + } + + @Override + @SuppressWarnings("checkstyle:NestedIfDepth") + public String toString() { + return String.format( + "%s@%s(%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getTypeNames(",")); + } + + @Override + public int hashCode() { + return Objects.hash(this.types.toArray()); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final ExpressionResultString other = (ExpressionResultString) obj; + return Objects.equals(this.types, other.types); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/GlobalReference.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/GlobalReference.java deleted file mode 100644 index f382851d..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/GlobalReference.java +++ /dev/null @@ -1,111 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing.types; - -import java.util.Objects; - -/** - * Global reference, containing package name and identifier. - * I.e., {{@code sw:rope}. - */ -public final class GlobalReference { - - private final String pakkage; - private final String identifier; - - /** - * Constructor. - * @param reference Full identifier, i.e., "sw:rope". - */ - public GlobalReference(final String reference) { - final String[] parts = reference.split(":"); - if (parts.length != 2) { - throw new IllegalStateException("Malformed reference: " + reference); - } - if (parts[0].indexOf(':') != -1 - || parts[1].indexOf(':') != -1) { - throw new IllegalStateException("Package or identifier with package in reference: " + reference); - } - - this.pakkage = parts[0]; - this.identifier = parts[1]; - } - - /** - * Constructor. - * @param pakkage Package name, i.e., "sw". - * @param identifier Identifier, i.e., "rope". - */ - public GlobalReference(final String pakkage, final String identifier) { - if (pakkage.indexOf(':') != -1 - || identifier.indexOf(':') != -1) { - throw new IllegalStateException( - "Malformed package or identifier, pakkage: " + pakkage + ", identifier: " + identifier); - } - - this.pakkage = pakkage; - this.identifier = identifier; - } - - /** - * Static constructor, for readability. - * @param reference Full identifier, i.e., "sw:rope". - * @return new GlobalReference. - */ - public static GlobalReference of(final String reference) { - return new GlobalReference(reference); - } - - /** - * Static constructor, for readability. - * @param pakkage Package name, i.e., "sw". - * @param identifier Identifier, i.e., "rope". - * @return new GlobalReference. - */ - public static GlobalReference of(final String pakkage, final String identifier) { - return new GlobalReference(pakkage, identifier); - } - - public String getPakkage() { - return this.pakkage; - } - - public String getIdentifier() { - return this.identifier; - } - - public String getFullName() { - return this.pakkage + ":" + this.identifier; - } - - @Override - public int hashCode() { - return Objects.hash(this.pakkage, this.identifier); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final GlobalReference other = (GlobalReference) obj; - return Objects.equals(this.pakkage, other.pakkage) - && Objects.equals(this.identifier, other.identifier); - } - - @Override - public String toString() { - return String.format( - "%s@%s(%s:%s)", - this.getClass().getName(), Integer.toHexString(this.hashCode()), - this.getPakkage(), this.getIdentifier()); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IndexedType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IndexedType.java deleted file mode 100644 index 12b382ea..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IndexedType.java +++ /dev/null @@ -1,37 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing.types; - -import java.util.Objects; - -/** - * Indexed magik type. - */ -public class IndexedType extends MagikType { - - public IndexedType(final GlobalReference globalReference) { - super(globalReference); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final IndexedType other = (IndexedType) obj; - return Objects.equals(this.getFullName(), other.getFullName()); - } - - @Override - public int hashCode() { - return Objects.hash(this.getFullName()); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IntrinsicType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IntrinsicType.java deleted file mode 100644 index 510a28cc..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/IntrinsicType.java +++ /dev/null @@ -1,37 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing.types; - -import java.util.Objects; - -/** - * Intrinsic magik type. - */ -public class IntrinsicType extends MagikType { - - public IntrinsicType(final GlobalReference globalReference) { - super(globalReference); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final IntrinsicType other = (IntrinsicType) obj; - return Objects.equals(this.getFullName(), other.getFullName()); - } - - @Override - public int hashCode() { - return Objects.hash(this.getFullName()); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/MagikType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/MagikType.java index ee854d91..e5c7c845 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/MagikType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/MagikType.java @@ -1,6 +1,5 @@ package nl.ramsolutions.sw.magik.analysis.typing.types; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; @@ -8,32 +7,90 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.Location; +import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; /** * Magik type: slotted exemplar, indexed exemplar, enumeration, or mixin. */ -public abstract class MagikType extends AbstractType { +public class MagikType extends AbstractType { - private final GlobalReference globalReference; - private final Set methods; - private final List parents; - private final Map slots; + /** + * Sort of MagikType. + */ + public enum Sort { + + /** + * Type has not been seen yet, but, e.g., referred to by a method definition. + */ + UNDEFINED, + + /** + * {@code object} type. + */ + OBJECT, + + /** + * Slotted exemplar type. + */ + SLOTTED, + + /** + * Indexed exemplar type. + */ + INDEXED, + + /** + * Intrinsic type. + */ + INTRINSIC; + + } + + private final TypeString typeString; + private final Set methods = ConcurrentHashMap.newKeySet(); + private final Set parents = ConcurrentHashMap.newKeySet(); + private final Map slots = new ConcurrentHashMap<>(); + private Sort sort; + private ITypeKeeper typeKeeper; /** * Constructor. - * @param globalReference Global reference. + * @param typeKeeper TypeKeeper. + * @param magikTypeType Sort. + * @param typeString Global reference. */ - protected MagikType(final GlobalReference globalReference) { - this.globalReference = globalReference; + public MagikType( + final ITypeKeeper typeKeeper, + final Sort magikTypeType, + final TypeString typeString) { + this.typeKeeper = typeKeeper; + this.sort = magikTypeType; + this.typeString = typeString; + + // Add self to TypeKeeper. + this.typeKeeper.addType(this); + } - this.methods = new HashSet<>(); - this.parents = new ArrayList<>(); - this.slots = new HashMap<>(); + /** + * Get the magik type. + * @return + */ + public Sort getSort() { + return this.sort; + } + + /** + * Set the magik type. + */ + public void setSort(final Sort sort) { + this.sort = sort; } /** @@ -45,34 +102,33 @@ public void clearParents() { /** * Add a parent type. - * @param parentType Type to inherit. + * @param parentTypeString Reference to parent type. */ @SuppressWarnings("java:S2583") - public void addParent(final AbstractType parentType) { - if (parentType == SelfType.INSTANCE - || parentType == UndefinedType.INSTANCE) { - final String message = "Trying to inherit invalid type: " + parentType; - throw new IllegalArgumentException(message); - } else if (parentType == this) { - final String message = "Type: " + parentType + " cannot be inherited by ourselves"; - throw new IllegalArgumentException(message); - } - this.parents.add(parentType); + public void addParent(final TypeString parentTypeString) { + this.parents.add(parentTypeString); } @Override public Collection getParents() { - return Collections.unmodifiableList(this.parents); + return this.parents.stream() + .map(parentRef -> this.typeKeeper.getType(parentRef)) + .collect(Collectors.toList()); + } + + @Override + public TypeString getTypeString() { + return this.typeString; } @Override public String getFullName() { - return this.globalReference.getFullName(); + return this.typeString.getFullString(); } @Override public String getName() { - return this.globalReference.getIdentifier(); + return this.typeString.getString(); } /** @@ -124,6 +180,10 @@ public Collection getSlots() { return allSlots; } + public Collection getLocalSlots() { + return Collections.unmodifiableCollection(this.slots.values()); + } + /** * Clear current slots, for this type. */ @@ -131,47 +191,36 @@ public void clearSlots() { this.slots.clear(); } - /** - * Add the resulting types of a method. - * @param methodLocation Location of method. - * @param methodName Name of method. - * @param parameters Parameters of method. - * @param assignmentParameter Assignment parameter for method. - * @param callResult Results the method returns. - */ - @SuppressWarnings("java:S1319") - public Method addMethod( - final EnumSet modifiers, - final @Nullable Location methodLocation, - final String methodName, - final List parameters, - final @Nullable Parameter assignmentParameter, - final ExpressionResult callResult) { - final ExpressionResult loopbodyResult = new ExpressionResult(); - return this.addMethod( - modifiers, methodLocation, methodName, parameters, assignmentParameter, callResult, loopbodyResult); - } - /** * Add the resulting types of a method and loopbody, overwrites existing methods. - * @param methodLocation Location of method. + * @param location Location of method. * @param methodName Name of method. * @param parameters Parameters for method. * @param assignmentParameter Assignment parameter for method. - * @param callResult {{MagikType}}s the method returns. - * @param loopbodyResult {{MagikType}}s the method iterates. + * @param methodDoc Method doc. + * @param callResult {@link MagikType}s the method returns. + * @param loopbodyResult {@link MagikType}s the method iterates. */ - @SuppressWarnings("java:S1319") + @SuppressWarnings({"java:S1319", "checkstyle:ParameterNumber"}) public Method addMethod( + final @Nullable Location location, final EnumSet modifiers, - final @Nullable Location methodLocation, final String methodName, final List parameters, final @Nullable Parameter assignmentParameter, - final ExpressionResult callResult, - final ExpressionResult loopbodyResult) { + final @Nullable String methodDoc, + final ExpressionResultString callResult, + final ExpressionResultString loopbodyResult) { final Method method = new Method( - modifiers, methodLocation, this, methodName, parameters, assignmentParameter, callResult, loopbodyResult); + location, + modifiers, + this, + methodName, + parameters, + assignmentParameter, + methodDoc, + callResult, + loopbodyResult); this.methods.add(method); return method; } @@ -192,7 +241,7 @@ public Collection getMethods() { }); // Add methods from parent types, if not overridden. - for (final AbstractType parent : this.parents) { + for (final AbstractType parent : this.getParents()) { parent.getMethods().forEach(method -> { final String methodName = method.getName(); if (allMethods.containsKey(methodName)) { @@ -225,7 +274,7 @@ public Collection getLocalMethods() { @Override public Collection getSuperMethods(final String methodName) { // Try to get the wanted method from all super types, first one wins. - return this.parents.stream() + return this.getParents().stream() .flatMap(parent -> parent.getMethods(methodName).stream()) .collect(Collectors.toSet()); } @@ -238,7 +287,7 @@ public Collection getSuperMethods(final String methodName) { @Override public Collection getSuperMethods(final String methodName, final String superName) { // If super-type was specified, specifically search that one. - final Optional superType = this.parents.stream() + final Optional superType = this.getParents().stream() .filter(type -> type.getFullName().equals(superName)) .findAny(); if (!superType.isPresent()) { @@ -264,4 +313,27 @@ public String toString() { this.getFullName()); } + @Override + public int hashCode() { + return Objects.hash(this.getFullName()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final MagikType other = (MagikType) obj; + return Objects.equals(this.getTypeString(), other.getTypeString()); + } + } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Method.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Method.java index 260c07b7..8d9a738d 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Method.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Method.java @@ -4,6 +4,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -15,6 +16,245 @@ */ public class Method { + /** + * Global usage. + */ + public static class GlobalUsage { + + private final TypeString ref; + private final Location location; + + /** + * Constructor. + * @param ref Global reference. + * @param location Location of use. + */ + public GlobalUsage(final TypeString ref, final @Nullable Location location) { + this.ref = ref; + this.location = location; + } + + public TypeString getGlobal() { + return this.ref; + } + + @CheckForNull + public Location getLocation() { + return this.location; + } + + @Override + public int hashCode() { + return Objects.hash(this.ref); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final GlobalUsage otherTypeUsage = (GlobalUsage) obj; + return Objects.equals(otherTypeUsage.getGlobal(), this.getGlobal()); + } + + } + + /** + * Method usage. + */ + public static class MethodUsage { + + private final TypeString typeRef; + private final String methodName; + private final Location location; + + /** + * Constructor. + * @param typeRef Type reference. + * @param methodName Name of method. + * @param location Location of use. + */ + public MethodUsage(final TypeString typeRef, final String methodName, final @Nullable Location location) { + this.typeRef = typeRef; + this.methodName = methodName; + this.location = location; + } + + /** + * Constructor. + * @param typeRef Type reference. + * @param methodName Name of method. + */ + public MethodUsage(final TypeString typeRef, final String methodName) { + this(typeRef, methodName, null); + } + + public TypeString getType() { + return this.typeRef; + } + + public String getMethodName() { + return this.methodName; + } + + @CheckForNull + public Location getLocation() { + return this.location; + } + + @Override + public int hashCode() { + return Objects.hash(this.typeRef, this.methodName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final MethodUsage otherMethodUsage = (MethodUsage) obj; + return Objects.equals(otherMethodUsage.getType(), this.getType()) + && Objects.equals(otherMethodUsage.getMethodName(), this.getMethodName()); + } + + } + + /** + * Slot usage. + */ + public static class SlotUsage { + + private final String slotName; + private final Location location; + + /** + * Constructor. + * @param slotName Name of slot. + * @param location Location of use. + */ + public SlotUsage(final String slotName, final @Nullable Location location) { + this.slotName = slotName; + this.location = location; + } + + /** + * Constructor. + * @param slotName Name of slot. + */ + public SlotUsage(final String slotName) { + this(slotName, null); + } + + public String getSlotName() { + return this.slotName; + } + + public Location getLocation() { + return this.location; + } + + @Override + public int hashCode() { + return Objects.hash(this.slotName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final SlotUsage otherSlotUsage = (SlotUsage) obj; + return Objects.equals(otherSlotUsage.getSlotName(), this.getSlotName()); + } + + } + + /** + * Condition usage. + */ + public static class ConditionUsage { + + private final String conditionName; + private final Location location; + + /** + * Constructor. + * @param conditionName Name of condition. + * @param location Location of use. + */ + public ConditionUsage(final String conditionName, final @Nullable Location location) { + this.conditionName = conditionName; + this.location = location; + } + + /** + * Constructor. + * @param conditionName Name of condition. + */ + public ConditionUsage(final String conditionName) { + this(conditionName, null); + } + + public String getConditionName() { + return this.conditionName; + } + + public Location getLocation() { + return this.location; + } + + @Override + public int hashCode() { + return Objects.hash(this.conditionName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final ConditionUsage otherConditionUsage = (ConditionUsage) obj; + return Objects.equals(otherConditionUsage.getConditionName(), this.getConditionName()); + } + + } + /** * Method modifier. */ @@ -41,47 +281,56 @@ public String getValue() { } - private final EnumSet modifiers; private final Location location; + private final EnumSet modifiers; private final MagikType owner; private final List parameters; private final Parameter assignmentParameter; - private final ExpressionResult callResult; - private final ExpressionResult loopbodyResult; + private final ExpressionResultString callResult; + private final ExpressionResultString loopbodyResult; private final String name; + private final Set usedTypes; + private final Set calledMethods; + private final Set usedSlots; + private final Set usedConditions; private String doc; - private final Set usedTypes; - private final Set calledMethods; - private final Set usedSlots; /** * Constructor. + * @param location Location of definition. + * @param modifiers Modifiers. + * @param owner Owner of method. * @param name Name of method. * @param parameters Parameters of method. + * @param assignmentParameter Assignment parameter. + * @param methodDoc Method doc. * @param callResult Result of call. * @param loopbodyResult Result of iterator call. */ @SuppressWarnings({"java:S1319", "checkstyle:ParameterNumber"}) public Method( - final EnumSet modifiers, final @Nullable Location location, + final EnumSet modifiers, final MagikType owner, final String name, final List parameters, final @Nullable Parameter assignmentParameter, - final ExpressionResult callResult, - final ExpressionResult loopbodyResult) { - this.modifiers = modifiers; + final @Nullable String methodDoc, + final ExpressionResultString callResult, + final ExpressionResultString loopbodyResult) { this.location = location; + this.modifiers = modifiers; this.owner = owner; this.name = name; this.parameters = parameters; this.assignmentParameter = assignmentParameter; + this.doc = methodDoc; this.callResult = callResult; this.loopbodyResult = loopbodyResult; this.usedTypes = new HashSet<>(); this.calledMethods = new HashSet<>(); this.usedSlots = new HashSet<>(); + this.usedConditions = new HashSet<>(); } /** @@ -211,7 +460,7 @@ public Parameter getAssignmentParameter() { * Get result of call. * @return Result of call. */ - public ExpressionResult getCallResult() { + public ExpressionResultString getCallResult() { return this.callResult; } @@ -219,7 +468,7 @@ public ExpressionResult getCallResult() { * Get result of iterator call. * @return Result of iterator call. */ - public ExpressionResult getLoopbodyResult() { + public ExpressionResultString getLoopbodyResult() { return this.loopbodyResult; } @@ -241,53 +490,65 @@ public String getDoc() { /** * Add a used type. - * @param typeName Type name, including package. - */ - public void addUsedType(final String typeName) { - this.usedTypes.add(typeName); - } - - /** - * Add a used type. - * @param type Used type. + * @param typeUsage Type usage. */ - public void addUsedType(final AbstractType type) { - final String typeName = type.getFullName(); - this.addUsedType(typeName); + public void addUsedType(final GlobalUsage typeUsage) { + this.usedTypes.add(typeUsage); } /** * Get the used types. * @return All used types by this method. */ - public Set getUsedTypes() { + public Set getTypeUsages() { return Collections.unmodifiableSet(this.usedTypes); } /** * Add a called method. - * @param methodName Name of called method. + * @param calledMethod Name of called method. */ - public void addCalledMethod(final String methodName) { - this.calledMethods.add(methodName); + public void addCalledMethod(final MethodUsage calledMethod) { + this.calledMethods.add(calledMethod); } /** - * Get the method names by this method. - * @return Called method names. + * Get the method usages by this method. + * @return Method usages. */ - public Set getCalledMethods() { + public Set getMethodUsages() { return Collections.unmodifiableSet(this.calledMethods); } - public void addUsedSlot(final String slotName) { - this.usedSlots.add(slotName); + /** + * Add used slot. + * @param slotUsage Name of used slot. + */ + public void addUsedSlot(final SlotUsage slotUsage) { + this.usedSlots.add(slotUsage); } - public Set getUsedSlots() { + /** + * Get the slot usages by this method. + * @return Slot usages. + * @return + */ + public Set getUsedSlots() { return Collections.unmodifiableSet(this.usedSlots); } + public void addUsedCondition(final ConditionUsage conditionUsage) { + this.usedConditions.add(conditionUsage); + } + + /** + * Get the condition usages by this method. + * @return Condition usages. + */ + public Set getConditionUsages() { + return Collections.unmodifiableSet(this.usedConditions); + } + @Override public String toString() { return String.format( diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ObjectType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ObjectType.java deleted file mode 100644 index 8f91967f..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ObjectType.java +++ /dev/null @@ -1,37 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing.types; - -import java.util.Objects; - -/** - * Object magik type. - */ -public class ObjectType extends MagikType { - - public ObjectType(final GlobalReference globalReference) { - super(globalReference); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final ObjectType other = (ObjectType) obj; - return Objects.equals(this.getFullName(), other.getFullName()); - } - - @Override - public int hashCode() { - return Objects.hash(this.getFullName()); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Package.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Package.java index 03cd86cb..50de9379 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Package.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Package.java @@ -1,14 +1,15 @@ package nl.ramsolutions.sw.magik.analysis.typing.types; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.Location; +import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; /** * Smallworld Package. @@ -16,18 +17,22 @@ public class Package { private final String name; - private final Set uses = new HashSet<>(); - private final Map types = new HashMap<>(); + private final Set uses = ConcurrentHashMap.newKeySet(); + private final Map types = new ConcurrentHashMap<>(); private Location location; + private String doc; + private ITypeKeeper typeKeeper; /** * Constructor. * @param name Name of package. */ - public Package(final String name) { + public Package(final ITypeKeeper typeKeeper, final String name) { + this.typeKeeper = typeKeeper; this.name = name; this.setLocation(null); + this.typeKeeper.addPackage(this); } /** @@ -57,50 +62,61 @@ public void setLocation(final @Nullable Location location) { /** * Add a use of another package. - * @param usePackage Other package. + * @param pakkageName Other package. */ - public void addUse(final Package usePackage) { - this.uses.add(usePackage); + public void addUse(final String pakkageName) { + this.uses.add(pakkageName); + } + + /** + * Clear uses. + */ + public void clearUses() { + this.uses.clear(); } /** * Get all used packages. * @return All used packages. */ - public Set uses() { - return Collections.unmodifiableSet(this.uses); + public Set getUses() { + return this.uses.stream() + .map(this.typeKeeper::getPackage) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableSet()); } /** - * See if {{key}} is defined in this package. + * See if {@code key} is defined in this package. * This also includes used packages. * @param key Key to check. * @return true if defined in this package, false otherwise. */ public boolean containsKey(final String key) { - if (!this.types.containsKey(key)) { - for (final Package usedPackage : this.uses) { - if (usedPackage.containsKey(key)) { - return true; - } - } - - return false; - } + return this.get(key) != null; + } - return true; + /** + * Test if type is contained by this package. + * @param type Type to check. + * @return True if contained, false otherwise. + */ + public boolean containsTypeLocal(final AbstractType type) { + final String identifier = type.getTypeString().getIdentifier(); + return this.types.containsKey(identifier) + && this.types.get(identifier).equals(type); } /** - * Get a {{AbstractType}} defined in this package. + * Get a {@link AbstractType} defined in this package. * This also includes used packages. * @param key Key to get. - * @return {{AbstractType}} if found, otherwise null. + * @return {@link AbstractType} if found, otherwise null. */ @CheckForNull public AbstractType get(final String key) { if (!this.types.containsKey(key)) { - for (final Package usedPackage : this.uses) { + for (final Package usedPackage : this.getUses()) { final AbstractType type = usedPackage.get(key); if (type != null) { return type; @@ -120,6 +136,14 @@ public void put(final String key, final AbstractType type) { this.types.put(key, type); } + /** + * Put all types. + * @param newTypes Types to add. + */ + public void putAll(final Map newTypes) { + this.types.putAll(newTypes); + } + /** * Get all types in this package. * @return All types in this pacakge. @@ -129,36 +153,34 @@ public Map getTypes() { } /** - * Test if type is contained by this package. - * @param type Type to check. - * @return True if contained, false otherwise. + * Remove a type from this package. + * @param type Type to remove. */ - public boolean containsTypeLocal(final AbstractType type) { - final String typeName = type.getName(); - return this.types.containsKey(typeName) - && this.types.get(typeName).equals(type); + public void remove(final AbstractType type) { + final String identifier = type.getTypeString().getIdentifier(); + this.types.remove(identifier); } /** - * Remove an identifier from this package. - * @param identifier Identifier to remove. + * Set method documentation. + * @param comment Method doc. */ - public void remove(final String identifier) { - this.types.remove(identifier); + public void setDoc(final String comment) { + this.doc = comment; } /** - * Remove a type from this package. - * @param type Type to remove. + * Get method documentation. + * @return Method doc. */ - public void remove(final AbstractType type) { - final String identifier = type.getName(); - this.remove(identifier); + @CheckForNull + public String getDoc() { + return this.doc; } @Override public String toString() { - final String usesParts = this.uses().stream() + final String usesParts = this.getUses().stream() .map(Package::getName) .collect(Collectors.joining(",")); return String.format( diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ParameterReferenceType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ParameterReferenceType.java new file mode 100644 index 00000000..961d7422 --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ParameterReferenceType.java @@ -0,0 +1,108 @@ +package nl.ramsolutions.sw.magik.analysis.typing.types; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** + * Reference to the type of a parameter. + * TODO: Should this be an AbstractType? Probably otherwise it cannot be used with the reasoner etc. + * Maybe the hierarchy should change a bit, such as: + * - AbstractType + * - ConcreteType + * - MagikType + * - SelfType + * - ... + * - ParameterRefType + * - GenericedType + * - ... + */ +public class ParameterReferenceType extends AbstractType { + + private final String parameterName; + + public ParameterReferenceType(final String parameterName) { + this.parameterName = parameterName; + } + + public String getParameterName() { + return this.parameterName; + } + + @Override + public TypeString getTypeString() { + throw new IllegalStateException(); + } + + @Override + public String getFullName() { + return "_parameter(" + this.parameterName + ")"; + } + + @Override + public String getName() { + return "_parameter(" + this.parameterName + ")"; + } + + @Override + public Collection getMethods() { + return Collections.emptySet(); + } + + @Override + public Collection getLocalMethods() { + return Collections.emptySet(); + } + + @Override + public Collection getParents() { + return Collections.emptySet(); + } + + @Override + public Collection getSuperMethods(final String methodName) { + return Collections.emptySet(); + } + + @Override + public Collection getSuperMethods(final String methodName, final String superName) { + return Collections.emptySet(); + } + + @Override + public Collection getSlots() { + return Collections.emptySet(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final ParameterReferenceType other = (ParameterReferenceType) obj; + return Objects.equals(this.getParameterName(), other.getParameterName()); + } + + @Override + public int hashCode() { + return Objects.hash(this.parameterName); + } + + @Override + public String toString() { + return String.format( + "%s@%s(%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getParameterName()); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ProcedureInstance.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ProcedureInstance.java index 1240447c..f0af1985 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ProcedureInstance.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/ProcedureInstance.java @@ -1,36 +1,58 @@ package nl.ramsolutions.sw.magik.analysis.typing.types; import java.util.Collection; -import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; import java.util.Objects; +import java.util.Set; import javax.annotation.Nullable; import nl.ramsolutions.sw.magik.analysis.Location; /** * Procedure instance. */ -public class ProcedureInstance extends IntrinsicType { - - // TODO: Should this be modeled this way? - // Ideally a IntrinsicType + (special/named?) invoke() method, using a AliasType to bind to a variable? +public class ProcedureInstance extends AbstractType { /** * Serializer name for anonymouse procedure. */ - public static final String ANONYMOUS_PROCEDURE = "__anonymous__:__procedure__"; + public static final String ANONYMOUS_PROCEDURE = "__anonymous_procedure"; + private final MagikType procedureType; private final String procedureName; + private final Method invokeMethod; /** * Constructor, with loopbody types. - * @param globalReference Global reference. * @param procedureName Name of method or procedure. */ - public ProcedureInstance(final GlobalReference globalReference, final @Nullable String procedureName) { - super(globalReference); + @SuppressWarnings("checkstyle:ParameterNumber") + public ProcedureInstance( + final MagikType procedureType, + final @Nullable Location location, + final @Nullable String procedureName, + final EnumSet modifiers, + final List parameters, + final @Nullable String methodDoc, + final ExpressionResultString callResult, + final ExpressionResultString loopbodyResult) { + this.procedureType = procedureType; this.procedureName = procedureName != null ? procedureName : ANONYMOUS_PROCEDURE; + this.invokeMethod = new Method( + location, + modifiers, + this.procedureType, + "invoke()", + parameters, + null, + methodDoc, + callResult, + loopbodyResult); + + this.setDoc(methodDoc); } public String getProcedureName() { @@ -38,13 +60,57 @@ public String getProcedureName() { } @Override - public Slot addSlot(final Location slotLocation, final String slotName) { - throw new IllegalStateException(); + public Collection getSlots() { + return this.procedureType.getSlots(); } @Override - public Collection getSlots() { - return Collections.emptySet(); + public TypeString getTypeString() { + return this.procedureType.getTypeString(); + } + + @Override + public String getFullName() { + return this.procedureName; + } + + @Override + public String getName() { + return this.procedureName; + } + + @Override + public Collection getMethods() { + final Collection methods = new HashSet<>(this.procedureType.getMethods()); + methods.add(this.invokeMethod); + return methods; + } + + @Override + public Collection getLocalMethods() { + final Collection methods = new HashSet<>(this.procedureType.getLocalMethods()); + methods.add(this.invokeMethod); + return methods; + } + + @Override + public Collection getParents() { + return Set.of(this.procedureType); + } + + @Override + public Collection getSuperMethods(final String methodName) { + return this.procedureType.getSuperMethods(methodName); + } + + @Override + public Collection getSuperMethods(final String methodName, final String superName) { + return this.procedureType.getSuperMethods(methodName, superName); + } + + @Override + public int hashCode() { + return Objects.hash(this.getFullName()); } @Override @@ -65,11 +131,6 @@ public boolean equals(final Object obj) { return this == other; // Test on identity. } - @Override - public int hashCode() { - return Objects.hash(this.getFullName()); - } - @Override public String toString() { return String.format( diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SelfType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SelfType.java index 79a514d1..7b3d1be3 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SelfType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SelfType.java @@ -10,12 +10,12 @@ public final class SelfType extends AbstractType { /** - * Instance of {{_self}}/{{_clone}} to be used in all cases. + * Instance of {@code _self}/{@code _clone} to be used in all cases. */ public static final SelfType INSTANCE = new SelfType(); /** - * Serialized name of {{SelfType}}. + * Serialized name of {@code SelfType}. */ public static final String SERIALIZED_NAME = "_self"; @@ -25,14 +25,19 @@ public final class SelfType extends AbstractType { private SelfType() { } + @Override + public TypeString getTypeString() { + return TypeString.of(SelfType.SERIALIZED_NAME); + } + @Override public String getFullName() { - return SERIALIZED_NAME; + return SelfType.SERIALIZED_NAME; } @Override public String getName() { - return SERIALIZED_NAME; + return SelfType.SERIALIZED_NAME; } @Override @@ -63,8 +68,8 @@ public Collection getLocalMethods() { @Override public String toString() { return String.format( - "%s@%s", - this.getClass().getName(), Integer.toHexString(this.hashCode())); + "%s@%s", + this.getClass().getName(), Integer.toHexString(this.hashCode())); } @Override diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Slot.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Slot.java index e0086ad4..dd0934c7 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Slot.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/Slot.java @@ -20,7 +20,7 @@ public class Slot { * @param owner Owner of the slot. * @param name Name of this slot. */ - public Slot(@Nullable Location location, MagikType owner, String name) { + public Slot(final @Nullable Location location, final MagikType owner, final String name) { this.location = location; this.owner = owner; this.name = name; diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SlottedType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SlottedType.java deleted file mode 100644 index 1a5580c9..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/SlottedType.java +++ /dev/null @@ -1,37 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing.types; - -import java.util.Objects; - -/** - * Slotted magik type. - */ -public class SlottedType extends MagikType { - - public SlottedType(final GlobalReference globalReference) { - super(globalReference); - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null) { - return false; - } - - if (this.getClass() != obj.getClass()) { - return false; - } - - final SlottedType other = (SlottedType) obj; - return Objects.equals(this.getFullName(), other.getFullName()); - } - - @Override - public int hashCode() { - return Objects.hash(this.getFullName()); - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/TypeString.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/TypeString.java new file mode 100644 index 00000000..e32567ab --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/TypeString.java @@ -0,0 +1,226 @@ +package nl.ramsolutions.sw.magik.analysis.typing.types; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import nl.ramsolutions.sw.magik.api.MagikKeyword; +import nl.ramsolutions.sw.magik.api.NewDocGrammar; + +/** + * Type string, containing package name and identifier. + * Examples: + * - {@code "sw:rope"} + * - {@code "sw:char16_vector|sw:symbol|sw:unset"} + * - {@code "_undefined"} + * - {@code "_self|sw:unset"} + */ +public final class TypeString { + + @SuppressWarnings("checkstyle:JavadocVariable") + public static final TypeString UNDEFINED = new TypeString(UndefinedType.SERIALIZED_NAME); + @SuppressWarnings("checkstyle:JavadocVariable") + public static final TypeString SELF = new TypeString(SelfType.SERIALIZED_NAME); + + @SuppressWarnings("checkstyle:JavadocVariable") + public static final String TYPE_COMBINATOR = NewDocGrammar.Punctuator.TYPE_COMBINATOR.getValue(); + + /** + * Stream collector. + */ + public static final Collector COLLECTOR = Collector.of( + ArrayList::new, + (list, value) -> list.add(value), + (list, values) -> { + list.addAll(values); + return list; + }, + list -> { + final String str = list.stream() + .map(TypeString::getString) + .sorted() + .collect(Collectors.joining(TYPE_COMBINATOR)); + return new TypeString(str); + }); + + private static final String TYPE_COMBINATOR_RE = Pattern.quote(TYPE_COMBINATOR); + private static final String DEFAULT_PACKAGE = "user"; + + private final String string; + private final String currentPackage; + + /** + * Constructor. + * @param string Type string, e.g., {@code "sw:rope"}. + */ + public TypeString(final String string) { + this.string = string.trim(); + this.currentPackage = DEFAULT_PACKAGE; + } + + /** + * Constructor. + * @param identifier Identifier, e.g., {@code "sw:rope"}. + * @param currentPackage The current package, e.g., {@code "user"}. + */ + public TypeString(final String identifier, final String currentPackage) { + this.string = identifier.trim(); + this.currentPackage = currentPackage.trim(); + } + + /** + * Static constructor, for readability. + * @param string String, e.g., {@code "sw:rope"}, or {@code "sw:symbol|sw:unset"}. + * @return New type string. + */ + public static TypeString of(final String string) { + return new TypeString(string); + } + + /** + * Static constructor, for readability. + * @param string Identifier, e.g., {@code "rope"}. + * @param currentPackage Package name, e.g., {@code "sw"}. + * @return Type strring.. + */ + public static TypeString of(final String string, final String currentPackage) { + return new TypeString(string, currentPackage); + } + + /** + * Get package of type string, otherwise package it was defined in. + * @return Package. + */ + public String getPakkage() { + if (!this.isSingle()) { + throw new IllegalStateException(); + } + + if (this.string.contains(":")) { + final String[] parts = this.string.split(":"); + return parts[0]; + } + + return this.currentPackage; + } + + /** + * Get the identifier of this TypeString. + * Will strip package if needed. + * @return Identifier. + */ + public String getIdentifier() { + if (!this.isSingle()) { + throw new IllegalStateException(); + } + + if (this.string.contains(":")) { + final String[] parts = this.string.split(":"); + return parts[1]; + } + + return this.string; + } + + /** + * Get the raw string. + */ + public String getString() { + return this.string; + } + + /** + * Get full string. + * @return + */ + public String getFullString() { + if (this.string.contains(":")) { + return this.string; + } + + return this.currentPackage + ":" + this.string; + } + + public boolean isUndefined() { + return this.getString().equalsIgnoreCase(UndefinedType.SERIALIZED_NAME); + } + + public boolean isSelf() { + return this.getString().equals(SelfType.SERIALIZED_NAME) + || this.getString().equalsIgnoreCase(MagikKeyword.CLONE.getValue()); + } + + public boolean isSingle() { + return !this.getString().contains(TYPE_COMBINATOR); + } + + /** + * Get parts of (combined) string. + */ + public List parts() { + final String[] parts = this.string.split(TYPE_COMBINATOR_RE); + return Stream.of(parts) + .map(String::trim) + .map(part -> TypeString.of(part, this.currentPackage)) + .collect(Collectors.toList()); + } + + public boolean isParameterReference() { + return this.string.toLowerCase().startsWith("_parameter"); + } + + /** + * Get reference parameter. + * @return Referenced parameter. + */ + public String referencedParameter() { + if (!this.isParameterReference()) { + throw new IllegalStateException(); + } + + final int indexOpen = this.string.indexOf("("); + final int indexClose = this.string.indexOf(")"); + if (indexOpen == -1 + || indexClose == -1) { + // Don't crash, just + return null; + } + + return this.string.substring(indexOpen + 1, indexClose).trim(); + } + + @Override + public int hashCode() { + return Objects.hash(this.currentPackage, this.string); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + final TypeString other = (TypeString) obj; + return Objects.equals(this.getFullString(), other.getFullString()); + } + + @Override + public String toString() { + return String.format( + "%s@%s(%s:%s)", + this.getClass().getName(), Integer.toHexString(this.hashCode()), + this.getPakkage(), this.getString()); + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/UndefinedType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/UndefinedType.java index 47f61006..bbe609b2 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/UndefinedType.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/analysis/typing/types/UndefinedType.java @@ -5,17 +5,17 @@ import nl.ramsolutions.sw.magik.analysis.Location; /** - * Special type used when the {{TypeReasoner}} cannot determine type. + * Special type used when the {@link TypeReasoner} cannot determine type. */ public final class UndefinedType extends AbstractType { /** - * Instance of {{UndefinedType}} to be used in all cases. + * Instance of {@code UndefinedType} to be used in all cases. */ public static final UndefinedType INSTANCE = new UndefinedType(); /** - * Serialized name of {{UndefinedType}}. + * Serialized name of {@code UndefinedType}. */ public static final String SERIALIZED_NAME = "_undefined"; @@ -25,14 +25,19 @@ public final class UndefinedType extends AbstractType { private UndefinedType() { } + @Override + public TypeString getTypeString() { + return TypeString.of(UndefinedType.SERIALIZED_NAME); + } + @Override public String getFullName() { - return SERIALIZED_NAME; + return UndefinedType.SERIALIZED_NAME; } @Override public String getName() { - return SERIALIZED_NAME; + return UndefinedType.SERIALIZED_NAME; } @Override @@ -41,12 +46,12 @@ public Collection getSlots() { } @Override - public Slot getSlot(String name) { + public Slot getSlot(final String name) { return null; } @Override - public Collection getMethods(String methodName) { + public Collection getMethods(final String methodName) { return Collections.emptySet(); } @@ -56,7 +61,7 @@ public Collection getMethods() { } @Override - public boolean hasLocalMethod(String methodName) { + public boolean hasLocalMethod(final String methodName) { return false; } @@ -68,8 +73,8 @@ public Collection getLocalMethods() { @Override public String toString() { return String.format( - "%s@%s", - this.getClass().getName(), Integer.toHexString(this.hashCode())); + "%s@%s", + this.getClass().getName(), Integer.toHexString(this.hashCode())); } @Override @@ -78,12 +83,12 @@ public Collection getParents() { } @Override - public Collection getSuperMethods(String methodName) { + public Collection getSuperMethods(final String methodName) { return Collections.emptySet(); } @Override - public Collection getSuperMethods(String methodName, String superName) { + public Collection getSuperMethods(final String methodName, final String superName) { return Collections.emptySet(); } @@ -93,7 +98,7 @@ public Location getLocation() { } @Override - public void setLocation(Location location) { + public void setLocation(final Location location) { throw new IllegalStateException(); } @@ -103,7 +108,7 @@ public String getDoc() { } @Override - public void setDoc(String comment) { + public void setDoc(final String comment) { throw new IllegalStateException(); } diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/ExtendedTokenType.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/ExtendedTokenType.java deleted file mode 100644 index 17d25d66..00000000 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/ExtendedTokenType.java +++ /dev/null @@ -1,32 +0,0 @@ -package nl.ramsolutions.sw.magik.api; - -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.TokenType; - -/** - * Extended token types. - */ -@SuppressWarnings("checkstyle:JavadocVariable") -public enum ExtendedTokenType implements TokenType { - - WHITESPACE, - STATEMENT_SEPARATOR, - OTHER, - SYNTAX_ERROR; - - @Override - public String getName() { - return this.name(); - } - - @Override - public String getValue() { - return this.name(); - } - - @Override - public boolean hasToBeSkippedFromAst(final AstNode node) { - return false; - } - -} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/MagikGrammar.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/MagikGrammar.java index c3bb2c6e..77144a87 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/MagikGrammar.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/MagikGrammar.java @@ -18,6 +18,8 @@ public enum MagikGrammar implements GrammarRuleKey { SPACING, SPACING_NO_LB, NEXT_NOT_LB, + SPACING_NO_LB_2, + NEXT_NOT_COMMENT, // specials SYNTAX_ERROR, @@ -27,6 +29,9 @@ public enum MagikGrammar implements GrammarRuleKey { PACKAGE_SPECIFICATION, PACKAGE_IDENTIFIER, METHOD_DEFINITION, + EXEMPLAR_NAME, + METHOD_NAME, + CONDITION_NAME, METHOD_DEFINITION_SYNTAX_ERROR, TRANSMIT, @@ -139,6 +144,7 @@ public enum MagikGrammar implements GrammarRuleKey { CLASS, LOOPBODY, PROCEDURE_DEFINITION, + PROCEDURE_NAME, PROCEDURE_DEFINITION_SYNTAX_ERROR, SELF, CLONE, @@ -185,9 +191,9 @@ public static LexerlessGrammar create() { final LexerlessGrammarBuilder b = LexerlessGrammarBuilder.create(); b.rule(NEWLINE).is( - b.commentTrivia(b.regexp(NEWLINE_REGEXP))).skip(); + b.skippedTrivia(b.regexp(NEWLINE_REGEXP))).skip(); b.rule(WHITESPACE).is( - b.commentTrivia(b.regexp("[" + WHITESPACE_SINGLE_REGEXP + "]+"))).skip(); + b.skippedTrivia(b.regexp("[" + WHITESPACE_SINGLE_REGEXP + "]+"))).skip(); b.rule(COMMENT).is( b.commentTrivia(b.regexp(COMMENT_REGEXP))).skip(); b.rule(SPACING).is( @@ -198,9 +204,15 @@ public static LexerlessGrammar create() { COMMENT))).skip(); b.rule(SPACING_NO_LB).is( b.zeroOrMore( - b.commentTrivia(b.regexp("[\\s&&[^\n\r]]++")))).skip(); + b.skippedTrivia(b.regexp("[\\s&&[^\n\r]]++")))).skip(); b.rule(NEXT_NOT_LB).is( b.nextNot(b.regexp("[\n\r]"))).skip(); + b.rule(SPACING_NO_LB_2).is( + SPACING_NO_LB, + NEXT_NOT_COMMENT, + NEXT_NOT_LB).skip(); + b.rule(NEXT_NOT_COMMENT).is( + b.nextNot(b.regexp("#.*"))).skip(); b.rule(MAGIK).is( b.zeroOrMore( @@ -269,7 +281,8 @@ private static void atoms(final LexerlessGrammarBuilder b) { b.rule(PROCEDURE_DEFINITION).is( b.optional(MagikKeyword.ITER), - MagikKeyword.PROC, b.optional(LABEL), + MagikKeyword.PROC, + b.optional(PROCEDURE_NAME), b.firstOf( b.sequence( PARAMETERS_PAREN, @@ -277,13 +290,14 @@ private static void atoms(final LexerlessGrammarBuilder b) { b.next(MagikKeyword.ENDPROC)), PROCEDURE_DEFINITION_SYNTAX_ERROR), MagikKeyword.ENDPROC); + b.rule(PROCEDURE_NAME).is(LABEL); b.rule(PROCEDURE_DEFINITION_SYNTAX_ERROR).is( SPACING, b.regexp(MagikGrammar.syntaxErrorRegexp(MagikKeyword.ENDPROC))); } private static void expressions(final LexerlessGrammarBuilder b) { - b.rule(SLOT).is(MagikPunctuator.DOT, SPACING_NO_LB, NEXT_NOT_LB, IDENTIFIER); + b.rule(SLOT).is(MagikPunctuator.DOT, SPACING_NO_LB_2, IDENTIFIER); b.rule(SIMPLE_VECTOR).is( MagikPunctuator.BRACE_L, b.firstOf( @@ -300,23 +314,23 @@ private static void expressions(final LexerlessGrammarBuilder b) { b.rule(GATHER_EXPRESSION).is(MagikKeyword.GATHER, TUPLE); b.rule(EXPRESSION).is(ASSIGNMENT_EXPRESSION); - b.rule(ASSIGNMENT_EXPRESSION).is(AUGMENTED_ASSIGNMENT_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), AUGMENTED_ASSIGNMENT_EXPRESSION)).skipIfOneChild(); - b.rule(AUGMENTED_ASSIGNMENT_EXPRESSION).is(OR_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, OPERATOR, b.firstOf(MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), OR_EXPRESSION)).skipIfOneChild(); - b.rule(OR_EXPRESSION).is(XOR_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikKeyword.ORIF, MagikKeyword.OR), XOR_EXPRESSION)).skipIfOneChild(); - b.rule(XOR_EXPRESSION).is(AND_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, MagikKeyword.XOR, AND_EXPRESSION)).skipIfOneChild(); - b.rule(AND_EXPRESSION).is(EQUALITY_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikKeyword.ANDIF, MagikKeyword.AND), EQUALITY_EXPRESSION)).skipIfOneChild(); - b.rule(EQUALITY_EXPRESSION).is(RELATIONAL_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikKeyword.ISNT, MagikKeyword.IS, MagikKeyword.CF, MagikOperator.EQ, MagikOperator.NEQ, MagikOperator.NE), RELATIONAL_EXPRESSION)).skipIfOneChild(); - b.rule(RELATIONAL_EXPRESSION).is(ADDITIVE_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikOperator.GE, MagikOperator.GT, MagikOperator.LE, MagikOperator.LT), ADDITIVE_EXPRESSION)).skipIfOneChild(); - b.rule(ADDITIVE_EXPRESSION).is(MULTIPLICATIVE_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikOperator.PLUS, MagikOperator.MINUS), MULTIPLICATIVE_EXPRESSION)).skipIfOneChild(); - b.rule(MULTIPLICATIVE_EXPRESSION).is(EXPONENTIAL_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, b.firstOf(MagikOperator.STAR, MagikOperator.DIV, MagikKeyword.DIV, MagikKeyword.MOD), EXPONENTIAL_EXPRESSION)).skipIfOneChild(); - b.rule(EXPONENTIAL_EXPRESSION).is(UNARY_EXPRESSION, b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, MagikOperator.EXP, UNARY_EXPRESSION)).skipIfOneChild(); + b.rule(ASSIGNMENT_EXPRESSION).is(AUGMENTED_ASSIGNMENT_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), AUGMENTED_ASSIGNMENT_EXPRESSION)).skipIfOneChild(); + b.rule(AUGMENTED_ASSIGNMENT_EXPRESSION).is(OR_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, OPERATOR, b.firstOf(MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), OR_EXPRESSION)).skipIfOneChild(); + b.rule(OR_EXPRESSION).is(XOR_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikKeyword.ORIF, MagikKeyword.OR), XOR_EXPRESSION)).skipIfOneChild(); + b.rule(XOR_EXPRESSION).is(AND_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, MagikKeyword.XOR, AND_EXPRESSION)).skipIfOneChild(); + b.rule(AND_EXPRESSION).is(EQUALITY_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikKeyword.ANDIF, MagikKeyword.AND), EQUALITY_EXPRESSION)).skipIfOneChild(); + b.rule(EQUALITY_EXPRESSION).is(RELATIONAL_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikKeyword.ISNT, MagikKeyword.IS, MagikKeyword.CF, MagikOperator.EQ, MagikOperator.NEQ, MagikOperator.NE), RELATIONAL_EXPRESSION)).skipIfOneChild(); + b.rule(RELATIONAL_EXPRESSION).is(ADDITIVE_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikOperator.GE, MagikOperator.GT, MagikOperator.LE, MagikOperator.LT), ADDITIVE_EXPRESSION)).skipIfOneChild(); + b.rule(ADDITIVE_EXPRESSION).is(MULTIPLICATIVE_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikOperator.PLUS, MagikOperator.MINUS), MULTIPLICATIVE_EXPRESSION)).skipIfOneChild(); + b.rule(MULTIPLICATIVE_EXPRESSION).is(EXPONENTIAL_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, b.firstOf(MagikOperator.STAR, MagikOperator.DIV, MagikKeyword.DIV, MagikKeyword.MOD), EXPONENTIAL_EXPRESSION)).skipIfOneChild(); + b.rule(EXPONENTIAL_EXPRESSION).is(UNARY_EXPRESSION, b.zeroOrMore(SPACING_NO_LB_2, MagikOperator.EXP, UNARY_EXPRESSION)).skipIfOneChild(); b.rule(UNARY_EXPRESSION).is(b.firstOf( b.sequence(b.firstOf(MagikKeyword.ALLRESULTS, MagikKeyword.SCATTER, MagikOperator.NOT, MagikKeyword.NOT, MagikOperator.PLUS, MagikOperator.MINUS), UNARY_EXPRESSION), POSTFIX_EXPRESSION)).skipIfOneChild(); b.rule(POSTFIX_EXPRESSION).is( ATOM, b.zeroOrMore( - SPACING_NO_LB, NEXT_NOT_LB, + SPACING_NO_LB_2, b.firstOf( METHOD_INVOCATION, PROCEDURE_INVOCATION))).skipIfOneChild(); @@ -340,6 +354,7 @@ private static void expressions(final LexerlessGrammarBuilder b) { IF, FOR, OVER, + WHILE, LOOP, BLOCK, PROTECT, @@ -369,13 +384,13 @@ private static void expressions(final LexerlessGrammarBuilder b) { b.rule(METHOD_INVOCATION).is( b.firstOf( b.sequence( - SPACING_NO_LB, NEXT_NOT_LB, MagikPunctuator.DOT, IDENTIFIER, + SPACING_NO_LB_2, MagikPunctuator.DOT, IDENTIFIER, b.optional( - SPACING_NO_LB, NEXT_NOT_LB, ARGUMENTS_PAREN)), + SPACING_NO_LB_2, ARGUMENTS_PAREN)), b.sequence( - SPACING_NO_LB, NEXT_NOT_LB, ARGUMENTS_SQUARE)), + SPACING_NO_LB_2, ARGUMENTS_SQUARE)), b.optional( - SPACING_NO_LB, NEXT_NOT_LB, + SPACING_NO_LB_2, b.firstOf( MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), @@ -407,8 +422,9 @@ private static void statements(final LexerlessGrammarBuilder b) { b.rule(STATEMENT_SEPARATOR).is( b.firstOf( b.sequence( - SPACING_NO_LB, - b.commentTrivia(MagikPunctuator.SEMICOLON)), + SPACING_NO_LB_2, + // b.commentTrivia(MagikPunctuator.SEMICOLON)), + b.skippedTrivia(MagikPunctuator.SEMICOLON)), b.sequence( SPACING_NO_LB, b.optional(COMMENT), @@ -484,27 +500,27 @@ private static void statements(final LexerlessGrammarBuilder b) { MagikKeyword.GATHER, EXPRESSION)))); b.rule(RETURN_STATEMENT).is( - MagikKeyword.RETURN, b.optional(SPACING_NO_LB, NEXT_NOT_LB, TUPLE)); + MagikKeyword.RETURN, b.optional(SPACING_NO_LB_2, TUPLE)); b.rule(EMIT_STATEMENT).is(MagikPunctuator.EMIT, TUPLE); b.rule(EXPRESSION_STATEMENT).is(EXPRESSION); b.rule(PRIMITIVE_STATEMENT).is(MagikKeyword.PRIMITIVE, NUMBER); b.rule(LEAVE_STATEMENT).is( MagikKeyword.LEAVE, b.optional(LABEL), - b.optional(SPACING_NO_LB, NEXT_NOT_LB, MagikKeyword.WITH, TUPLE)); + b.optional(SPACING_NO_LB_2, MagikKeyword.WITH, TUPLE)); b.rule(CONTINUE_STATEMENT).is( MagikKeyword.CONTINUE, b.optional(LABEL), - b.optional(SPACING_NO_LB, NEXT_NOT_LB, MagikKeyword.WITH, TUPLE)); + b.optional(SPACING_NO_LB_2, MagikKeyword.WITH, TUPLE)); b.rule(LOOPBODY).is( MagikKeyword.LOOPBODY, MagikPunctuator.PAREN_L, b.optional(TUPLE), MagikPunctuator.PAREN_R); b.rule(THROW_STATEMENT).is( MagikKeyword.THROW, EXPRESSION, - b.optional(SPACING_NO_LB, NEXT_NOT_LB, MagikKeyword.WITH, TUPLE)); + b.optional(SPACING_NO_LB_2, MagikKeyword.WITH, TUPLE)); b.rule(HANDLING).is( MagikKeyword.HANDLING, b.firstOf( b.sequence( - IDENTIFIERS, + CONDITION_NAME, b.zeroOrMore(MagikPunctuator.COMMA, CONDITION_NAME), MagikKeyword.WITH, b.firstOf(EXPRESSION, MagikKeyword.DEFAULT)), MagikKeyword.DEFAULT), @@ -552,11 +568,12 @@ private static void statements(final LexerlessGrammarBuilder b) { b.regexp(MagikGrammar.syntaxErrorRegexp(MagikKeyword.ENDTRY))); b.rule(TRY_VARIABLE).is(IDENTIFIER); b.rule(WHEN).is( - MagikKeyword.WHEN, IDENTIFIERS, + MagikKeyword.WHEN, CONDITION_NAME, b.zeroOrMore(MagikPunctuator.COMMA, CONDITION_NAME), BODY); + b.rule(CONDITION_NAME).is(IDENTIFIER); b.rule(CATCH).is( - MagikKeyword.CATCH, b.optional(SPACING_NO_LB, NEXT_NOT_LB, EXPRESSION), + MagikKeyword.CATCH, b.optional(SPACING_NO_LB_2, EXPRESSION), b.firstOf( b.sequence( BODY, @@ -636,22 +653,24 @@ private static void constructs(final LexerlessGrammarBuilder b) { MagikKeyword.METHOD, b.firstOf( b.sequence( - IDENTIFIER, + EXEMPLAR_NAME, b.firstOf( b.sequence( - MagikPunctuator.DOT, IDENTIFIER, + MagikPunctuator.DOT, METHOD_NAME, b.optional( - SPACING_NO_LB, NEXT_NOT_LB, + SPACING_NO_LB_2, PARAMETERS_PAREN)), PARAMETERS_SQUARE), b.optional( - SPACING_NO_LB, NEXT_NOT_LB, + SPACING_NO_LB_2, b.firstOf(MagikOperator.CHEVRON, MagikOperator.BOOT_CHEVRON), ASSIGNMENT_PARAMETER), BODY, b.next(MagikKeyword.ENDMETHOD)), METHOD_DEFINITION_SYNTAX_ERROR), MagikKeyword.ENDMETHOD); + b.rule(EXEMPLAR_NAME).is(IDENTIFIER); + b.rule(METHOD_NAME).is(IDENTIFIER); b.rule(METHOD_DEFINITION_SYNTAX_ERROR).is( SPACING, b.regexp(MagikGrammar.syntaxErrorRegexp(MagikKeyword.ENDMETHOD))); @@ -765,7 +784,7 @@ private static void constructs(final LexerlessGrammarBuilder b) { b.firstOf( b.sequence( EXPRESSION, - b.zeroOrMore(SPACING_NO_LB, NEXT_NOT_LB, MagikPunctuator.COMMA, EXPRESSION)), + b.zeroOrMore(SPACING_NO_LB_2, MagikPunctuator.COMMA, EXPRESSION)), b.sequence( MagikPunctuator.PAREN_L, EXPRESSION, @@ -775,7 +794,7 @@ private static void constructs(final LexerlessGrammarBuilder b) { b.rule(IDENTIFIERS).is( IDENTIFIER, b.zeroOrMore( - SPACING_NO_LB, NEXT_NOT_LB, MagikPunctuator.COMMA, IDENTIFIER)); + MagikPunctuator.COMMA, IDENTIFIER)); b.rule(IDENTIFIERS_WITH_GATHER).is( b.firstOf( b.sequence( @@ -821,10 +840,7 @@ private static void constructs(final LexerlessGrammarBuilder b) { MagikKeyword.PACKAGE, PACKAGE_IDENTIFIER, b.next( b.firstOf( - b.sequence( - SPACING_NO_LB, - b.optional(COMMENT), - NEWLINE), + STATEMENT_SEPARATOR, b.endOfInput()))); b.rule(LABEL).is(MagikPunctuator.AT, SPACING, b.regexp(LABEL_REGEXP)); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/NewDocGrammar.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/NewDocGrammar.java index 1cdce9e5..f09cd32b 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/NewDocGrammar.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/api/NewDocGrammar.java @@ -39,6 +39,7 @@ public enum NewDocGrammar implements GrammarRuleKey { TYPE_SELF, TYPE_CLONE, + TYPE_PARAMETER_REFERENCE, // root NEW_DOC; @@ -87,7 +88,10 @@ public enum Punctuator implements GrammarRuleKey { TYPE_SEPARATOR(","), TYPE_COMBINATOR("|"), TYPE_OPEN("{"), - TYPE_CLOSE("}"); + TYPE_CLOSE("}"), + TYPE_ARG_OPEN("("), + TYPE_ARG_CLOSE(")"), + TYPE_PARAM_REF("_parameter"); private final String value; @@ -200,12 +204,18 @@ public static LexerlessGrammar create() { b.firstOf( TYPE_SELF, TYPE_CLONE, + TYPE_PARAMETER_REFERENCE, b.regexp(TYPE_IDENTIFIER_REGEXP))); b.rule(TYPE_SELF).is( b.regexp(SELF_REGEXP)); b.rule(TYPE_CLONE).is( b.regexp(CLONE_REGEXP)); + b.rule(TYPE_PARAMETER_REFERENCE).is( + Punctuator.TYPE_PARAM_REF, + Punctuator.TYPE_ARG_OPEN, + b.regexp(SIMPLE_IDENTIFIER_REGEXP), + Punctuator.TYPE_ARG_CLOSE); b.setRootRule(NEW_DOC); diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReader.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReader.java new file mode 100644 index 00000000..bdfbec0e --- /dev/null +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReader.java @@ -0,0 +1,195 @@ +package nl.ramsolutions.sw.magik.parser; + +import com.sonar.sslr.api.AstNode; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.CheckForNull; +import nl.ramsolutions.sw.magik.MagikFile; +import nl.ramsolutions.sw.magik.analysis.scope.Scope; + +/** + * Comment Instruction reader. + */ +public class CommentInstructionReader { + + /** + * Instruction type. + */ + public static final class InstructionType { + + private final String type; + private final boolean isScopeInstruction; + private final Pattern pattern; + + private InstructionType(final String type, final boolean isScopeInstruction) { + this.type = type; + this.isScopeInstruction = isScopeInstruction; + this.pattern = isScopeInstruction + ? Pattern.compile("^\\s*#[ \t]*" + Pattern.quote(type) + ": ?(.*)") + : Pattern.compile(".*#[ \t]*" + Pattern.quote(type) + ": ?(.*)"); + } + + public String getType() { + return this.type; + } + + public boolean isScopeInstruction() { + return this.isScopeInstruction; + } + + public Pattern getPattern() { + return this.pattern; + } + + /** + * Create a new `InstructionType`. + * @param type Name of type. + * @return New `InstructionType`. + */ + public static InstructionType createInstructionType(final String type) { + return new InstructionType(type, false); + } + + /** + * Create a new `InstructionType` which only matches in a scope/on its own line. + * To be used, for example, to specify instructions which appyl to the whole scope. + * @param type Name of type. + * @return New `InstructionType`. + */ + public static InstructionType createScopeInstructionType(final String type) { + return new InstructionType(type, true); + } + + } + + private final MagikFile magikFile; + private final Set instructionTypes; + private final Map> instructions = new HashMap<>(); + private boolean isRead; + + /** + * Constructor. + * @param magikFile Magik file. + * @param instructionTypes Instrunction types to read. + */ + public CommentInstructionReader(final MagikFile magikFile, final Set instructionTypes) { + this.magikFile = magikFile; + this.instructionTypes = instructionTypes; + } + + /** + * Get instruction for node. + * Simply takes line number from node and gets instruction(s) at line. + * @param node Node. + * @param instructionType Instruction type. + * @return Instruction, if any. + */ + @CheckForNull + public String getInstructionForNode(final AstNode node, final InstructionType instructionType) { + final int lineNo = node.getTokenLine(); + return this.getInstructionsAtLine(lineNo, instructionType); + } + + /** + * Get instructions at line. + * @param lineNo Line number. + * @param instructionType Instruction type. + * @return Instruction, if any. + */ + @CheckForNull + public String getInstructionsAtLine(final int lineNo, final InstructionType instructionType) { + this.ensureRead(); + + if (!this.instructions.containsKey(lineNo)) { + return null; + } + + return this.instructions.get(lineNo).entrySet().stream() + .filter(entry -> entry.getKey() == instructionType) + .map(entry -> entry.getValue()) + .findAny() + .orElse(null); + } + + /** + * Get instructions for this scope. + * @param scope Scope to get instructions from. + * @param instructionType Instruction type. + * @return Instructions in scope. + */ + public Set getScopeInstructions(final Scope scope, final InstructionType instructionType) { + if (!instructionType.isScopeInstruction()) { + throw new IllegalStateException("Excepted scope instruction"); + } + + final int fromLine = scope.getStartLine(); + final int toLine = scope.getEndLine(); + return IntStream.range(fromLine, toLine) + .mapToObj(line -> this.getInstructionsAtLine(line, instructionType)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + private void ensureRead() { + if (this.isRead) { + return; + } + + final String[] lines = this.magikFile.getSourceLines(); + if (lines == null) { + return; + } + + this.instructionTypes.forEach(instructionType -> { + final Pattern pattern = instructionType.getPattern(); + for (int lineNo = 0; lineNo < lines.length; ++lineNo) { + final String line = lines[lineNo]; + final Matcher matcher = pattern.matcher(line); + if (!matcher.find()) { + continue; + } + + final Map instructionsAtLine = + this.instructions.computeIfAbsent(lineNo + 1, HashMap::new); + final String instruction = matcher.group(1); + instructionsAtLine.put(instructionType, instruction); + } + }); + + this.isRead = true; + } + + /** + * Parse a instruction(s) with the form: `a=b; c=d`. + * An instruction is a key/value pair bound by a `=` character. + * Instructions are separated with a `;`. + * @param str Instructions to parse + * @return Map of parsed key/value pairs. + */ + public static Map parseInstructions(final String str) { + final Map instructions = new HashMap<>(); + + Arrays.stream(str.split(";")) + .map(String::trim) + .forEach(item -> { + final String[] parts = item.split("="); + if (parts.length != 2) { + return; + } + + final String key = parts[0].trim(); + final String value = parts[1].trim(); + instructions.put(key, value); + }); + + return instructions; + } + +} diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikCommentExtractor.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikCommentExtractor.java index abe3f341..1537a4c7 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikCommentExtractor.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikCommentExtractor.java @@ -19,7 +19,7 @@ private MagikCommentExtractor() { } /** - * Extract comments from {{node}}. + * Extract comments from {@link node}. * @param node Node containing {@link Trivia} with comments. * @return Stream of comment {@link Token}s. */ @@ -31,7 +31,7 @@ public static Stream extractComments(final AstNode node) { } /** - * Extract line comments from {{node}}. + * Extract line comments from {@link node}. * @param node Node containing {@link Trivia} with comments. * @return Stream of comment {@link Token}s. */ @@ -48,9 +48,9 @@ public static Stream extractLineComments(final AstNode node) { final Token previousTriviaLastToken = previousTriviaTokens.get(previousTriviaTokens.size() - 1); if (trivia.isComment() && (previousTrivia.isSkippedText() // Whitespace from start of line. - && previousTriviaLastToken.getColumn() == 0 - || previousTrivia.isSkippedText() // EOL. - && previousTriviaLastToken.getType() == GenericTokenType.EOL)) { + && previousTriviaLastToken.getColumn() == 0 + || previousTrivia.isSkippedText() // EOL. + && previousTriviaLastToken.getType() == GenericTokenType.EOL)) { final Token lineCommentToken = trivia.getToken(); lineCommentTokens.add(lineCommentToken); } @@ -62,7 +62,7 @@ public static Stream extractLineComments(final AstNode node) { } /** - * Extract Doc comments for {{node}}. + * Extract Doc comments for {@link node}. * @param node Node containing {@link Trivia} with comments. * @return Stream of Doc comment {@link Token}s. */ diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikParser.java index 3de18f74..7af35e9a 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/MagikParser.java @@ -2,9 +2,7 @@ import com.sonar.sslr.api.AstNode; import com.sonar.sslr.api.AstNodeType; -import com.sonar.sslr.api.GenericTokenType; import com.sonar.sslr.api.Token; -import com.sonar.sslr.api.Trivia; import com.sonar.sslr.impl.Parser; import java.io.File; import java.io.FileReader; @@ -15,12 +13,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.EnumMap; -import java.util.List; import java.util.Map; import nl.ramsolutions.sw.FileCharsetDeterminer; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import nl.ramsolutions.sw.magik.api.MagikGrammar; -import nl.ramsolutions.sw.magik.api.MagikPunctuator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.sslr.parser.LexerlessGrammar; @@ -123,9 +118,6 @@ public AstNode parse(final String source) throws IOException { // Apply RULE_MAPPING. this.applyRuleMapping(magikNode); - // Apply whitespace conversion. - this.applyWhitespaceConversion(magikNode); - return magikNode; } @@ -148,9 +140,6 @@ public AstNode parse(final Path path) throws IOException { // Apply RULE_MAPPING. this.applyRuleMapping(magikNode); - // Apply whitespace conversion. - this.applyWhitespaceConversion(magikNode); - return magikNode; } } @@ -179,48 +168,6 @@ private void applyRuleMapping(final AstNode node) { node.getChildren().forEach(this::applyRuleMapping); } - /** - * Convert whitespace trivia disguised as comment trivia. - * @param node Node to do conversion on. - */ - private void applyWhitespaceConversion(final AstNode node) { - final Token token = node.getToken(); - if (token != null) { - final List tokenTrivia = token.getTrivia(); - - tokenTrivia.replaceAll(trivia -> { - final List triviaTokens = trivia.getTokens(); - final Token triviaToken = triviaTokens.get(0); - final String triviaTokenValue = triviaToken.getOriginalValue(); - if (triviaTokenValue.startsWith("#")) { - // Comment, do nothing - return trivia; - } else if (triviaTokenValue.matches("\r|\n|\r\n")) { - // Newline. - final Token newToken = Token.builder(triviaToken) - .setType(GenericTokenType.EOL) - .build(); - return Trivia.createSkippedText(newToken); - } else if (triviaTokenValue.matches("[\\t\\v\\f\\u0020\\u00A0\\uFEFF]+")) { // Whitespace. - final Token newToken = Token.builder(triviaToken) - .setType(ExtendedTokenType.WHITESPACE) - .build(); - return Trivia.createSkippedText(newToken); - } else if (triviaTokenValue.equals(MagikPunctuator.SEMICOLON.getValue())) { - final Token newToken = Token.builder(triviaToken) - .setType(ExtendedTokenType.STATEMENT_SEPARATOR) - .build(); - return Trivia.createSkippedText(newToken); - } - - LOGGER.warn("Unknown triva token: " + triviaToken); - return null; - }); - } - - node.getChildren().forEach(this::applyWhitespaceConversion); - } - /** * Recusrively update URI for AstNode/Tokens. * @param node Node to start at. diff --git a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/NewDocParser.java b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/NewDocParser.java index 821a3e89..bf533d58 100644 --- a/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/NewDocParser.java +++ b/magik-squid/src/main/java/nl/ramsolutions/sw/magik/parser/NewDocParser.java @@ -1,24 +1,19 @@ package nl.ramsolutions.sw.magik.parser; import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.GenericTokenType; -import com.sonar.sslr.api.RecognitionException; import com.sonar.sslr.api.Token; import com.sonar.sslr.api.Trivia; import com.sonar.sslr.impl.Parser; import java.lang.reflect.Field; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.analysis.AstQuery; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.api.NewDocGrammar; import org.slf4j.Logger; @@ -63,13 +58,14 @@ public class NewDocParser { private static final Logger LOGGER = LoggerFactory.getLogger(NewDocParser.class); - private final Parser parser = new ParserAdapter<>(StandardCharsets.UTF_8, NewDocGrammar.create()); + private final Parser parser = + new ParserAdapter<>(StandardCharsets.ISO_8859_1, NewDocGrammar.create()); private final List tokens; private AstNode newDocNode; /** * Constructor. - * @param node {{AstNode}} to analyze. + * @param node {@link AstNode} to analyze. */ public NewDocParser(final AstNode node) { this(NewDocParser.getCommentTokens(node)); @@ -82,8 +78,9 @@ public NewDocParser(final List docTokens) { private static List getCommentTokens(final AstNode node) { final Predicate predicate = - predNode -> node == predNode - || predNode.isNot(MagikGrammar.PROCEDURE_DEFINITION); + predNode -> + node == predNode + || predNode.isNot(MagikGrammar.PROCEDURE_DEFINITION); return AstQuery.dfs(node, predicate) .map(AstNode::getToken) .filter(Objects::nonNull) @@ -95,17 +92,12 @@ private static List getCommentTokens(final AstNode node) { .collect(Collectors.toUnmodifiableList()); } - private AstNode parseNewDocSafely() { - try { - return this.parseNewDoc(); - } catch (RecognitionException exception) { - LOGGER.debug(exception.getMessage(), exception); - return this.buildSyntaxErrorNode(exception); - } - } - @SuppressWarnings("java:S3011") private AstNode parseNewDoc() { + // TODO: Can't we convert to string first, and replace all before with spaces/tabs? + // Perhaps use MagikFile for this? + // Atleast then we don't have to update token lines/columns. + // Build comment. final String comments = this.tokens.stream() .map(Token::getValue) @@ -142,107 +134,46 @@ private AstNode parseNewDoc() { return node; } - private AstNode buildSyntaxErrorNode(RecognitionException recognitionException) { - // Build comment. - final String comments = this.tokens.stream() - .map(Token::getOriginalValue) - .collect(Collectors.joining("\n")); - - int line = recognitionException.getLine(); - final String[] lines = comments.split("\n"); - if (lines.length <= line) { - line = lines.length; - } - - // parse message, as the exception doesn't provide the raw value - final String message = recognitionException.getMessage(); - final Pattern pattern = Pattern.compile("Parse error at line (\\d+) column (\\d+):.*"); - final Matcher matcher = pattern.matcher(message); - if (!matcher.find()) { - throw new IllegalStateException("Unrecognized RecognitionException message"); - } - final String columnStr = matcher.group(2); - int column = Integer.parseInt(columnStr) - 1; - final String offendingLineStr = lines[line - 1]; - if (column >= offendingLineStr.length()) { - // Don't break things! - column = offendingLineStr.length() - 1; - } - - final URI uri = URI.create("magik://syntax_error"); // This is later updated. - final Token syntaxErrorToken = Token.builder() - .setValueAndOriginalValue(comments) - .setURI(uri) - .setLine(line) - .setColumn(column) - .setType(ExtendedTokenType.SYNTAX_ERROR) - .build(); - - final AstNode dummyNode = new AstNode(MagikGrammar.MAGIK, "NEW_DOC", null); - - final AstNode errorNode = new AstNode(MagikGrammar.SYNTAX_ERROR, "SYNTAX_ERROR", syntaxErrorToken); - dummyNode.addChild(errorNode); - - int eofLine = lines.length; - int eofColumn = 0; - if (comments.endsWith("\n")) { - eofLine += 1; - } else { - eofColumn = lines[lines.length - 1].length(); - } - final Token eofToken = Token.builder() - .setValueAndOriginalValue("") - .setURI(uri) - .setLine(eofLine) - .setColumn(eofColumn) - .setType(ExtendedTokenType.SYNTAX_ERROR) - .build(); - final AstNode eofNode = new AstNode(GenericTokenType.EOF, "EOF", eofToken); - dummyNode.addChild(eofNode); - - return dummyNode; - } - /** * Get NewDoc AstNode. * @return Parsed AstNode. */ public AstNode getNewDocNode() { if (this.newDocNode == null) { - this.newDocNode = this.parseNewDocSafely(); + this.newDocNode = this.parseNewDoc(); } return this.newDocNode; } /** - * Get @param types. - * @return Map with @param types, keyed on @param name, values on @param type. + * Get param types. + * @return Map with @param types, keyed on name, valued on type. */ - public Map getParameterTypes() { + public Map getParameterTypes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.PARAM).stream() .filter(this::noEmptyName) .collect(Collectors.toMap( this::getName, - this::getTypeName)); + this::getTypeString)); } /** - * Get @param nodes + @param type names. + * Get @param nodes + type strings. * @return */ - public Map getParameterTypeNodes() { + public Map getParameterTypeNodes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.PARAM).stream() .filter(this::hasTypeNode) .collect(Collectors.toMap( this::getTypeNode, - this::getTypeName)); + this::getTypeString)); } /** - * Get parameter name node + names. + * Get @param name node + names. * @return */ public Map getParameterNameNodes() { @@ -258,34 +189,34 @@ public Map getParameterNameNodes() { * Get @return types. * @return List with @return types. */ - public List getReturnTypes() { + public List getReturnTypes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.RETURN).stream() - .map(this::getTypeName) + .map(this::getTypeString) .collect(Collectors.toList()); } /** - * Get @return type nodes + names. + * Get return type nodes + names. * @return Map with @return type nodes + type names. */ - public Map getReturnTypeNodes() { + public Map getReturnTypeNodes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.RETURN).stream() .filter(this::hasTypeNode) .collect(Collectors.toMap( this::getTypeNode, - this::getTypeName)); + this::getTypeString)); } /** * Get @loop types. * @return List with @loop types. */ - public List getLoopTypes() { + public List getLoopTypes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.LOOP).stream() - .map(this::getTypeName) + .map(this::getTypeString) .collect(Collectors.toList()); } @@ -293,43 +224,43 @@ public List getLoopTypes() { * Get @loop type nodes + names. * @return List with @loop type nodes + type names. */ - public Map getLoopTypeNodes() { + public Map getLoopTypeNodes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.LOOP).stream() .filter(this::hasTypeNode) .collect(Collectors.toMap( this::getTypeNode, - this::getTypeName)); + this::getTypeString)); } /** - * Get slot types. - * @return Map with slot types, keyed on slot name, values on slot type. + * Get @slot types. + * @return Map with @slot types, keyed on name, valued on type. */ - public Map getSlotTypes() { + public Map getSlotTypes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.SLOT).stream() .filter(this::noEmptyName) .collect(Collectors.toMap( this::getName, - this::getTypeName)); + this::getTypeString)); } /** - * Get slot type nodes + name. + * Get @slot type nodes + types. * @return */ - public Map getSlotTypeNodes() { + public Map getSlotTypeNodes() { final AstNode node = this.getNewDocNode(); return node.getChildren(NewDocGrammar.SLOT).stream() .filter(this::hasTypeNode) .collect(Collectors.toMap( this::getTypeNode, - this::getTypeName)); + this::getTypeString)); } /** - * Get slot name nodes + name. + * Get @slot name nodes + name. * @return */ public Map getSlotNameNodes() { @@ -359,17 +290,18 @@ private boolean hasTypeNode(final AstNode node) { return node.getFirstChild(NewDocGrammar.TYPE) != null; } - private String getTypeName(final AstNode node) { + private TypeString getTypeString(final AstNode node) { final AstNode typeNode = node.getFirstChild(NewDocGrammar.TYPE); if (typeNode == null) { - return ""; + return TypeString.UNDEFINED; } - return typeNode.getTokens().stream() + final String value = typeNode.getTokens().stream() .filter(token -> !token.getValue().equals("{")) .filter(token -> !token.getValue().equals("}")) .map(Token::getValue) .collect(Collectors.joining()); + return TypeString.of(value); } private AstNode getTypeNode(final AstNode node) { diff --git a/magik-squid/src/main/java/org/sonar/sslr/internal/matchers/AstCreator.java b/magik-squid/src/main/java/org/sonar/sslr/internal/matchers/AstCreator.java new file mode 100644 index 00000000..1de5864a --- /dev/null +++ b/magik-squid/src/main/java/org/sonar/sslr/internal/matchers/AstCreator.java @@ -0,0 +1,210 @@ +/* + * SonarSource Language Recognizer + * Copyright (C) 2010-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.sslr.internal.matchers; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.GenericTokenType; +import com.sonar.sslr.api.Token; +import com.sonar.sslr.api.TokenType; +import com.sonar.sslr.api.Trivia; +import com.sonar.sslr.api.Trivia.TriviaKind; +import org.sonar.sslr.internal.grammar.MutableParsingRule; +import org.sonar.sslr.internal.vm.TokenExpression; +import org.sonar.sslr.internal.vm.TriviaExpression; +import org.sonar.sslr.parser.ParsingResult; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class AstCreator { + + private static final URI FAKE_URI; + + static { + try { + FAKE_URI = new URI("tests://unittest"); + } catch (URISyntaxException e) { + // Can't happen + throw new IllegalStateException(e); + } + } + + private final LocatedText input; + private final Token.Builder tokenBuilder = Token.builder(); + private final List trivias = new ArrayList<>(); + + public static AstNode create(ParsingResult parsingResult, LocatedText input) { + AstNode astNode = new AstCreator(input).visit(parsingResult.getParseTreeRoot()); + // Unwrap AstNodeType for root node: + astNode.hasToBeSkippedFromAst(); + return astNode; + } + + private AstCreator(LocatedText input) { + this.input = input; + } + + private AstNode visit(ParseNode node) { + if (node.getMatcher() instanceof MutableParsingRule) { + return visitNonTerminal(node); + } else { + return visitTerminal(node); + } + } + + private AstNode visitTerminal(ParseNode node) { + if (node.getMatcher() instanceof TriviaExpression) { + TriviaExpression ruleMatcher = (TriviaExpression) node.getMatcher(); + if (ruleMatcher.getTriviaKind() == TriviaKind.SKIPPED_TEXT) { + handleSkippedTextTrivia(node); + return null; + } else if (ruleMatcher.getTriviaKind() == TriviaKind.COMMENT) { + updateTokenPositionAndValue(node); + tokenBuilder.setTrivia(Collections.emptyList()); + tokenBuilder.setType(GenericTokenType.COMMENT); + trivias.add(Trivia.createComment(tokenBuilder.build())); + return null; + } else { + throw new IllegalStateException("Unexpected trivia kind: " + ruleMatcher.getTriviaKind()); + } + } else if (node.getMatcher() instanceof TokenExpression) { + updateTokenPositionAndValue(node); + TokenExpression ruleMatcher = (TokenExpression) node.getMatcher(); + tokenBuilder.setType(ruleMatcher.getTokenType()); + if (ruleMatcher.getTokenType() == GenericTokenType.COMMENT) { + tokenBuilder.setTrivia(Collections.emptyList()); + trivias.add(Trivia.createComment(tokenBuilder.build())); + return null; + } + } else { + updateTokenPositionAndValue(node); + tokenBuilder.setType(UNDEFINED_TOKEN_TYPE); + } + Token token = tokenBuilder.setTrivia(trivias).build(); + trivias.clear(); + AstNode astNode = new AstNode(token); + astNode.setFromIndex(node.getStartIndex()); + astNode.setToIndex(node.getEndIndex()); + return astNode; + } + + private void updateTokenPositionAndValue(ParseNode node) { + TextLocation location = input.getLocation(node.getStartIndex()); + if (location == null) { + tokenBuilder.setGeneratedCode(true); + // Godin: line, column and uri has no value for generated code, but we should bypass checks in TokenBuilder + tokenBuilder.setLine(1); + tokenBuilder.setColumn(0); + tokenBuilder.setURI(FAKE_URI); + } else { + tokenBuilder.setGeneratedCode(false); + tokenBuilder.setLine(location.getLine()); + tokenBuilder.setColumn(location.getColumn() - 1); + tokenBuilder.setURI(location.getFileURI() == null ? FAKE_URI : location.getFileURI()); + tokenBuilder.notCopyBook(); + } + + String value = getValue(node); + tokenBuilder.setValueAndOriginalValue(value); + } + + private AstNode visitNonTerminal(ParseNode node) { + MutableParsingRule ruleMatcher = (MutableParsingRule) node.getMatcher(); + List astNodes = new ArrayList<>(); + for (ParseNode child : node.getChildren()) { + AstNode astNode = visit(child); + if (astNode != null) { + if (astNode.hasToBeSkippedFromAst()) { + astNodes.addAll(astNode.getChildren()); + } else { + astNodes.add(astNode); + } + } + } + + Token token = null; + for (AstNode child : astNodes) { + if (child.getToken() != null) { + token = child.getToken(); + break; + } + } + + AstNode astNode = new AstNode(ruleMatcher, ruleMatcher.getName(), token); + for (AstNode child : astNodes) { + astNode.addChild(child); + } + astNode.setFromIndex(node.getStartIndex()); + astNode.setToIndex(node.getEndIndex()); + return astNode; + } + + private String getValue(ParseNode node) { + StringBuilder result = new StringBuilder(); + for (int i = node.getStartIndex(); i < Math.min(node.getEndIndex(), input.length()); i++) { + result.append(input.charAt(i)); + } + return result.toString(); + } + + // @VisibleForTesting + static final TokenType UNDEFINED_TOKEN_TYPE = new TokenType() { + @Override + public String getName() { + return "TOKEN"; + } + + @Override + public String getValue() { + return getName(); + } + + @Override + public boolean hasToBeSkippedFromAst(AstNode node) { + return false; + } + }; + + /** + * Hack to do store skipped text as trivia. + * @param node Parse node. + */ + private void handleSkippedTextTrivia(ParseNode node) { + updateTokenPositionAndValue(node); + tokenBuilder.setTrivia(Collections.emptyList()); + + final String nodeValue = getValue(node); + final GenericTokenType tokenType; + if (nodeValue.startsWith("\r") || nodeValue.startsWith("\n")) { + tokenType = GenericTokenType.EOL; + } else if (nodeValue.equals(";")) { + tokenType = GenericTokenType.STATEMENT_SEPARATOR; + } else { + tokenType = GenericTokenType.WHITESPACE; + } + + tokenBuilder.setType(tokenType); + trivias.add(Trivia.createSkippedText(tokenBuilder.build())); + } + +} diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReaderTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReaderTest.java index c2dc1af8..8e682494 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReaderTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/definitions/DefinitionReaderTest.java @@ -2,6 +2,7 @@ import com.sonar.sslr.api.AstNode; import java.util.List; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.parser.MagikParser; import org.junit.jupiter.api.Test; @@ -55,7 +56,7 @@ void testPackageDefintionUses() { @Test void testEnumeratorDefintion() { - final String code = "def_enumeration(:test_enum, _false, :a)"; + final String code = "sw:def_enumeration(:test_enum, _false, :a)"; final AstNode node = this.parseCode(code); final DefinitionReader reader = new DefinitionReader(); reader.walkAst(node); @@ -144,7 +145,7 @@ void testDefSlottedExemplarParentsSingle() { final SlottedExemplarDefinition slottedExemplarDef = (SlottedExemplarDefinition) definitions.get(0); assertThat(slottedExemplarDef.getName()).isEqualTo("test_exemplar"); assertThat(slottedExemplarDef.getNode()).isEqualTo(node.getFirstDescendant(MagikGrammar.STATEMENT)); - assertThat(slottedExemplarDef.getParents()).containsExactly("sw:rope"); + assertThat(slottedExemplarDef.getParents()).containsExactly(TypeString.of("sw:rope")); } @Test @@ -161,12 +162,14 @@ void testDefSlottedExemplarParentsMultiple() { final SlottedExemplarDefinition slottedExemplarDef = (SlottedExemplarDefinition) definitions.get(0); assertThat(slottedExemplarDef.getName()).isEqualTo("test_exemplar"); assertThat(slottedExemplarDef.getNode()).isEqualTo(node.getFirstDescendant(MagikGrammar.STATEMENT)); - assertThat(slottedExemplarDef.getParents()).containsExactly("mixin1", "rope"); + assertThat(slottedExemplarDef.getParents()).containsExactly( + TypeString.of("user:mixin1"), + TypeString.of("user:rope")); } @Test void testDefIndexeExemplar() { - final String code = "def_indexed_exemplar(:test_exemplar, {}, {:mixin1, :integer})"; + final String code = "def_indexed_exemplar(:test_exemplar, _unset, {:mixin1, :integer})"; final AstNode node = this.parseCode(code); final DefinitionReader reader = new DefinitionReader(); reader.walkAst(node); @@ -176,8 +179,10 @@ void testDefIndexeExemplar() { assertThat(definitions.get(0)).isInstanceOf(IndexedExemplarDefinition.class); final IndexedExemplarDefinition indexedExemplarDef = (IndexedExemplarDefinition) definitions.get(0); - assertThat(indexedExemplarDef.getName()).isEqualTo("test_exemplar"); - assertThat(indexedExemplarDef.getParents()).containsExactly("mixin1", "integer"); + assertThat(indexedExemplarDef.getTypeString()).isEqualTo(TypeString.of("user:test_exemplar")); + assertThat(indexedExemplarDef.getParents()).containsExactly( + TypeString.of("user:mixin1"), + TypeString.of("user:integer")); } @Test @@ -192,8 +197,8 @@ void testDefMixin() { assertThat(definitions.get(0)).isInstanceOf(MixinDefinition.class); final MixinDefinition mixinDef = (MixinDefinition) definitions.get(0); - assertThat(mixinDef.getName()).isEqualTo("test_mixin"); - assertThat(mixinDef.getParents()).containsExactly("mixin1"); + assertThat(mixinDef.getTypeString()).isEqualTo(TypeString.of("user:test_mixin")); + assertThat(mixinDef.getParents()).containsExactly(TypeString.of("user:mixin1")); } @Test @@ -208,10 +213,10 @@ void testDefineBinaryOperatorCase() { assertThat(definitions.get(0)).isInstanceOf(BinaryOperatorDefinition.class); final BinaryOperatorDefinition operatorDefinition = (BinaryOperatorDefinition) definitions.get(0); - assertThat(operatorDefinition.getName()).isEqualTo("integer > float"); + assertThat(operatorDefinition.getName()).isEqualTo("user:integer > user:float"); assertThat(operatorDefinition.getOperator()).isEqualTo(">"); - assertThat(operatorDefinition.getLhs()).isEqualTo("integer"); - assertThat(operatorDefinition.getRhs()).isEqualTo("float"); + assertThat(operatorDefinition.getLhs()).isEqualTo(TypeString.of("user:integer")); + assertThat(operatorDefinition.getRhs()).isEqualTo(TypeString.of("user:float")); } @Test @@ -319,6 +324,69 @@ void testDefineSlotAccess() { assertThat(methodDef.getParameters()).isEmpty(); } + @Test + void testDefineSlotExternallyReadable() { + final String code = "test_exemplar.define_slot_externally_readable(:slot1)"; + final AstNode node = this.parseCode(code); + final DefinitionReader reader = new DefinitionReader(); + reader.walkAst(node); + + final List definitions = reader.getDefinitions(); + assertThat(definitions).hasSize(1); + + assertThat(definitions.get(0)).isInstanceOf(MethodDefinition.class); + final MethodDefinition methodDef = (MethodDefinition) definitions.get(0); + assertThat(methodDef.getName()).isEqualTo("test_exemplar.slot1"); + assertThat(methodDef.getModifiers()).isEmpty(); + assertThat(methodDef.getParameters()).isEmpty(); + } + + @Test + void testDefineSlotExternallyReadablePublic() { + final String code = "test_exemplar.define_slot_externally_readable(:slot1, :public)"; + final AstNode node = this.parseCode(code); + final DefinitionReader reader = new DefinitionReader(); + reader.walkAst(node); + + final List definitions = reader.getDefinitions(); + assertThat(definitions).hasSize(1); + + assertThat(definitions.get(0)).isInstanceOf(MethodDefinition.class); + final MethodDefinition methodDef = (MethodDefinition) definitions.get(0); + assertThat(methodDef.getName()).isEqualTo("test_exemplar.slot1"); + assertThat(methodDef.getModifiers()).isEmpty(); + assertThat(methodDef.getParameters()).isEmpty(); + } + + @Test + void testDefineSlotExternallyWritable() { + final String code = "test_exemplar.define_slot_externally_writable(:slot1, :public)"; + final AstNode node = this.parseCode(code); + final DefinitionReader reader = new DefinitionReader(); + reader.walkAst(node); + + final List definitions = reader.getDefinitions(); + assertThat(definitions).hasSize(3); + + assertThat(definitions.get(0)).isInstanceOf(MethodDefinition.class); + final MethodDefinition getMethodDef = (MethodDefinition) definitions.get(0); + assertThat(getMethodDef.getName()).isEqualTo("test_exemplar.slot1"); + assertThat(getMethodDef.getModifiers()).isEmpty(); + assertThat(getMethodDef.getParameters()).isEmpty(); + + assertThat(definitions.get(1)).isInstanceOf(MethodDefinition.class); + final MethodDefinition setMethodDef = (MethodDefinition) definitions.get(1); + assertThat(setMethodDef.getName()).isEqualTo("test_exemplar.slot1<<"); + assertThat(setMethodDef.getModifiers()).isEmpty(); + assertThat(setMethodDef.getParameters()).isEmpty(); + + assertThat(definitions.get(0)).isInstanceOf(MethodDefinition.class); + final MethodDefinition bootMethodDef = (MethodDefinition) definitions.get(2); + assertThat(bootMethodDef.getName()).isEqualTo("test_exemplar.slot1^<<"); + assertThat(bootMethodDef.getModifiers()).isEmpty(); + assertThat(bootMethodDef.getParameters()).isEmpty(); + } + @Test void testDefineSharedVariable() { final String code = "test_exemplar.define_shared_variable(:var1, 1, :readonly)"; @@ -387,4 +455,43 @@ void testAssignGlobal() { assertThat(globalDef.getName()).isEqualTo("g"); } + @Test + void testParseCondition() { + final String code = "condition.define_condition(:cond1, :information, {:data1, :data2})"; + final AstNode node = this.parseCode(code); + final DefinitionReader reader = new DefinitionReader(); + reader.walkAst(node); + + final List definitions = reader.getDefinitions(); + assertThat(definitions).hasSize(1); + + assertThat(definitions.get(0)).isInstanceOf(ConditionDefinition.class); + final ConditionDefinition conditionDef = (ConditionDefinition) definitions.get(0); + assertThat(conditionDef.getName()).isEqualTo("cond1"); + assertThat(conditionDef.getParent()).isEqualTo("information"); + assertThat(conditionDef.getDataNames()).containsOnly( + "data1", "data2"); + } + + @Test + void testParseConditionInBlock() { + final String code = "" + + "_block\n" + + " condition.define_condition(:cond1, :information, {:data1, :data2})\n" + + "_endblock"; + final AstNode node = this.parseCode(code); + final DefinitionReader reader = new DefinitionReader(); + reader.walkAst(node); + + final List definitions = reader.getDefinitions(); + assertThat(definitions).hasSize(1); + + assertThat(definitions.get(0)).isInstanceOf(ConditionDefinition.class); + final ConditionDefinition conditionDef = (ConditionDefinition) definitions.get(0); + assertThat(conditionDef.getName()).isEqualTo("cond1"); + assertThat(conditionDef.getParent()).isEqualTo("information"); + assertThat(conditionDef.getDataNames()).containsOnly( + "data1", "data2"); + } + } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitorTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitorTest.java index 720cfe3d..052f50bf 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitorTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/scope/ScopeBuilderVisitorTest.java @@ -407,4 +407,64 @@ void testTopLevelProcImport() { assertThat(entryTracebackShowArgs.getUsages()).isEmpty(); } + @Test + void testHidingScopeEntryLocal() { + final String code = "" + + "_method a.b\n" + + " _local x << 10\n" + + " _block\n" + + " _local x << 10\n" + + " show(x)\n" + + " _endblock\n" + + "_endmethod"; + final ScopeBuilderVisitor visitor = this.buildCode(code); + final Scope globalScope = visitor.getGlobalScope(); + final Scope bodyScope = globalScope.getSelfAndDescendantScopes().get(1); + final Scope loopScope = globalScope.getSelfAndDescendantScopes().get(2); + + final ScopeEntry bodyXEntry = bodyScope.getScopeEntry("x"); + final ScopeEntry loopXEntry = loopScope.getScopeEntry("x"); + assertThat(bodyXEntry).isNotSameAs(loopXEntry); + } + + @Test + void testNotHidingScopeEntryDefinition() { + final String code = "" + + "_method a.b\n" + + " x << 10\n" + + " _block\n" + + " x << 10\n" + + " show(x)\n" + + " _endblock\n" + + "_endmethod"; + final ScopeBuilderVisitor visitor = this.buildCode(code); + final Scope globalScope = visitor.getGlobalScope(); + final Scope bodyScope = globalScope.getSelfAndDescendantScopes().get(1); + final Scope loopScope = globalScope.getSelfAndDescendantScopes().get(2); + + final ScopeEntry bodyXEntry = bodyScope.getScopeEntry("x"); + final ScopeEntry loopXEntry = loopScope.getScopeEntry("x"); + assertThat(bodyXEntry).isEqualTo(loopXEntry); + } + + @Test + void testNotHidingScopeEntryDefinitionMulti() { + final String code = "" + + "_method a.b\n" + + " (x, y) << (10, 20)\n" + + " _block\n" + + " x << 10\n" + + " show(x)\n" + + " _endblock\n" + + "_endmethod"; + final ScopeBuilderVisitor visitor = this.buildCode(code); + final Scope globalScope = visitor.getGlobalScope(); + final Scope bodyScope = globalScope.getSelfAndDescendantScopes().get(1); + final Scope loopScope = globalScope.getSelfAndDescendantScopes().get(2); + + final ScopeEntry bodyXEntry = bodyScope.getScopeEntry("x"); + final ScopeEntry loopXEntry = loopScope.getScopeEntry("x"); + assertThat(bodyXEntry).isEqualTo(loopXEntry); + } + } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasonerTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasonerTest.java index af05b83f..4d08e13f 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasonerTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/LocalTypeReasonerTest.java @@ -6,17 +6,19 @@ import java.util.Collection; import java.util.Collections; import java.util.EnumSet; +import java.util.List; import nl.ramsolutions.sw.magik.MagikTypedFile; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IndexedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; +import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; import nl.ramsolutions.sw.magik.analysis.typing.types.ProcedureInstance; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; -import nl.ramsolutions.sw.magik.analysis.typing.types.SlottedType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import org.junit.jupiter.api.Test; @@ -35,7 +37,29 @@ private MagikTypedFile createMagikFile(String code, ITypeKeeper typeKeeper) { } @Test - void testReasonMethodReturn() { + void testReasonMethodReturnNone() { + final String code = "" + + "_package sw\n" + + "_method object.test\n" + + " _return\n" + + "_endmethod\n"; + + // Set up TypeKeeper/TypeReasoner. + final TypeKeeper typeKeeper = new TypeKeeper(); + + // Do analysis. + final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); + final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + + // Assert user:object.test type determined. + final AstNode topNode = magikFile.getTopNode(); + final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); + final ExpressionResult result = reasoner.getNodeType(methodNode); + assertThat(result.isEmpty()).isTrue(); + } + + @Test + void testReasonMethodReturnRope() { final String code = "" + "_package sw\n" + "_method object.test\n" @@ -44,15 +68,17 @@ void testReasonMethodReturn() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType ropeType = new SlottedType(GlobalReference.of("sw:rope")); + final TypeString ropeRef = TypeString.of("sw:rope"); + final MagikType ropeType = new MagikType(typeKeeper, Sort.SLOTTED, ropeRef); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "new()", Collections.emptyList(), null, - new ExpressionResult(ropeType)); - typeKeeper.addType(ropeType); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -64,9 +90,10 @@ void testReasonMethodReturn() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:rope"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(ropeType); } @Test @@ -91,9 +118,11 @@ void testReasonMethodMultiReturnSame() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); + final AbstractType integerType = typeKeeper.getType(TypeString.of("sw:integer")); final AbstractType resultType = (AbstractType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -120,9 +149,13 @@ void testReasonMethodMultiReturnDifferent() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); + final AbstractType combinedType = new CombinedType( + typeKeeper.getType(TypeString.of("sw:integer")), + typeKeeper.getType(TypeString.of("sw:symbol"))); final AbstractType resultType = (AbstractType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer|sw:symbol"); + assertThat(resultType) + .isNotNull() + .isEqualTo(combinedType); } @Test @@ -146,8 +179,9 @@ void testReasonMethodSelf() { assertThat(result.size()).isEqualTo(1); final SelfType resultType = (SelfType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo(SelfType.SERIALIZED_NAME); + assertThat(resultType) + .isNotNull() + .isEqualTo(SelfType.INSTANCE); } @Test @@ -161,22 +195,26 @@ void testReasonVariable() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType ropeType = new SlottedType(GlobalReference.of("sw:rope")); + final TypeString ropeRef = TypeString.of("sw:rope"); + final MagikType ropeType = new MagikType(typeKeeper, Sort.SLOTTED, ropeRef); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "new()", Collections.emptyList(), null, - new ExpressionResult(ropeType)); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "add()", Collections.emptyList(), null, - new ExpressionResult()); - typeKeeper.addType(ropeType); + null, + new ExpressionResultString(), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -188,9 +226,10 @@ void testReasonVariable() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:rope"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(ropeType); } @Test @@ -204,22 +243,26 @@ void testReasonVariable2() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType ropeType = new SlottedType(GlobalReference.of("sw:rope")); + final TypeString ropeRef = TypeString.of("sw:rope"); + final MagikType ropeType = new MagikType(typeKeeper, Sort.SLOTTED, ropeRef); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "new()", Collections.emptyList(), null, - new ExpressionResult(ropeType)); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "add()", Collections.emptyList(), null, - new ExpressionResult()); - typeKeeper.addType(ropeType); + null, + new ExpressionResultString(), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -231,9 +274,10 @@ void testReasonVariable2() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:rope"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(ropeType); } @Test @@ -245,9 +289,17 @@ void testUnaryOperator() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final AbstractType falseType = typeKeeper.getType(GlobalReference.of("sw:false")); - final UnaryOperator unaryOp = new UnaryOperator(UnaryOperator.Operator.valueFor("_not"), falseType, falseType); - typeKeeper.addUnaryOperator(unaryOp); + final TypeString falseRef = TypeString.of("sw:false"); + final MagikType falseType = (MagikType) typeKeeper.getType(falseRef); + falseType.addMethod( + null, + EnumSet.noneOf(Method.Modifier.class), + "not", + Collections.emptyList(), + null, + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -259,9 +311,10 @@ void testUnaryOperator() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:false"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(falseType); } @Test @@ -273,9 +326,9 @@ void testBinaryOperator() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final AbstractType char16VectorType = typeKeeper.getType(GlobalReference.of("sw:char16_vector")); + final TypeString char16VectorRef = TypeString.of("sw:char16_vector"); final BinaryOperator binOp = new BinaryOperator( - BinaryOperator.Operator.valueFor("+"), char16VectorType, char16VectorType, char16VectorType); + BinaryOperator.Operator.valueFor("+"), char16VectorRef, char16VectorRef, char16VectorRef); typeKeeper.addBinaryOperator(binOp); // Do analysis. @@ -288,9 +341,11 @@ void testBinaryOperator() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:char16_vector"); + final AbstractType char16VectorType = typeKeeper.getType(char16VectorRef); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(char16VectorType); } @Test @@ -302,14 +357,14 @@ void testBinaryOperatorChained() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final AbstractType char16VectorType = typeKeeper.getType(GlobalReference.of("sw:char16_vector")); - final AbstractType symbolType = typeKeeper.getType(GlobalReference.of("sw:symbol")); - final AbstractType integerType = typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString char16VectorRef = TypeString.of("sw:char16_vector"); + final TypeString integerRef = TypeString.of("sw:integer"); + final TypeString symbolRef = TypeString.of("sw:symbol"); final BinaryOperator binOp1 = - new BinaryOperator(BinaryOperator.Operator.valueFor("+"), integerType, symbolType, symbolType); + new BinaryOperator(BinaryOperator.Operator.valueFor("+"), integerRef, symbolRef, symbolRef); typeKeeper.addBinaryOperator(binOp1); final BinaryOperator binOp2 = - new BinaryOperator(BinaryOperator.Operator.valueFor("+"), symbolType, char16VectorType, char16VectorType); + new BinaryOperator(BinaryOperator.Operator.valueFor("+"), symbolRef, char16VectorRef, char16VectorRef); typeKeeper.addBinaryOperator(binOp2); // Do analysis. @@ -322,9 +377,11 @@ void testBinaryOperatorChained() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:char16_vector"); + final AbstractType char16VectorType = typeKeeper.getType(char16VectorRef); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(char16VectorType); } @Test @@ -349,17 +406,22 @@ void testEmitStatement() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(3); - final MagikType resultType1 = (MagikType) result.get(0, null); - assertThat(resultType1).isNotNull(); - assertThat(resultType1.getFullName()).isEqualTo("sw:integer"); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); + final AbstractType resultType1 = result.get(0, null); + assertThat(resultType1) + .isNotNull() + .isEqualTo(integerType); - final MagikType resultType2 = (MagikType) result.get(1, null); - assertThat(resultType2).isNotNull(); - assertThat(resultType2.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType2 = result.get(1, null); + assertThat(resultType2) + .isNotNull() + .isEqualTo(integerType); - final MagikType resultType3 = (MagikType) result.get(2, null); - assertThat(resultType3).isNotNull(); - assertThat(resultType3.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType3 = result.get(2, null); + assertThat(resultType3) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -388,9 +450,12 @@ void testIfElseStatement() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -405,15 +470,17 @@ void testLoopStatement() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "upto()", Collections.emptyList(), null, - new ExpressionResult(), - new ExpressionResult(integerType)); + null, + new ExpressionResultString(), + new ExpressionResultString(integerRef)); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -425,9 +492,10 @@ void testLoopStatement() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -442,15 +510,17 @@ void testLoopStatementUseIterator() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "upto()", Collections.emptyList(), null, - new ExpressionResult(), - new ExpressionResult(integerType)); + null, + new ExpressionResultString(), + new ExpressionResultString(integerRef)); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -462,9 +532,10 @@ void testLoopStatementUseIterator() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -482,15 +553,17 @@ void testLoopResultOptional() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "upto()", Collections.emptyList(), null, - new ExpressionResult(), - new ExpressionResult(integerType)); + null, + new ExpressionResultString(), + new ExpressionResultString(integerRef)); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -502,9 +575,13 @@ void testLoopResultOptional() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); + final CombinedType expectedType = new CombinedType( + typeKeeper.getType(TypeString.of("sw:integer")), + typeKeeper.getType(TypeString.of("sw:unset"))); final CombinedType resultType = (CombinedType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer|sw:unset"); + assertThat(resultType) + .isNotNull() + .isEqualTo(expectedType); } @Test @@ -528,8 +605,9 @@ void testUnknownMethodCall() { assertThat(result).isEqualTo(ExpressionResult.UNDEFINED); final UndefinedType resultType = (UndefinedType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo(UndefinedType.SERIALIZED_NAME); + assertThat(resultType) + .isNotNull() + .isEqualTo(UndefinedType.INSTANCE); } @Test @@ -541,10 +619,8 @@ void testReasonMethodReturnProc() { + " _endproc\n" + "_endmethod\n"; - // Set up TypeKeeper/TypeReasoner. - final TypeKeeper typeKeeper = new TypeKeeper(); - // Do analysis. + final TypeKeeper typeKeeper = new TypeKeeper(); final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); @@ -558,15 +634,17 @@ void testReasonMethodReturnProc() { assertThat(resultType).isNotNull(); assertThat(resultType.getProcedureName()).isEqualTo("test"); - final Collection procMethods = resultType.getMethods("invoke()"); - assertThat(procMethods).isNotEmpty(); - procMethods.forEach(procMethod -> { - final ExpressionResult procResult = procMethod.getCallResult(); + final Collection invokeMethods = resultType.getMethods("invoke()"); + assertThat(invokeMethods).isNotEmpty(); + final TypeString integerRef = TypeString.of("sw:integer"); + invokeMethods.forEach(procMethod -> { + final ExpressionResultString procResult = procMethod.getCallResult(); assertThat(procResult.size()).isEqualTo(1); - final MagikType procResultType = (MagikType) procResult.get(0, null); - assertThat(procResultType).isNotNull(); - assertThat(procResultType.getFullName()).isEqualTo("sw:integer"); + final TypeString procResultTypeString = procResult.get(0, null); + assertThat(procResultTypeString) + .isNotNull() + .isEqualTo(integerRef); }); } @@ -574,7 +652,7 @@ void testReasonMethodReturnProc() { void testMultipleAssignmentStatement() { final String code = "" + "_method object.test\n" - + " (a, b) << (2, 3)\n" + + " (a, b) << (:a, 3)\n" + " _return b\n" + "_endmethod\n"; @@ -591,9 +669,11 @@ void testMultipleAssignmentStatement() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType = result.get(0, null); + final AbstractType integerType = typeKeeper.getType(TypeString.of("sw:integer")); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -607,15 +687,17 @@ void testAssignmentMethod() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType propertyListType = new IndexedType(GlobalReference.of("sw:property_list")); + final MagikType propertyListType = + new MagikType(typeKeeper, Sort.INDEXED, TypeString.of("sw:property_list")); propertyListType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "new()", Collections.emptyList(), null, - new ExpressionResult(SelfType.INSTANCE)); - typeKeeper.addType(propertyListType); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -627,9 +709,10 @@ void testAssignmentMethod() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:property_list"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(propertyListType); } // region: self @@ -643,14 +726,17 @@ void testSelf() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType objectType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:object")); + final TypeString objectRef = TypeString.of("sw:object"); + final MagikType objectType = (MagikType) typeKeeper.getType(objectRef); objectType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "test1", Collections.emptyList(), null, - new ExpressionResult(SelfType.INSTANCE)); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -663,8 +749,9 @@ void testSelf() { assertThat(result.size()).isEqualTo(1); final SelfType resultType = (SelfType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo(SelfType.SERIALIZED_NAME); + assertThat(resultType) + .isNotNull() + .isEqualTo(SelfType.INSTANCE); } @Test @@ -676,15 +763,17 @@ void testSelfNewInit() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType ropeType = new SlottedType(GlobalReference.of("sw:rope")); - typeKeeper.addType(ropeType); + final TypeString ropeRef = TypeString.of("sw:rope"); + final MagikType ropeType = new MagikType(typeKeeper, Sort.SLOTTED, ropeRef); ropeType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "new()", Collections.emptyList(), null, - new ExpressionResult(SelfType.INSTANCE)); + null, + new ExpressionResultString(TypeString.SELF), + new ExpressionResultString()); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -696,9 +785,10 @@ void testSelfNewInit() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:rope"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(ropeType); } // endregion @@ -722,9 +812,11 @@ void testExpressionTypeAnnotation() throws IOException { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + final AbstractType integerType = typeKeeper.getType(TypeString.of("sw:integer")); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -737,10 +829,8 @@ void testIterableExpressionIterTypeAnnotation() throws IOException { + " _endloop\n" + "_endmethod\n"; - // Set up TypeKeeper/TypeReasoner. - final TypeKeeper typeKeeper = new TypeKeeper(); - // Do analysis. + final TypeKeeper typeKeeper = new TypeKeeper(); final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); @@ -752,17 +842,21 @@ void testIterableExpressionIterTypeAnnotation() throws IOException { final ExpressionResult aResult = reasoner.getNodeType(aNode); assertThat(aResult.size()).isEqualTo(1); - final MagikType aResultType = (MagikType) aResult.get(0, null); - assertThat(aResultType).isNotNull(); - assertThat(aResultType.getFullName()).isEqualTo("sw:integer"); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); + final AbstractType aResultType = aResult.get(0, null); + assertThat(aResultType) + .isEqualTo(integerType); final AstNode bNode = forVariablesNode.getDescendants(MagikGrammar.IDENTIFIER).get(1); final ExpressionResult bResult = reasoner.getNodeType(bNode); assertThat(bResult.size()).isEqualTo(1); - final MagikType bResultType = (MagikType) bResult.get(0, null); - assertThat(bResultType).isNotNull(); - assertThat(bResultType.getFullName()).isEqualTo("sw:float"); + final TypeString floatRef = TypeString.of("sw:float"); + final AbstractType floatType = typeKeeper.getType(floatRef); + final AbstractType bResultType = bResult.get(0, null); + assertThat(bResultType) + .isEqualTo(floatType); } @Test @@ -786,9 +880,11 @@ void testEmptyLocalDefinition() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:unset"); + final AbstractType unsetType = typeKeeper.getType(TypeString.of("sw:unset")); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(unsetType); } @Test @@ -802,10 +898,11 @@ void testAugmentedAssignment() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final AbstractType integerType = typeKeeper.getType(GlobalReference.of("sw:integer")); - final AbstractType floatType = typeKeeper.getType(GlobalReference.of("sw:float")); + final TypeString integerRef = TypeString.of("sw:integer"); + final TypeString floatRef = TypeString.of("sw:float"); + final AbstractType floatType = typeKeeper.getType(floatRef); final BinaryOperator binaryOperator = - new BinaryOperator(BinaryOperator.Operator.valueFor("+"), integerType, floatType, floatType); + new BinaryOperator(BinaryOperator.Operator.valueFor("+"), integerRef, floatRef, floatRef); typeKeeper.addBinaryOperator(binaryOperator); // Do analysis. @@ -818,9 +915,10 @@ void testAugmentedAssignment() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(1); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:float"); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(floatType); } @Test @@ -838,11 +936,17 @@ void testGatherParameters() { // Assert user:object.test type determined. final AstNode topNode = magikFile.getTopNode(); - final AstNode parameterNode = topNode.getFirstDescendant(MagikGrammar.PARAMETER); + final AstNode parameterNode = topNode + .getFirstDescendant(MagikGrammar.PARAMETER) + .getFirstChild(MagikGrammar.IDENTIFIER); final ExpressionResult result = reasoner.getNodeType(parameterNode); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:simple_vector"); + + final TypeString simpleVectorRef = TypeString.of("sw:simple_vector"); + final AbstractType simpleVectorType = typeKeeper.getType(simpleVectorRef); + final AbstractType actualResultType = result.get(0, null); + assertThat(actualResultType) + .isNotNull() + .isEqualTo(simpleVectorType); } @Test @@ -867,9 +971,12 @@ void testImport() { final AstNode procNode = topNode.getFirstDescendant(MagikGrammar.PROCEDURE_DEFINITION); final AstNode importNode = procNode.getFirstDescendant(MagikGrammar.VARIABLE_DEFINITION); final ExpressionResult result = reasoner.getNodeType(importNode); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:integer"); + + final AbstractType integerType = typeKeeper.getType(TypeString.of("sw:integer")); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -893,13 +1000,16 @@ void testAssignmentChain() { final ExpressionResult result = reasoner.getNodeType(methodNode); assertThat(result.size()).isEqualTo(2); - final MagikType resultType1 = (MagikType) result.get(0, null); - assertThat(resultType1).isNotNull(); - assertThat(resultType1.getFullName()).isEqualTo("sw:integer"); + final AbstractType integerType = typeKeeper.getType(TypeString.of("sw:integer")); + final AbstractType resultType1 = result.get(0, null); + assertThat(resultType1) + .isNotNull() + .isEqualTo(integerType); - final MagikType resultType2 = (MagikType) result.get(1, null); - assertThat(resultType2).isNotNull(); - assertThat(resultType2.getFullName()).isEqualTo("sw:integer"); + final AbstractType resultType2 = result.get(1, null); + assertThat(resultType2) + .isNotNull() + .isEqualTo(integerType); } @Test @@ -924,9 +1034,11 @@ void testTryWith() { final ExpressionResult result = reasoner.getNodeType(tryVarNode); assertThat(result).isNotNull(); - final MagikType resultType = (MagikType) result.get(0, null); - assertThat(resultType).isNotNull(); - assertThat(resultType.getFullName()).isEqualTo("sw:condition"); + final AbstractType conditionType = typeKeeper.getType(TypeString.of("sw:condition")); + final AbstractType resultType = result.get(0, null); + assertThat(resultType) + .isNotNull() + .isEqualTo(conditionType); } @Test @@ -950,20 +1062,26 @@ void testMergeReturnTypes() { assertThat(result).isNotNull(); // First is integer + undefined. + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType intAndUndefinedType = new CombinedType( + typeKeeper.getType(integerRef), + UndefinedType.INSTANCE); final AbstractType resultType0 = result.get(0, null); assertThat(resultType0) .isNotNull() - .isInstanceOf(CombinedType.class); - assertThat(resultType0.getFullName()).isEqualTo("_undefined|sw:integer"); + .isEqualTo(intAndUndefinedType); // The rest is unset + undefined, as merging the two yields unset for the rest. + final TypeString unsetRef = TypeString.of("sw:unset"); + final AbstractType unsetAndUndefinedType = new CombinedType( + typeKeeper.getType(unsetRef), + UndefinedType.INSTANCE); result.getTypes().stream() .skip(1) .forEach(type -> { assertThat(type) .isNotNull() - .isInstanceOf(CombinedType.class); - assertThat(type.getFullName()).isEqualTo("_undefined|sw:unset"); + .isEqualTo(unsetAndUndefinedType); }); } @@ -976,11 +1094,11 @@ void testSingleSuperType() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType sType = new SlottedType(GlobalReference.of("sw:s")); - typeKeeper.addType(sType); - final MagikType tType = new SlottedType(GlobalReference.of("sw:t")); - tType.addParent(sType); - typeKeeper.addType(tType); + final TypeString sRef = TypeString.of("sw:s"); + final MagikType sType = new MagikType(typeKeeper, Sort.SLOTTED, sRef); + final TypeString tRef = TypeString.of("sw:t"); + final MagikType tType = new MagikType(typeKeeper, Sort.SLOTTED, tRef); + tType.addParent(sRef); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -1006,14 +1124,14 @@ void testSingleNamedSuperType() { // Set up TypeKeeper/TypeReasoner. final TypeKeeper typeKeeper = new TypeKeeper(); - final MagikType rType = new SlottedType(GlobalReference.of("sw", "r")); - typeKeeper.addType(rType); - final MagikType sType = new SlottedType(GlobalReference.of("sw", "s")); - typeKeeper.addType(sType); - final MagikType tType = new SlottedType(GlobalReference.of("sw", "t")); - tType.addParent(rType); - tType.addParent(sType); - typeKeeper.addType(tType); + final TypeString rRef = TypeString.of("sw:r"); + final MagikType rType = new MagikType(typeKeeper, Sort.SLOTTED, rRef); + final TypeString sRef = TypeString.of("sw:s"); + new MagikType(typeKeeper, Sort.SLOTTED, sRef); + final TypeString tRef = TypeString.of("sw:t"); + final MagikType tType = new MagikType(typeKeeper, Sort.SLOTTED, tRef); + tType.addParent(rRef); + tType.addParent(sRef); // Do analysis. final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); @@ -1035,6 +1153,7 @@ void testParameterType() { final String code = "" + "_method a.b(p1)\n" + " ## @param {sw:float} p1\n" + + " show(p1)" + "_endmethod"; // Set up TypeKeeper/TypeReasoner. @@ -1044,15 +1163,26 @@ void testParameterType() { final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + final TypeString floatRef = TypeString.of("sw:float"); + final AbstractType floatType = typeKeeper.getType(floatRef); final AstNode topNode = magikFile.getTopNode(); - final AstNode parameterNode = topNode.getFirstDescendant(MagikGrammar.PARAMETER); - final ExpressionResult result = reasoner.getNodeType(parameterNode); - assertThat(result) - .isNotNull(); - final AbstractType floatType = typeKeeper.getType(GlobalReference.of("sw:float")); - final AbstractType actualType = result.get(0, null); - assertThat(actualType) + // Test parameter definition. + final AstNode parameterNode = topNode + .getFirstDescendant(MagikGrammar.PARAMETER) + .getFirstChild(MagikGrammar.IDENTIFIER); + final ExpressionResult actualParameterResult = reasoner.getNodeType(parameterNode); + final AbstractType actualParameterType = actualParameterResult.get(0, null); + assertThat(actualParameterType) + .isNotNull() + .isEqualTo(floatType); + + // Test parameter usage. + final AstNode procInvocationNode = topNode.getFirstDescendant(MagikGrammar.PROCEDURE_INVOCATION); + final AstNode atomNode = procInvocationNode.getFirstDescendant(MagikGrammar.ATOM); + final ExpressionResult actualAtomResult = reasoner.getNodeType(atomNode); + final AbstractType actualAtomType = actualAtomResult.get(0, null); + assertThat(actualAtomType) .isNotNull() .isEqualTo(floatType); } @@ -1072,13 +1202,14 @@ void testParameterTypeCombined() { final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); final AstNode topNode = magikFile.getTopNode(); - final AstNode parameterNode = topNode.getFirstDescendant(MagikGrammar.PARAMETER); + final AstNode parameterNode = topNode + .getFirstDescendant(MagikGrammar.PARAMETER) + .getFirstChild(MagikGrammar.IDENTIFIER); final ExpressionResult result = reasoner.getNodeType(parameterNode); - assertThat(result) - .isNotNull(); - - final AbstractType floatType = typeKeeper.getType(GlobalReference.of("sw:float")); - final AbstractType integerType = typeKeeper.getType(GlobalReference.of("sw:integer")); + final TypeString floatRef = TypeString.of("sw:float"); + final AbstractType floatType = typeKeeper.getType(floatRef); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); final AbstractType expectedType = CombinedType.combine(floatType, integerType); final AbstractType actualType = result.get(0, null); assertThat(actualType) @@ -1086,4 +1217,122 @@ void testParameterTypeCombined() { .isEqualTo(expectedType); } + @Test + void testParameterReferenceUsage() { + final String code = "" + + "_method a.b\n" + + " _return _self.returns_param(10)\n" + + "_endmethod"; + + // Set up TypeKeeper/TypeReasoner. + final TypeKeeper typeKeeper = new TypeKeeper(); + final MagikType aType = new MagikType(typeKeeper, Sort.SLOTTED, TypeString.of("user:a")); + final TypeString param1Ref = TypeString.of("_parameter(p1)"); + aType.addMethod( + null, + EnumSet.noneOf(Method.Modifier.class), + "returns_param()", + List.of( + new Parameter("p1", Parameter.Modifier.NONE)), + null, + null, + new ExpressionResultString(param1Ref), + new ExpressionResultString()); + + // Do analysis. + final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); + final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + + final AstNode topNode = magikFile.getTopNode(); + final AstNode methodDefinitionNode = topNode.getFirstDescendant(MagikGrammar.METHOD_DEFINITION); + final ExpressionResult result = reasoner.getNodeType(methodDefinitionNode); + final AbstractType expectedType = typeKeeper.getType(TypeString.of("sw:integer")); + final AbstractType actualType = result.get(0, null); + assertThat(actualType) + .isNotNull() + .isEqualTo(expectedType); + } + + @Test + void testParameterReferenceNested() { + final String code = "" + + "_method a.b\n" + + " _return _self.returns_param(_self.returns_param2(10))\n" + + "_endmethod"; + + // Set up TypeKeeper/TypeReasoner. + final TypeKeeper typeKeeper = new TypeKeeper(); + final MagikType aType = new MagikType(typeKeeper, Sort.SLOTTED, TypeString.of("user:a")); + final TypeString param1Ref = TypeString.of("_parameter(p1)"); + aType.addMethod( + null, + EnumSet.noneOf(Method.Modifier.class), + "returns_param()", + List.of( + new Parameter("p1", Parameter.Modifier.NONE)), + null, + null, + new ExpressionResultString(param1Ref), + new ExpressionResultString()); + aType.addMethod( + null, + EnumSet.noneOf(Method.Modifier.class), + "returns_param2()", + List.of( + new Parameter("p1", Parameter.Modifier.NONE)), + null, + null, + new ExpressionResultString(param1Ref), + new ExpressionResultString()); + + // Do analysis. + final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); + final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + + final AstNode topNode = magikFile.getTopNode(); + final AstNode methodDefinitionNode = topNode.getFirstDescendant(MagikGrammar.METHOD_DEFINITION); + final ExpressionResult result = reasoner.getNodeType(methodDefinitionNode); + final AbstractType expectedType = typeKeeper.getType(TypeString.of("sw:integer")); + final AbstractType actualType = result.get(0, null); + assertThat(actualType) + .isNotNull() + .isEqualTo(expectedType); + } + + @Test + void testParameterReferenceOptional() { + final String code = "" + + "_method a.b\n" + + " _return _self.returns_param()\n" + + "_endmethod"; + + // Set up TypeKeeper/TypeReasoner. + final TypeKeeper typeKeeper = new TypeKeeper(); + final MagikType aType = new MagikType(typeKeeper, Sort.SLOTTED, TypeString.of("user:a")); + final TypeString param1Ref = TypeString.of("_parameter(p1)"); + aType.addMethod( + null, + EnumSet.noneOf(Method.Modifier.class), + "returns_param()", + List.of( + new Parameter("p1", Parameter.Modifier.OPTIONAL)), + null, + null, + new ExpressionResultString(param1Ref), + new ExpressionResultString()); + + // Do analysis. + final MagikTypedFile magikFile = this.createMagikFile(code, typeKeeper); + final LocalTypeReasoner reasoner = magikFile.getTypeReasoner(); + + final AstNode topNode = magikFile.getTopNode(); + final AstNode methodDefinitionNode = topNode.getFirstDescendant(MagikGrammar.METHOD_DEFINITION); + final ExpressionResult result = reasoner.getNodeType(methodDefinitionNode); + final AbstractType expectedType = typeKeeper.getType(TypeString.of("sw:unset")); + final AbstractType actualType = result.get(0, null); + assertThat(actualType) + .isNotNull() + .isEqualTo(expectedType); + } + } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandlerTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandlerTest.java deleted file mode 100644 index 50d88937..00000000 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeAnnotationHandlerTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package nl.ramsolutions.sw.magik.analysis.typing; - -import com.sonar.sslr.api.AstNode; -import java.io.IOException; -import nl.ramsolutions.sw.magik.api.MagikGrammar; -import nl.ramsolutions.sw.magik.parser.MagikParser; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test TypeAnnotationHandler. - */ -class TypeAnnotationHandlerTest { - - private AstNode parseMagik(String code) { - final MagikParser parser = new MagikParser(); - return parser.parseSafe(code); - } - - @Test - void testTypeForExpression() throws IOException { - final String code = "" - + "_method a.b\n" - + " _local x << fn() # type: sw:integer\n" - + "_endmethod"; - final AstNode topNode = this.parseMagik(code); - final AstNode expressionNode = topNode.getFirstDescendant(MagikGrammar.EXPRESSION); - - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(expressionNode); - assertThat(typeAnnotation).isEqualTo("sw:integer"); - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(expressionNode); - assertThat(iterTypeAnnotation).isNull(); - } - - @Test - void testTypeForExpressionMultiple() throws IOException { - final String code = "" - + "_method a.b\n" - + " _local x << fn() + fn2() # type: sw:integer\n" - + "_endmethod"; - final AstNode topNode = this.parseMagik(code); - final AstNode expressionNode = topNode.getFirstDescendant(MagikGrammar.EXPRESSION); - - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(expressionNode); - assertThat(typeAnnotation).isEqualTo("sw:integer"); - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(expressionNode); - assertThat(iterTypeAnnotation).isNull(); - } - - @Test - void testTypeForExpressionMultipleLines1() throws IOException { - // This is not supported, thus should return null. - final String code = "" - + "_method a.b\n" - + " _local x <<\n" - + " fn() + # type: sw:integer\n" - + " fn2()\n" - + "_endmethod"; - final AstNode topNode = this.parseMagik(code); - final AstNode expressionNode = topNode.getFirstDescendant(MagikGrammar.EXPRESSION); - - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(expressionNode); - assertThat(typeAnnotation).isEqualTo("sw:integer"); - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(expressionNode); - assertThat(iterTypeAnnotation).isNull(); - } - - @Test - void testTypeForExpressionMultipleLines2() throws IOException { - final String code = "" - + "_method a.b\n" - + " _local x <<\n" - + " fn() + \n" - + " fn2() # type: sw:integer\n" - + "_endmethod\n"; - final AstNode topNode = this.parseMagik(code); - final AstNode expressionNode = topNode.getFirstDescendant(MagikGrammar.EXPRESSION); - - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(expressionNode); - assertThat(typeAnnotation).isNull(); - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(expressionNode); - assertThat(iterTypeAnnotation).isNull(); - } - - @Test - void testIterTypeForForExpression() throws IOException { - final String code = "" - + "_method a.b\n" - + " _for x, y _over fn() # iter-type: sw:integer, sw:float\n" - + " _loop\n" - + " _endloop\n" - + "_endmethod"; - final AstNode topNode = this.parseMagik(code); - final AstNode iterableExpressionNode = topNode.getFirstDescendant(MagikGrammar.ITERABLE_EXPRESSION); - - final String typeAnnotation = TypeAnnotationHandler.typeAnnotationForExpression(iterableExpressionNode); - assertThat(typeAnnotation).isNull(); - final String iterTypeAnnotation = TypeAnnotationHandler.iterTypeAnnotationForExpression(iterableExpressionNode); - assertThat(iterTypeAnnotation).isEqualTo("sw:integer, sw:float"); - } - -} diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeMatcherTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeMatcherTest.java index 2610c998..1536a57d 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeMatcherTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeMatcherTest.java @@ -2,9 +2,9 @@ import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; -import nl.ramsolutions.sw.magik.analysis.typing.types.IntrinsicType; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; +import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType.Sort; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -16,116 +16,148 @@ class TypeMatcherTest { @Test void testTypeEquals() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type")); - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:type")); - final boolean matches = TypeMatcher.typeMatches(type, criterium); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString typeRef = TypeString.of("sw:type"); + final AbstractType type = new MagikType(typeKeeper, Sort.INTRINSIC, typeRef); + + final boolean matches = TypeMatcher.typeMatches(type, type); assertThat(matches).isTrue(); } @Test void testTypeNotEquals() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type1")); - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:type2")); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type1Ref = TypeString.of("sw:type1"); + final TypeString type2Ref = TypeString.of("sw:type2"); + final AbstractType type = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final AbstractType criterium = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final boolean matches = TypeMatcher.typeMatches(type, criterium); assertThat(matches).isFalse(); } @Test void testTypeIsKindOf() { - final AbstractType baseType = new IntrinsicType(GlobalReference.of("sw:base")); - final MagikType childType = new IntrinsicType(GlobalReference.of("sw:child")); - childType.addParent(baseType); - - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:base")); - final boolean matches = TypeMatcher.typeMatches(childType, criterium); + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString baseRef = TypeString.of("sw:base"); + final MagikType baseType = new MagikType(typeKeeper, Sort.INTRINSIC, baseRef); + final TypeString childRef = TypeString.of("sw:child"); + final MagikType childType = new MagikType(typeKeeper, Sort.INTRINSIC, childRef); + childType.addParent(baseRef); + + final boolean matches = TypeMatcher.typeMatches(childType, baseType); assertThat(matches).isTrue(); } @Test void testTypeMatchesCombinedType() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type1")); - - final AbstractType criterium = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type1")), - new IntrinsicType(GlobalReference.of("sw:type2"))); - final boolean matches = TypeMatcher.typeMatches(type, criterium); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type1Ref = TypeString.of("sw:type1"); + final MagikType type1 = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final TypeString type2Ref = TypeString.of("sw:type2"); + final MagikType type2 = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final AbstractType criterium = new CombinedType(type1, type2); + + final boolean matches = TypeMatcher.typeMatches(type1, criterium); assertThat(matches).isTrue(); } @Test void testTypeNotMatchesCombinedType() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type3")); - - final AbstractType criterium = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type1")), - new IntrinsicType(GlobalReference.of("sw:type2"))); - final boolean matches = TypeMatcher.typeMatches(type, criterium); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type3Ref = TypeString.of("sw:type3"); + final AbstractType type3 = new MagikType(typeKeeper, Sort.INTRINSIC, type3Ref); + final TypeString type1Ref = TypeString.of("sw:type1"); + final MagikType type1 = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final TypeString type2Ref = TypeString.of("sw:type2"); + final MagikType type2 = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final AbstractType criterium = new CombinedType(type1, type2); + + final boolean matches = TypeMatcher.typeMatches(type3, criterium); assertThat(matches).isFalse(); } @Test void testCombinedTypeMatchesCombinedType() { - final AbstractType type = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type1")), - new IntrinsicType(GlobalReference.of("sw:type2"))); - - final AbstractType criterium = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type1")), - new IntrinsicType(GlobalReference.of("sw:type2")), - new IntrinsicType(GlobalReference.of("sw:type3"))); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type1Ref = TypeString.of("sw:type1"); + final MagikType type1 = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final TypeString type2Ref = TypeString.of("sw:type2"); + final MagikType type2 = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final AbstractType type = new CombinedType(type1, type2); + final TypeString type3Ref = TypeString.of("sw:type3"); + final MagikType type3 = new MagikType(typeKeeper, Sort.INTRINSIC, type3Ref); + final AbstractType criterium = new CombinedType(type1, type2, type3); + final boolean matches = TypeMatcher.typeMatches(type, criterium); assertThat(matches).isTrue(); } @Test void testCombinedTypeNotMatchesCombinedType() { - final AbstractType type = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type1")), - new IntrinsicType(GlobalReference.of("sw:type2"))); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type1Ref = TypeString.of("sw:type1"); + final MagikType type1 = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final TypeString type2Ref = TypeString.of("sw:type2"); + final MagikType type2 = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final AbstractType type = new CombinedType(type1, type2); + final TypeString type3Ref = TypeString.of("sw:type3"); + final MagikType type3 = new MagikType(typeKeeper, Sort.INTRINSIC, type3Ref); + final AbstractType criterium = new CombinedType(type2, type3); - final AbstractType criterium = new CombinedType( - new IntrinsicType(GlobalReference.of("sw:type2")), - new IntrinsicType(GlobalReference.of("sw:type3"))); final boolean matches = TypeMatcher.typeMatches(type, criterium); assertThat(matches).isFalse(); } @Test void testIsKindOfEquals() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type")); - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:type")); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString typeRef = TypeString.of("sw:type"); + final AbstractType type = new MagikType(typeKeeper, Sort.INTRINSIC, typeRef); + final AbstractType criterium = type; + final boolean isKindOf = TypeMatcher.isKindOf(type, criterium); assertThat(isKindOf).isTrue(); } @Test void testIsKindOfNotEquals() { - final AbstractType type = new IntrinsicType(GlobalReference.of("sw:type1")); - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:type2")); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString type1Ref = TypeString.of("sw:type1"); + final AbstractType type = new MagikType(typeKeeper, Sort.INTRINSIC, type1Ref); + final TypeString type2Ref = TypeString.of("sw:type2"); + final AbstractType criterium = new MagikType(typeKeeper, Sort.INTRINSIC, type2Ref); + final boolean isKindOf = TypeMatcher.isKindOf(type, criterium); assertThat(isKindOf).isFalse(); } @Test void testIsKindOfDirectParent() { - final AbstractType baseType = new IntrinsicType(GlobalReference.of("sw:base")); - final MagikType childType = new IntrinsicType(GlobalReference.of("sw:child")); - childType.addParent(baseType); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString baseRef = TypeString.of("sw:base"); + final TypeString childRef = TypeString.of("sw:child"); + final MagikType childType = new MagikType(typeKeeper, Sort.INTRINSIC, childRef); + childType.addParent(baseRef); + final AbstractType criterium = new MagikType(typeKeeper, Sort.INTRINSIC, baseRef); - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:base")); final boolean isKindOf = TypeMatcher.isKindOf(childType, criterium); assertThat(isKindOf).isTrue(); } @Test void testIsKindOfIndirectParent() { - final AbstractType baseType = new IntrinsicType(GlobalReference.of("sw:base")); - final MagikType child1Type = new IntrinsicType(GlobalReference.of("sw:child1")); - child1Type.addParent(baseType); - final MagikType child2Type = new IntrinsicType(GlobalReference.of("sw:child2")); - child2Type.addParent(child1Type); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString baseRef = TypeString.of("sw:base"); + final AbstractType baseType = new MagikType(typeKeeper, Sort.INTRINSIC, baseRef); + final TypeString child1Ref = TypeString.of("sw:child1"); + final MagikType child1Type = new MagikType(typeKeeper, Sort.INTRINSIC, child1Ref); + child1Type.addParent(baseRef); + final TypeString child2Ref = TypeString.of("sw:child2"); + final MagikType child2Type = new MagikType(typeKeeper, Sort.INTRINSIC, child2Ref); + child2Type.addParent(child1Ref); + final AbstractType criterium = baseType; - final AbstractType criterium = new IntrinsicType(GlobalReference.of("sw:base")); final boolean isKindOf = TypeMatcher.isKindOf(child2Type, criterium); assertThat(isKindOf).isTrue(); } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParserTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParserTest.java index 4ca331eb..669c1798 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParserTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/TypeParserTest.java @@ -2,7 +2,10 @@ import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; +import nl.ramsolutions.sw.magik.analysis.typing.types.ParameterReferenceType; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import org.junit.jupiter.api.Test; @@ -15,29 +18,41 @@ class TypeParserTest { @Test void testParseSelf() { - final String typeStr = "_self"; final ITypeKeeper typeKeeper = new TypeKeeper(); final TypeParser parser = new TypeParser(typeKeeper); - final AbstractType parsedType = parser.parseTypeString(typeStr, "sw"); + final TypeString typeStr = TypeString.SELF; + final AbstractType parsedType = parser.parseTypeString(typeStr); assertThat(parsedType).isEqualTo(SelfType.INSTANCE); } @Test void testParseUndefined() { - final String typeStr = "_undefined"; final ITypeKeeper typeKeeper = new TypeKeeper(); final TypeParser parser = new TypeParser(typeKeeper); - final AbstractType parsedType = parser.parseTypeString(typeStr, "sw"); + final TypeString typeStr = TypeString.UNDEFINED; + final AbstractType parsedType = parser.parseTypeString(typeStr); assertThat(parsedType).isEqualTo(UndefinedType.INSTANCE); } @Test void testParseUndefinedResult() { final String resultStr = "__UNDEFINED_RESULT__"; + final ExpressionResultString expressionResultString = ExpressionResultString.of(resultStr, "sw"); final ITypeKeeper typeKeeper = new TypeKeeper(); final TypeParser parser = new TypeParser(typeKeeper); - ExpressionResult result = parser.parseExpressionResultString(resultStr, "sw"); + final ExpressionResult result = parser.parseExpressionResultString(expressionResultString); assertThat(result).isEqualTo(ExpressionResult.UNDEFINED); } + @Test + void testParameterReference() { + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeParser parser = new TypeParser(typeKeeper); + final TypeString typeStr = TypeString.of("_parameter(param1)", "sw"); + final AbstractType parsedType = parser.parseTypeString(typeStr); + assertThat(parsedType).isInstanceOf(ParameterReferenceType.class); + final ParameterReferenceType parameterType = (ParameterReferenceType) parsedType; + assertThat(parameterType.getName()).isEqualTo("_parameter(param1)"); + } + } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultTest.java index d2e32525..efa5f71f 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/analysis/typing/types/ExpressionResultTest.java @@ -1,5 +1,7 @@ package nl.ramsolutions.sw.magik.analysis.typing.types; +import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; +import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -11,7 +13,9 @@ class ExpressionResultTest { @Test void testToStringOne() { - final AbstractType symbolType = new IndexedType(GlobalReference.of("sw:symbol")); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final ExpressionResult result = new ExpressionResult(symbolType); final String toString = result.toString(); assertThat(toString).contains("(sw:symbol)"); @@ -19,7 +23,9 @@ void testToStringOne() { @Test void testToStringThree() { - final AbstractType integerType = new SlottedType(GlobalReference.of("sw:integer")); + final TypeKeeper typeKeeper = new TypeKeeper(); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); final ExpressionResult result = new ExpressionResult(integerType, integerType, integerType); final String toString = result.toString(); assertThat(toString).contains("(sw:integer,sw:integer,sw:integer)"); @@ -34,8 +40,11 @@ void testToStringUndefined() { @Test void testToStringRepeating() { - final AbstractType unsetType = new SlottedType(GlobalReference.of("sw:unset")); - final AbstractType symbolType = new IndexedType(GlobalReference.of("sw:symbol")); + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString unsetRef = TypeString.of("sw:unset"); + final AbstractType unsetType = typeKeeper.getType(unsetRef); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final ExpressionResult result1 = ExpressionResult.UNDEFINED; final ExpressionResult result2 = new ExpressionResult(symbolType); final ExpressionResult result = new ExpressionResult(result1, result2, unsetType); @@ -43,4 +52,49 @@ void testToStringRepeating() { assertThat(toString).contains("(_undefined|sw:symbol,_undefined|sw:unset...)"); } + @Test + void testSubstituteType1() { + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); + + final ExpressionResult result = new ExpressionResult(symbolType); + final ExpressionResult newResult = result.substituteType(symbolType, integerType); + final AbstractType newType = newResult.get(0, null); + assertThat(newType).isEqualTo(integerType); + } + + @Test + void testSubstituteType2() { + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); + final TypeString integerRef = TypeString.of("sw:integer"); + final AbstractType integerType = typeKeeper.getType(integerRef); + final CombinedType combinedType = new CombinedType(symbolType, integerType); + + final ExpressionResult result = new ExpressionResult(combinedType); + final ExpressionResult newResult = result.substituteType(symbolType, integerType); + final AbstractType newType = newResult.get(0, null); + final AbstractType expectedType = new CombinedType(newType); + assertThat(newType).isEqualTo(expectedType); + } + + @Test + void testSubstituteType3() { + final ITypeKeeper typeKeeper = new TypeKeeper(); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); + final AbstractType parameterReferenceType = new ParameterReferenceType("p1"); + final CombinedType combinedType = new CombinedType(symbolType, parameterReferenceType); + + final ExpressionResult result = new ExpressionResult(combinedType); + final ExpressionResult newResult = result.substituteType(parameterReferenceType, symbolType); + final AbstractType newType = newResult.get(0, null); + final AbstractType expectedType = new CombinedType(symbolType); + assertThat(newType).isEqualTo(expectedType); + } + } diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/MagikGrammarTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/MagikGrammarTest.java index 4ea601a4..ddec5085 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/MagikGrammarTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/MagikGrammarTest.java @@ -86,6 +86,7 @@ void testBlock() { void testTry() { MagikRuleForbiddenAssert.assertThat(g.rule(MagikGrammar.TRY), MagikGrammar.TRY_SYNTAX_ERROR) .matches("_try _when error _endtry") + .matches("_try _when information, warning _endtry") .matches("_try expr() _when error _endtry") .matches("_try _with e _when error _endtry") .matches("_try _with e expr() _when error _endtry"); @@ -213,6 +214,7 @@ void testFor() { void testWhile() { Assertions.assertThat(g.rule(MagikGrammar.WHILE)) .matches("_while a _loop _endloop") + .matches("_while a < 10 _loop _endloop") .matches("_while a _andif b _loop _endloop") .matches("_while a _isnt _unset _loop _endloop"); } @@ -310,7 +312,8 @@ void testPrimitiveStatement() { @Test void testExpression() { - Assertions.assertThat(g.rule(MagikGrammar.EXPRESSION)) + final Rule rule = g.rule(MagikGrammar.EXPRESSION); + Assertions.assertThat(rule) .matches("a()") .matches("expr()") .matches("a _is b") diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/NewDocGrammarTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/NewDocGrammarTest.java index 15795dad..6c45577d 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/NewDocGrammarTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/api/NewDocGrammarTest.java @@ -35,7 +35,8 @@ void testReturn() { Assertions.assertThat(g.rule(NewDocGrammar.RETURN)) .matches("## @return Aaaaa aaa.") .matches("## @return {sw:rope}") - .matches("## @return {user:thing} Aaaaa aaa."); + .matches("## @return {user:thing} Aaaaa aaa.") + .matches("## @return {_parameter(p1)}"); } @Test diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReaderReaderTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReaderReaderTest.java new file mode 100644 index 00000000..7d245797 --- /dev/null +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/CommentInstructionReaderReaderTest.java @@ -0,0 +1,70 @@ +package nl.ramsolutions.sw.magik.parser; + +import java.util.Set; +import nl.ramsolutions.sw.magik.MagikFile; +import nl.ramsolutions.sw.magik.analysis.scope.GlobalScope; +import nl.ramsolutions.sw.magik.analysis.scope.Scope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for InstructionReader. + */ +public class CommentInstructionReaderReaderTest { + + private static final CommentInstructionReader.InstructionType STATEMENT_INSTRUCTION_TYPE = + CommentInstructionReader.InstructionType.createInstructionType("mlint"); + private static final CommentInstructionReader.InstructionType SCOPE_INSTRUCTION_TYPE = + CommentInstructionReader.InstructionType.createScopeInstructionType("mlint"); + + @Test + void testReadInstruction() { + final String code = "" + + "_proc()\n" + + " print(10) # mlint: disable=forbidden-call\n" + + "_endproc"; + final MagikFile magikFile = new MagikFile("tests://unittest", code); + + final CommentInstructionReader instructionReader = + new CommentInstructionReader(magikFile, Set.of(STATEMENT_INSTRUCTION_TYPE)); + + final String instructionAtLine = + instructionReader.getInstructionsAtLine(2, STATEMENT_INSTRUCTION_TYPE); + assertThat(instructionAtLine).isEqualTo("disable=forbidden-call"); + + // Cannot read instruction via scope. + final GlobalScope globalScope = magikFile.getGlobalScope(); + final Scope scope = globalScope.getScopeForLineColumn(1, 99); + final Set scopeInstructions = instructionReader.getScopeInstructions(scope, SCOPE_INSTRUCTION_TYPE); + assertThat(scopeInstructions).isEmpty(); + } + + @Test + void testReadScopeInstruction() { + final String code = "" + + "_proc()\n" + + " # mlint: disable=no-self-use\n" + + " print(10) # mlint: disable=forbidden-call\n" + + "_endproc"; + final MagikFile magikFile = new MagikFile("tests://unittest", code); + + final CommentInstructionReader instructionReader = + new CommentInstructionReader(magikFile, Set.of(SCOPE_INSTRUCTION_TYPE)); + + // Can read a scope/single line instruction at the specific line. + final String instructionAtLine2 = instructionReader.getInstructionsAtLine(2, SCOPE_INSTRUCTION_TYPE); + assertThat(instructionAtLine2).isEqualTo("disable=no-self-use"); + + // Can read instruction via scope. + final GlobalScope globalScope = magikFile.getGlobalScope(); + final Scope scope = globalScope.getScopeForLineColumn(1, 99); + final Set scopeInstructions = instructionReader.getScopeInstructions(scope, SCOPE_INSTRUCTION_TYPE); + assertThat(scopeInstructions).containsOnly("disable=no-self-use"); + + // Does not match the statement instruction. + final String instructionAtLine3 = instructionReader.getInstructionsAtLine(3, STATEMENT_INSTRUCTION_TYPE); + assertThat(instructionAtLine3).isNull(); + } + +} diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/MagikParserTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/MagikParserTest.java index 30efd1b5..d1f4185f 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/MagikParserTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/MagikParserTest.java @@ -5,7 +5,6 @@ import com.sonar.sslr.api.Token; import com.sonar.sslr.api.Trivia; import java.util.List; -import nl.ramsolutions.sw.magik.api.ExtendedTokenType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import org.junit.jupiter.api.Test; @@ -17,7 +16,7 @@ @SuppressWarnings("checkstyle:MagicNumber") class MagikParserTest { - private AstNode parseMagik(String code) { + private AstNode parseMagik(final String code) { final MagikParser parser = new MagikParser(); return parser.parseSafe(code); } @@ -304,7 +303,7 @@ void testWhitespaceTrivia() { assertThat(trivia0.getToken().getOriginalValue()).isEqualTo("\n"); final Trivia trivia1 = trivia.get(1); - assertThat(trivia1.getToken().getType()).isEqualTo(ExtendedTokenType.WHITESPACE); + assertThat(trivia1.getToken().getType()).isEqualTo(GenericTokenType.WHITESPACE); assertThat(trivia1.getToken().getOriginalValue()).isEqualTo(" "); final Trivia trivia2 = trivia.get(2); @@ -316,7 +315,7 @@ void testWhitespaceTrivia() { assertThat(trivia3.getToken().getOriginalValue()).isEqualTo("\n"); final Trivia trivia4 = trivia.get(4); - assertThat(trivia4.getToken().getType()).isEqualTo(ExtendedTokenType.WHITESPACE); + assertThat(trivia4.getToken().getType()).isEqualTo(GenericTokenType.WHITESPACE); assertThat(trivia4.getToken().getOriginalValue()).isEqualTo(" "); final Trivia trivia5 = trivia.get(5); diff --git a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/NewDocParserTest.java b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/NewDocParserTest.java index 99fa551f..f6806621 100644 --- a/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/NewDocParserTest.java +++ b/magik-squid/src/test/java/nl/ramsolutions/sw/magik/parser/NewDocParserTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import org.junit.jupiter.api.Test; @@ -14,7 +15,7 @@ */ class NewDocParserTest { - private AstNode parseMagik(String code) { + private AstNode parseMagik(final String code) { final MagikParser parser = new MagikParser(); return parser.parseSafe(code); } @@ -31,13 +32,12 @@ void testParameter() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser docParser = new NewDocParser(methodNode); - final Map parameterTypes = docParser.getParameterTypes(); - assertThat(parameterTypes) - .containsOnly( - Map.entry("param1", "sw:symbol"), - Map.entry("param2", "integer"), - Map.entry("param3", "integer"), - Map.entry("param4", "integer|float")); + final Map parameterTypes = docParser.getParameterTypes(); + assertThat(parameterTypes).containsOnly( + Map.entry("param1", TypeString.of("sw:symbol")), + Map.entry("param2", TypeString.of("integer")), + Map.entry("param3", TypeString.of("integer")), + Map.entry("param4", TypeString.of("integer|float"))); } @Test @@ -53,13 +53,12 @@ void testParameterEmpty() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser docParser = new NewDocParser(methodNode); - final Map parameterTypes = docParser.getParameterTypes(); - assertThat(parameterTypes) - .containsOnly( - Map.entry("param1", ""), - Map.entry("param2", "integer"), - Map.entry("param3", "integer"), - Map.entry("param4", "integer|float")); + final Map parameterTypes = docParser.getParameterTypes(); + assertThat(parameterTypes).containsOnly( + Map.entry("param1", TypeString.UNDEFINED), + Map.entry("param2", TypeString.of("integer")), + Map.entry("param3", TypeString.of("integer")), + Map.entry("param4", TypeString.of("integer|float"))); } @Test @@ -71,9 +70,9 @@ void testReturn() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser docParser = new NewDocParser(methodNode); - final List returnTypes = docParser.getReturnTypes(); + final List returnTypes = docParser.getReturnTypes(); assertThat(returnTypes) - .containsExactly("sw:integer"); + .containsExactly(TypeString.of("sw:integer")); } @Test @@ -88,9 +87,12 @@ void testReturnEmpty() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser docParser = new NewDocParser(methodNode); - final List returnTypes = docParser.getReturnTypes(); - assertThat(returnTypes) - .containsExactly("", "sw:integer", "sw:integer", "sw:integer|sw:float"); + final List returnTypes = docParser.getReturnTypes(); + assertThat(returnTypes).containsExactly( + TypeString.UNDEFINED, + TypeString.of("sw:integer"), + TypeString.of("sw:integer"), + TypeString.of("sw:integer|sw:float")); } @Test @@ -102,9 +104,9 @@ void testReturnSelf() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser docParser = new NewDocParser(methodNode); - final List returnTypes = docParser.getReturnTypes(); + final List returnTypes = docParser.getReturnTypes(); assertThat(returnTypes) - .containsExactly("_self"); + .containsExactly(TypeString.SELF); } @Test @@ -120,15 +122,15 @@ void testNestedProcedure() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); final NewDocParser methodDocParser = new NewDocParser(methodNode); - final List methodReturnTypes = methodDocParser.getReturnTypes(); + final List methodReturnTypes = methodDocParser.getReturnTypes(); assertThat(methodReturnTypes) - .containsExactly("sw:integer"); + .containsExactly(TypeString.of("sw:integer")); final AstNode procNode = topNode.getFirstDescendant(MagikGrammar.PROCEDURE_DEFINITION); final NewDocParser procDocParser = new NewDocParser(procNode); - final List procReturnTypes = procDocParser.getReturnTypes(); + final List procReturnTypes = procDocParser.getReturnTypes(); assertThat(procReturnTypes) - .containsExactly("sw:float"); + .containsExactly(TypeString.of("sw:float")); } @Test @@ -143,11 +145,10 @@ void testSlotTypes() throws IOException { final AstNode topNode = this.parseMagik(code); final AstNode definitionNode = topNode.getFirstChild(MagikGrammar.STATEMENT); final NewDocParser docParser = new NewDocParser(definitionNode); - final Map slotTypes = docParser.getSlotTypes(); - assertThat(slotTypes) - .containsOnly( - Map.entry("slot1", ""), - Map.entry("slot2", "integer")); + final Map slotTypes = docParser.getSlotTypes(); + assertThat(slotTypes).containsOnly( + Map.entry("slot1", TypeString.UNDEFINED), + Map.entry("slot2", TypeString.of("integer"))); } @Test @@ -169,4 +170,18 @@ void testTokenLines() throws IOException { .isEqualTo(2); } + @Test + void testReturnParameterReference() throws IOException { + final String code = "" + + "_method a.b(p1)\n" + + " ## @return {_parameter(p1)} First parameter.\n" + + "_endmethod"; + final AstNode topNode = this.parseMagik(code); + final AstNode methodNode = topNode.getFirstChild(MagikGrammar.METHOD_DEFINITION); + final NewDocParser docParser = new NewDocParser(methodNode); + final List returnTypes = docParser.getReturnTypes(); + assertThat(returnTypes) + .containsExactly(TypeString.of("_parameter(p1)")); + } + } diff --git a/magik-typed-checks/pom.xml b/magik-typed-checks/pom.xml index 041d41b6..90889ca2 100644 --- a/magik-typed-checks/pom.xml +++ b/magik-typed-checks/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 magik-typed-checks diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/CheckList.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/CheckList.java index 5f303e9e..9a08c107 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/CheckList.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/CheckList.java @@ -22,8 +22,8 @@ private CheckList() { } /** - * Get the list of {{MagikCheck}}s. - * @return List of with {{MagikCheck}}s + * Get the list of {@link MagikCheck}s. + * @return List of with {@link MagikCheck}s */ public static List> getChecks() { return List.of( @@ -38,8 +38,8 @@ public static List> getChecks() { } /** - * Get {{MagikCheck}}s which are disabled by default. - * @return List of {{MagikCheck}}s. + * Get {@link MagikCheck}s which are disabled by default. + * @return List of {@link MagikCheck}s. */ public static List> getDisabledByDefaultChecks() { return getChecks().stream() @@ -48,8 +48,8 @@ public static List> getDisabledByDefaultChecks() { } /** - * Get {{MagikCheck}}s which are templated. - * @return List of {{MagikCheck}}s. + * Get {@link MagikCheck}s which are templated. + * @return List of {@link MagikCheck}s. */ public static List> getTemplatedChecks() { return getChecks().stream() diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/MagikTypedCheck.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/MagikTypedCheck.java index d2b26186..1b4af8f3 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/MagikTypedCheck.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/MagikTypedCheck.java @@ -9,8 +9,8 @@ import nl.ramsolutions.sw.magik.analysis.typing.ReadOnlyTypeKeeperAdapter; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; import nl.ramsolutions.sw.magik.analysis.typing.types.SelfType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.checks.MagikCheck; @@ -82,9 +82,9 @@ protected AbstractType getTypeOfMethodDefinition(final AstNode node) { } final MethodDefinitionNodeHelper methodDefHelper = new MethodDefinitionNodeHelper(node); - final GlobalReference globalRef = methodDefHelper.getTypeGlobalReference(); + final TypeString typeString = methodDefHelper.getTypeString(); final ITypeKeeper typeKeeper = this.getTypeKeeper(); - return typeKeeper.getType(globalRef); + return typeKeeper.getType(typeString); } } diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/GlobalKnownTypedCheck.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/GlobalKnownTypedCheck.java index 7962b4dd..63675db4 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/GlobalKnownTypedCheck.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/GlobalKnownTypedCheck.java @@ -8,7 +8,7 @@ import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry; import nl.ramsolutions.sw.magik.analysis.scope.ScopeEntry.Type; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.typedchecks.MagikTypedCheck; @@ -51,10 +51,8 @@ protected void walkPostIdentifier(final AstNode node) { } final ITypeKeeper typeKeeper = this.getTypeKeeper(); - final GlobalReference globalRef = identifier.indexOf(':') != -1 - ? GlobalReference.of(identifier) - : GlobalReference.of(this.currentPakkage, identifier); - if (typeKeeper.hasType(globalRef)) { + final TypeString typeString = TypeString.of(identifier, this.currentPakkage); + if (typeKeeper.hasType(typeString)) { return; } diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypedCheck.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypedCheck.java index dabcb41d..7db3daa4 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypedCheck.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypedCheck.java @@ -8,9 +8,11 @@ import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.TypeMatcher; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.CombinedType; import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.typedchecks.MagikTypedCheck; @@ -21,6 +23,7 @@ public class MethodArgumentParameterTypedCheck extends MagikTypedCheck { private static final String MESSAGE = "Argument type (%s) does not match parameter type (%s)"; + private static final String SW_UNSET = "sw:unset"; @Override protected void walkPostMethodInvocation(final AstNode node) { @@ -49,6 +52,7 @@ protected void walkPostMethodInvocation(final AstNode node) { // Get methods. final MethodInvocationNodeHelper helper = new MethodInvocationNodeHelper(node); final String methodName = helper.getMethodName(); + final AbstractType unsetType = this.getTypeKeeper().getType(TypeString.of(SW_UNSET)); for (final Method method : calledType.getMethods(methodName)) { final List parameters = method.getParameters(); if (parameters.isEmpty()) { @@ -58,7 +62,13 @@ protected void walkPostMethodInvocation(final AstNode node) { final List parameterTypes = method.getParameters().stream() .filter(parameter -> parameter.is(Parameter.Modifier.NONE) || parameter.is(Parameter.Modifier.OPTIONAL)) // Don't check gather. - .map(Parameter::getType) + .map(parameter -> { + final AbstractType type = parameter.getType(); + if (parameter.is(Parameter.Modifier.OPTIONAL)) { + return CombinedType.combine(type, unsetType); + } + return type; + }) .collect(Collectors.toList()); // Test parameter type against argument type. diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodReturnMatchesDocTypedCheck.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodReturnMatchesDocTypedCheck.java index 74379843..d0a76113 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodReturnMatchesDocTypedCheck.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodReturnMatchesDocTypedCheck.java @@ -5,7 +5,6 @@ import java.util.Objects; import java.util.stream.Collectors; import nl.ramsolutions.sw.magik.analysis.helpers.MethodDefinitionNodeHelper; -import nl.ramsolutions.sw.magik.analysis.helpers.PackageNodeHelper; import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.LocalTypeReasoner; import nl.ramsolutions.sw.magik.analysis.typing.TypeParser; @@ -45,13 +44,11 @@ private ExpressionResult extractReasonedResult(final AstNode node) { } private ExpressionResult extractMethodDocResult(final AstNode node) { - final PackageNodeHelper packageHelper = new PackageNodeHelper(node); - final String currentPakkage = packageHelper.getCurrentPackage(); final NewDocParser docParser = new NewDocParser(node); final ITypeKeeper typeKeeper = this.getTypeKeeper(); final TypeParser typeParser = new TypeParser(typeKeeper); final List docReturnTypes = docParser.getReturnTypes().stream() - .map(typeStr -> typeParser.parseTypeString(typeStr, currentPakkage)) + .map(typeStr -> typeParser.parseTypeString(typeStr)) .collect(Collectors.toList()); return new ExpressionResult(docReturnTypes); } diff --git a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/NewDocTypeExistsTypeCheck.java b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/NewDocTypeExistsTypeCheck.java index 6bcdb58d..b433747a 100644 --- a/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/NewDocTypeExistsTypeCheck.java +++ b/magik-typed-checks/src/main/java/nl/ramsolutions/sw/magik/typedchecks/checks/NewDocTypeExistsTypeCheck.java @@ -7,6 +7,7 @@ import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeParser; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.analysis.typing.types.UndefinedType; import nl.ramsolutions.sw.magik.api.MagikGrammar; import nl.ramsolutions.sw.magik.parser.NewDocParser; @@ -43,11 +44,11 @@ private void checkDefinitionParameters(final NewDocParser newDocParser, final St final ITypeKeeper typeKeeper = this.getTypeKeeper(); final TypeParser typeParser = new TypeParser(typeKeeper); newDocParser.getParameterTypeNodes().entrySet().stream() - .filter(entry -> typeParser.parseTypeString(entry.getValue(), pakkage) == UndefinedType.INSTANCE) + .filter(entry -> typeParser.parseTypeString(entry.getValue()) == UndefinedType.INSTANCE) .forEach(entry -> { final AstNode typeNode = entry.getKey(); - final String typeName = entry.getValue(); - final String message = String.format(MESSAGE, typeName); + final TypeString typeString = entry.getValue(); + final String message = String.format(MESSAGE, typeString); this.addIssue(typeNode, message); }); } @@ -57,11 +58,11 @@ private void checkDefinitionLoops(final NewDocParser newDocParser, final String final ITypeKeeper typeKeeper = this.getTypeKeeper(); final TypeParser typeParser = new TypeParser(typeKeeper); newDocParser.getLoopTypeNodes().entrySet().stream() - .filter(entry -> typeParser.parseTypeString(entry.getValue(), pakkage) == UndefinedType.INSTANCE) + .filter(entry -> typeParser.parseTypeString(entry.getValue()) == UndefinedType.INSTANCE) .forEach(entry -> { final AstNode typeNode = entry.getKey(); - final String typeName = entry.getValue(); - final String message = String.format(MESSAGE, typeName); + final TypeString typeString = entry.getValue(); + final String message = String.format(MESSAGE, typeString); this.addIssue(typeNode, message); }); } @@ -71,11 +72,11 @@ private void checkDefinitionReturns(final NewDocParser newDocParser, final Strin final ITypeKeeper typeKeeper = this.getTypeKeeper(); final TypeParser typeParser = new TypeParser(typeKeeper); newDocParser.getReturnTypeNodes().entrySet().stream() - .filter(entry -> typeParser.parseTypeString(entry.getValue(), pakkage) == UndefinedType.INSTANCE) + .filter(entry -> typeParser.parseTypeString(entry.getValue()) == UndefinedType.INSTANCE) .forEach(entry -> { final AstNode typeNode = entry.getKey(); - final String typeName = entry.getValue(); - final String message = String.format(MESSAGE, typeName); + final TypeString typeString = entry.getValue(); + final String message = String.format(MESSAGE, typeString); this.addIssue(typeNode, message); }); } @@ -92,21 +93,18 @@ protected void walkPostProcedureInvocation(final AstNode node) { // Get slot defintions. final AstNode statementNode = node.getFirstAncestor(MagikGrammar.STATEMENT); final NewDocParser newDocParser = new NewDocParser(statementNode); - final Map slotTypeNodes = newDocParser.getSlotTypeNodes(); - - final PackageNodeHelper helper = new PackageNodeHelper(node); - final String currentPakkage = helper.getCurrentPackage(); + final Map slotTypeNodes = newDocParser.getSlotTypeNodes(); // Test slot types. slotTypeNodes.entrySet().stream() .filter(entry -> { - final AbstractType type = typeParser.parseTypeString(entry.getValue(), currentPakkage); + final AbstractType type = typeParser.parseTypeString(entry.getValue()); return type == UndefinedType.INSTANCE; }) .forEach(entry -> { final AstNode typeNode = entry.getKey(); - final String typeName = entry.getValue(); - final String message = String.format(MESSAGE, typeName); + final TypeString typeString = entry.getValue(); + final String message = String.format(MESSAGE, typeString); this.addIssue(typeNode, message); }); } diff --git a/magik-typed-checks/src/test/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypeCheckTest.java b/magik-typed-checks/src/test/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypeCheckTest.java index 863dce2a..8c1a7b4d 100644 --- a/magik-typed-checks/src/test/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypeCheckTest.java +++ b/magik-typed-checks/src/test/java/nl/ramsolutions/sw/magik/typedchecks/checks/MethodArgumentParameterTypeCheckTest.java @@ -5,11 +5,11 @@ import nl.ramsolutions.sw.magik.analysis.typing.ITypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.TypeKeeper; import nl.ramsolutions.sw.magik.analysis.typing.types.AbstractType; -import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResult; -import nl.ramsolutions.sw.magik.analysis.typing.types.GlobalReference; +import nl.ramsolutions.sw.magik.analysis.typing.types.ExpressionResultString; import nl.ramsolutions.sw.magik.analysis.typing.types.MagikType; import nl.ramsolutions.sw.magik.analysis.typing.types.Method; import nl.ramsolutions.sw.magik.analysis.typing.types.Parameter; +import nl.ramsolutions.sw.magik.analysis.typing.types.TypeString; import nl.ramsolutions.sw.magik.checks.MagikIssue; import nl.ramsolutions.sw.magik.typedchecks.MagikTypedCheck; import org.junit.jupiter.api.Test; @@ -26,16 +26,20 @@ void testArgumentTypeMatches() { final String code = "integer.m1(:symbol)"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); - final AbstractType symbolType = typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final Parameter param1 = new Parameter("p1", Parameter.Modifier.NONE, symbolType); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "m1()", List.of(param1), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final MagikTypedCheck check = new MethodArgumentParameterTypedCheck(); final List checkResults = this.runCheck(code, typeKeeper, check); @@ -47,16 +51,20 @@ void testArgumentTypeNotMatches() { final String code = "integer.m1(1)"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); - final AbstractType symbolType = typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final Parameter param1 = new Parameter("p1", Parameter.Modifier.NONE, symbolType); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "m1()", List.of(param1), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final MagikTypedCheck check = new MethodArgumentParameterTypedCheck(); final List checkResults = this.runCheck(code, typeKeeper, check); @@ -68,16 +76,20 @@ void testNoArguments() { final String code = "integer.m1()"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); - final AbstractType symbolType = typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final Parameter param1 = new Parameter("p1", Parameter.Modifier.NONE, symbolType); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "m1()", List.of(param1), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final MagikTypedCheck check = new MethodArgumentParameterTypedCheck(); final List checkResults = this.runCheck(code, typeKeeper, check); @@ -89,16 +101,20 @@ void testTooManyArguments() { final String code = "integer.m1(:symbol, :symbol)"; final ITypeKeeper typeKeeper = new TypeKeeper(); - final MagikType integerType = (MagikType) typeKeeper.getType(GlobalReference.of("sw:integer")); - final AbstractType symbolType = typeKeeper.getType(GlobalReference.of("sw:symbol")); + final TypeString integerRef = TypeString.of("sw:integer"); + final MagikType integerType = (MagikType) typeKeeper.getType(integerRef); + final TypeString symbolRef = TypeString.of("sw:symbol"); + final AbstractType symbolType = typeKeeper.getType(symbolRef); final Parameter param1 = new Parameter("p1", Parameter.Modifier.NONE, symbolType); integerType.addMethod( - EnumSet.noneOf(Method.Modifier.class), null, + EnumSet.noneOf(Method.Modifier.class), "m1()", List.of(param1), null, - ExpressionResult.UNDEFINED); + null, + ExpressionResultString.UNDEFINED, + new ExpressionResultString()); final MagikTypedCheck check = new MethodArgumentParameterTypedCheck(); final List checkResults = this.runCheck(code, typeKeeper, check); diff --git a/pom.xml b/pom.xml index 99baab4a..72343d72 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 pom Magik Tools @@ -77,9 +77,8 @@ 1.24.0.633 1.7.30 1.0.13 - 0.12.0 + 0.15.0 1.5.0 - 1.9 20211205 3.21.0 @@ -154,11 +153,6 @@ commons-cli ${commons-cli.version} - - org.apache.commons - commons-text - ${commons-text.version} - org.slf4j @@ -284,6 +278,7 @@ com/github/ngeor/checkstyle.xml true ${skipTests} + **/org/sonar/sslr/internal/matchers/AstCreator.java,**/com/sonar/sslr/api/GenericTokenType.java @@ -374,6 +369,12 @@ nl.ramsolutions.sw.magik.debugadapter.ThreadManager.stackTrace(long) nl.ramsolutions.sw.magik.debugadapter.ThreadManager.step(long, nl.ramsolutions.sw.magik.debugadapter.slap.StepType) nl.ramsolutions.sw.magik.debugadapter.slap.SlapProtocol.handleReplyMessage(java.nio.ByteBuffer) + nl.ramsolutions.sw.magik.languageserver.formatting.StandardFormattingStrategy.requireNoWhitespaceBefore(com.sonar.sslr.api.Token) + nl.ramsolutions.sw.magik.languageserver.formatting.StandardFormattingStrategy.requireWhitespaceBefore(com.sonar.sslr.api.Token) + nl.ramsolutions.sw.magik.languageserver.hover.HoverProvider.provideHover(nl.ramsolutions.sw.magik.MagikTypedFile, org.eclipse.lsp4j.Position) + nl.ramsolutions.sw.magik.languageserver.references.ReferencesProvider.provideReferences(nl.ramsolutions.sw.magik.MagikTypedFile, org.eclipse.lsp4j.Position) + nl.ramsolutions.sw.magik.languageserver.semantictokens.SemanticTokenWalker.walkPostIdentifier(com.sonar.sslr.api.AstNode) + nl.ramsolutions.sw.magik.languageserver.symbol.SymbolProvider.getSymbols(java.lang.String) nl.ramsolutions.sw.magik.lint.ConfigurationLocator.locateConfiguration() nl.ramsolutions.sw.magik.parser.MagikWhitespaceTriviaAdder.createWhitespaceTokens(com.sonar.sslr.api.Token, com.sonar.sslr.api.Token) diff --git a/sonar-magik-plugin/pom.xml b/sonar-magik-plugin/pom.xml index 7c5b6415..86c54107 100644 --- a/sonar-magik-plugin/pom.xml +++ b/sonar-magik-plugin/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 sonar-magik-plugin diff --git a/sslr-magik-toolkit/pom.xml b/sslr-magik-toolkit/pom.xml index 0f091bff..1d46b815 100644 --- a/sslr-magik-toolkit/pom.xml +++ b/sslr-magik-toolkit/pom.xml @@ -5,7 +5,7 @@ nl.ramsolutions magik-tools - 0.5.5-SNAPSHOT + 0.6.0 sslr-magik-toolkit diff --git a/sslr-magik-toolkit/src/main/java/nl/ramsolutions/sw/magik/ramsolutions/MagikConfigurationModel.java b/sslr-magik-toolkit/src/main/java/nl/ramsolutions/sw/magik/ramsolutions/MagikConfigurationModel.java index 4a3252ce..373b1260 100644 --- a/sslr-magik-toolkit/src/main/java/nl/ramsolutions/sw/magik/ramsolutions/MagikConfigurationModel.java +++ b/sslr-magik-toolkit/src/main/java/nl/ramsolutions/sw/magik/ramsolutions/MagikConfigurationModel.java @@ -50,7 +50,7 @@ public Parser getParser() { } /** - * Get {{Tokenizer}}s. + * Get {@link Tokenizer}s. */ public List getTokenizers() { return List.of( @@ -61,4 +61,3 @@ public List getTokenizers() { } } - diff --git a/type_dumper.magik b/type_dumper.magik index b64e2e09..3a44b556 100644 --- a/type_dumper.magik +++ b/type_dumper.magik @@ -4,13 +4,14 @@ _if _not sw_module_manager.module(:json).loaded? _then sw_module_manager.load_module(:json) _endif +$ + # Auxilary methods. method_finder.auto_start? << _true $ - _pragma(classify_level=restricted) _private _method method_finder.read_comment() ## Read a comment from input. @@ -60,6 +61,7 @@ _endmethod $ + _pragma(classify_level=basic, topic=type_dumper) ## Type dumper. ## @slot {sw:set} ignore_vars @@ -176,6 +178,8 @@ _pragma(classify_level=restricted, topic=type_dumper, usage=internal) _private _method type_dumper.int!run() ## Run the actual dump. ## Note that order of export is important when reading this file! + _self.pre_process() + # Write intro. _self.write_line("// self type: ", _self.self_type) _self.write_line("// undefined type: ", _self.undefined_type) @@ -232,6 +236,24 @@ _private _method type_dumper.int!run() _self.write_procedure(var) _endloop _endloop + + # Write conditions. + _self.write_line("// conditions") + _for cond _over _self.sorted_conditions() + _loop + _self.write_condition(cond) + _endloop +_endmethod +$ + +_pragma(classify_level=restricted, topic=type_dumper, usage=internal) +_private _method type_dumper.pre_process() + ## Pre process: + ## - massage some exemplars for better type dumping. + sw:auth!access.define_method_target.metadata[:exemplar_global] << @sw:auth!access + sw:alternative_access.define_method_target.metadata[:exemplar_global] << @sw:alternative_access + sw:random.define_method_target.metadata[:exemplar_global] << @sw:random + sw:sw_regexp.define_method_target.metadata[:exemplar_global] << @sw:sw_regexp _endmethod $ @@ -447,7 +469,7 @@ $ _pragma(classify_level=restricted, topic=type_dumper, usage=internal) _private _method type_dumper.write_procedure(var) - ## Write type methods. + ## Write procedure. ## @param {sw:global_variable} var _local instruction << sw:property_list.new_with(:instruction, "procedure") @@ -697,5 +719,35 @@ _private _method type_dumper.build_params(name, procedure) _endmethod $ +_pragma(classify_level=restricted, topic=type_dumper, usage=internal) +_private _iter _method type_dumper.sorted_conditions() + ## Get all conditions, sorted. + ## @loop {sw:condition} + _local templates << condition.sys!perform(:template_map) + _local conditions << sorted_collection.new_from(templates.elements, {:method_result, :name}) + _for cond _over conditions.fast_elements() + _loop + _loopbody(cond) + _endloop +_endmethod +$ + +_pragma(classify_level=restricted, topic=type_dumper, usage=internal) +_private _method type_dumper.write_condition(cond) + ## Write condition. + ## @param {sw:condition} cond + _local instruction << sw:property_list.new_with(:instruction, "condition") + instruction[:name] << cond.name + instruction[:data_name_list] << cond.data_name_list + instruction[:parent] << + _if cond.taxonomy.size > 1 + _then + >> cond.taxonomy[cond.taxonomy.size - 1] + _endif + instruction[:doc] << method_finder.get_method_comment(cond.name, "") + _self.write_instruction(instruction) +_endmethod +$ + type_dumper.new("/tmp/sw_types.jsonl").run() $