Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion eo-maven-plugin/src/main/java/org/eolang/maven/EoSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import org.cactoos.Input;
import org.cactoos.io.InputOf;
import org.eolang.parser.Canonical;
import org.eolang.parser.EoSyntax;
import org.eolang.parser.OnDefault;
import org.eolang.parser.OnDetailed;
Expand All @@ -37,6 +39,11 @@ final class EoSource {
*/
private final Input input;

/**
* Transform that parses EO into XMIR.
*/
private final UnaryOperator<XML> transform;

/**
* Ctor.
* @param identifier Object identifier
Expand All @@ -52,8 +59,29 @@ final class EoSource {
* @param input Object source code
*/
EoSource(final String identifier, final Input input) {
this(identifier, input, new Canonical());
}

/**
* Ctor.
* @param identifier Object identifier
* @param source Path to the source file
* @param transform Transform that parses EO into XMIR
*/
EoSource(final String identifier, final Path source, final UnaryOperator<XML> transform) {
this(identifier, new InputOf(source), transform);
}

/**
* Ctor.
* @param identifier Object identifier
* @param input Object source code
* @param transform Transform that parses EO into XMIR
*/
EoSource(final String identifier, final Input input, final UnaryOperator<XML> transform) {
this.identifier = identifier;
this.input = input;
this.transform = transform;
}

/**
Expand All @@ -62,7 +90,7 @@ final class EoSource {
* @throws IOException If fails
*/
Xmir parsed() throws IOException {
final XML xmir = new EoSyntax(this.input).parsed();
final XML xmir = new EoSyntax(this.input, this.transform).parsed();
final List<String> errors = new ArrayList<>(0);
final Node document = xmir.inner();
final String name = new OnDetailed(
Expand Down
53 changes: 43 additions & 10 deletions eo-maven-plugin/src/main/java/org/eolang/maven/Parsing.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@
import com.github.lombrozo.xnav.Filter;
import com.github.lombrozo.xnav.Xnav;
import com.jcabi.log.Logger;
import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.cactoos.iterable.Filtered;
import org.cactoos.text.TextOf;
import org.eolang.parser.Canonical;
import org.w3c.dom.Node;

/**
Expand Down Expand Up @@ -115,10 +118,15 @@ final class Parsing implements Step {
@Override
public void exec() {
final Collection<TjForeign> sources = this.tojos.withSources();
final int total = new Threaded<>(
new Filtered<>(TjForeign::notParsed, sources),
this::parsed
).total();
final String objects = sources.stream()
.map(TjForeign::identifier)
.filter(id -> id.contains("."))
.map(id -> id.substring(id.lastIndexOf('.') + 1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flattens every object across all packages into one set of simple names, so eo:homed homes a bare name into the current package whenever the name exists in any package. ss and tt already both define contains/index-of/last-index-of, and ss/index-of.eo uses a bare number — the day any package gains a number object, that reference silently re-homes to Φ.ss.number. Keying on the qualified name avoids this: pass the .-containing identifiers as objects unchanged, then home only when concat($package, '.', $name) matches. Same single pipeline, no simple-name collisions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 501c5d1. add-default-package.xsl now keys on the qualified name: the plugin passes the .-containing identifiers (package.name) unchanged, and a bare name is homed only when concat($package, '.', $name) is in the list. So the simple-name collision is gone — verified against eo-runtime, the bare number in ss/index-of.eo stays Φ.number (33 occurrences, 0 Φ.ss.number).


Generated by Claude Code

.distinct()
.collect(Collectors.joining(" "));
final int total = this.parsed(
sources, new Canonical(objects), Integer.toHexString(objects.hashCode())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String.hashCode is a collidable 32-bit hash, and objects is built with distinct() over withSources(), whose order isn't guaranteed. A collision gives a stale cache hit; an order flip gives a needless miss. Sorting the names and using a real digest makes this deterministic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 501c5d1. The qualified names are now sorted and hashed with SHA-256 (cactoos Sha256DigestOf + HexOf) instead of the order-sensitive, 32-bit String.hashCode, so the cache key is deterministic and collision-resistant.


Generated by Claude Code

);
if (0 == total) {
if (sources.isEmpty()) {
Logger.info(
Expand All @@ -140,13 +148,35 @@ public void exec() {
}
}

/**
* Parse all the given sources to XMIRs, concurrently.
* @param sources The sources to parse
* @param pipeline The canonical parsing transform to apply
* @param digest Digest of the set of known objects (part of the cache key)
* @return Amount of parsed tojos
*/
private int parsed(
final Collection<TjForeign> sources,
final UnaryOperator<XML> pipeline,
final String digest
) {
return new Threaded<>(
new Filtered<>(TjForeign::notParsed, sources),
tojo -> this.parsed(tojo, pipeline, digest)
).total();
}

/**
* Parse EO file to XML.
* @param tojo The tojo
* @param pipeline The canonical parsing transform to apply
* @param digest Digest of the set of known objects (part of the cache key)
* @return Amount of parsed tojos
* @throws Exception If fails
*/
private int parsed(final TjForeign tojo) throws Exception {
private int parsed(
final TjForeign tojo, final UnaryOperator<XML> pipeline, final String digest
) throws Exception {
final Path source = tojo.source();
final String name = tojo.identifier();
final Path base = this.targetDir.resolve(Parsing.DIR);
Expand All @@ -157,18 +187,18 @@ private int parsed(final TjForeign tojo) throws Exception {
new Cache(
new CachePath(
this.cacheDir.resolve(Parsing.CACHE),
this.version,
String.format("%s-%s", this.version, digest),
new TojoHash(tojo).get()
),
src -> {
final Node node = this.parsed(src, name);
final Node node = this.parsed(src, name, pipeline);
refs.add(node);
return new XMLDocument(node).toString();
}
)
).apply(source, target, base.relativize(target));
} else {
final Node node = this.parsed(source, name);
final Node node = this.parsed(source, name, pipeline);
new Saved(new XMLDocument(node).toString(), target).value();
refs.add(node);
}
Expand Down Expand Up @@ -198,11 +228,14 @@ private int parsed(final TjForeign tojo) throws Exception {
* Source parsed to {@link Node}.
* @param source Relative source path
* @param identifier Name of the EO object as tojo identifier
* @param pipeline The canonical parsing transform to apply
* @return Parsed EO object as {@link Node}
* @throws IOException If fails to parse
*/
private Node parsed(final Path source, final String identifier) throws IOException {
final EoSource.Xmir xmir = new EoSource(identifier, source).parsed();
private Node parsed(
final Path source, final String identifier, final UnaryOperator<XML> pipeline
) throws IOException {
final EoSource.Xmir xmir = new EoSource(identifier, source, pipeline).parsed();
Logger.debug(
Parsing.class,
"Parsed program '%s' from %[file]s:%n %s",
Expand Down
78 changes: 77 additions & 1 deletion eo-maven-plugin/src/test/java/org/eolang/maven/MjParseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void parsesWithCache(@Mktmp final Path temp) throws Exception {
final Path target = new Place("foo.x.main").make(base, MjAssemble.XMIR);
new Cache(
cache.resolve(Parsing.CACHE)
.resolve(FakeMaven.pluginVersion())
.resolve(MjParseTest.cacheVersion(maven.programTojo().identifier()))
.resolve(hash.value()),
src -> expected
).apply(maven.programTojo().source(), target, base.relativize(target));
Expand Down Expand Up @@ -131,6 +131,66 @@ void doesNotCrashesOnError(@Mktmp final Path temp) throws Exception {
);
}

@Test
void homesBareReferenceToSamePackageObject(@Mktmp final Path temp) throws IOException {
final FakeMaven maven = new FakeMaven(temp);
maven.withProgram(
String.join(
System.lineSeparator(),
"+package foo",
"",
"[] > bar",
" 42 > @"
),
"foo.bar",
"foo/bar.eo"
).withProgram(
String.join(
System.lineSeparator(),
"+package foo",
"",
"[] > app",
" bar > @"
),
"foo.app",
"foo/app.eo"
);
MatcherAssert.assertThat(
"bare reference 'bar' must be resolved into the same package as 'Φ.foo.bar'",
new XMLDocument(
maven.execute(new FakeMaven.Parse()).result().get(
String.format("target/%s/foo/app.%s", Parsing.DIR, MjAssemble.XMIR)
)
),
XhtmlMatchers.hasXPath("//o[@base='Φ.foo.bar']")
);
}

@Test
void keepsBareGlobalReferenceAtRoot(@Mktmp final Path temp) throws IOException {
final FakeMaven maven = new FakeMaven(temp);
maven.withProgram(
String.join(
System.lineSeparator(),
"+package foo",
"",
"[] > app",
" bar > @"
),
"foo.app",
"foo/app.eo"
);
MatcherAssert.assertThat(
"bare reference 'bar' must stay at the root when no 'Φ.foo.bar' object exists",
new XMLDocument(
maven.execute(new FakeMaven.Parse()).result().get(
String.format("target/%s/foo/app.%s", Parsing.DIR, MjAssemble.XMIR)
)
),
XhtmlMatchers.hasXPath("//o[@base='Φ.bar']")
);
}

@Test
void crashesIfWrongPackage(@Mktmp final Path temp) throws IOException {
new FakeMaven(temp).withProgram(
Expand Down Expand Up @@ -278,6 +338,22 @@ void parsesWithTargetCache(@Mktmp final Path temp) throws IOException {
);
}

/**
* The parse cache version segment for a program with a single object.
* It mirrors what {@link Parsing} computes: the plugin version plus a
* digest of the local package object names (here, the simple name of
* the only object).
* @param identifier The tojo identifier of the only registered object
* @return The version segment used as part of the parse cache path
*/
private static String cacheVersion(final String identifier) {
return String.format(
"%s-%s",
FakeMaven.pluginVersion(),
Integer.toHexString(identifier.substring(identifier.lastIndexOf('.') + 1).hashCode())
);
}

/**
* The mojo that does nothing, but executes infinitely.
* @since 0.29
Expand Down
Loading
Loading