Natjav
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -45,3 +45,6 @@ replay_pid*
|
||||
|
||||
/.mvn/
|
||||
/.idea/
|
||||
|
||||
# local symlink to external project
|
||||
/korin-natjav
|
||||
|
||||
@@ -13,7 +13,7 @@ import picocli.CommandLine.Option;
|
||||
description = "Per-language LoC/SLoC rollup of a project (plus total)")
|
||||
final class LocCommand extends AbstractProjectCommand {
|
||||
|
||||
@Option(names = "--language", description = "Filter by language (natural/java)")
|
||||
@Option(names = "--language", description = "Filter by language (natural/java/natjav)")
|
||||
@Nullable String language;
|
||||
|
||||
@Option(names = "--source-file", description = "Filter by source file")
|
||||
|
||||
@@ -59,7 +59,7 @@ final class ProjectCommand implements Callable<Integer> {
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
@Option(names = {"-l", "--language"}, required = true,
|
||||
description = "Project source language (natural/java)")
|
||||
description = "Project source language (natural/java/natjav)")
|
||||
String language;
|
||||
|
||||
@Option(names = {"-g", "--generated-dir"},
|
||||
@@ -97,7 +97,7 @@ final class ProjectCommand implements Callable<Integer> {
|
||||
description = "Directory name to skip when scanning the root (case-insensitive); repeatable")
|
||||
List<String> excludeDirs = List.of();
|
||||
|
||||
@Option(names = {"-l", "--language"}, description = "Project source language (natural/java)")
|
||||
@Option(names = {"-l", "--language"}, description = "Project source language (natural/java/natjav)")
|
||||
String language = "";
|
||||
|
||||
@Option(names = {"-g", "--generated-dir"},
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
server.url=http://localhost:8787
|
||||
# Stamped by manage-ac.sh (stamp_cli_version) from ac-code-server's agenticcode.version
|
||||
# at build time. "dev" means this jar wasn't built via manage-ac.sh.
|
||||
version=294
|
||||
version=346
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-parser-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-parser-natjav</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-neo4j-store</artifactId>
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ProjectResource {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static final List<String> SUPPORTED_LANGUAGES = List.of("natural", "java");
|
||||
private static final List<String> SUPPORTED_LANGUAGES = List.of("natural", "java", "natjav");
|
||||
|
||||
private static @Nullable String normalize(@Nullable String value) {
|
||||
return value == null || value.isBlank() ? null : value.strip();
|
||||
|
||||
@@ -11,6 +11,10 @@ import com.agenticcode.parsercore.ast.spi.LineCounter;
|
||||
import com.agenticcode.parserjava.JavaCoarseScanner;
|
||||
import com.agenticcode.parserjava.JavaLineCounter;
|
||||
import com.agenticcode.parserjava.JavaParser;
|
||||
import com.agenticcode.parsernatjav.NatJavCoarseScanner;
|
||||
import com.agenticcode.parsernatjav.NatJavDdmResolver;
|
||||
import com.agenticcode.parsernatjav.NatJavLineCounter;
|
||||
import com.agenticcode.parsernatjav.NatJavParser;
|
||||
import com.agenticcode.parsernatural.CopycodeResolver;
|
||||
import com.agenticcode.parsernatural.NaturalCoarseScanner;
|
||||
import com.agenticcode.parsernatural.NaturalLineCounter;
|
||||
@@ -39,6 +43,9 @@ public class AstIngestService {
|
||||
private final NaturalCoarseScanner naturalScanner = new NaturalCoarseScanner();
|
||||
private final LineCounter javaLineCounter = new JavaLineCounter();
|
||||
private final LineCounter naturalLineCounter = new NaturalLineCounter();
|
||||
private final NatJavParser natJavParser = new NatJavParser();
|
||||
private final NatJavCoarseScanner natJavScanner = new NatJavCoarseScanner();
|
||||
private final LineCounter natJavLineCounter = new NatJavLineCounter();
|
||||
|
||||
/**
|
||||
* Item 179 (DIAGNOSTIC): when false, {@link NodeType#COMMENT} nodes and their {@code DOCUMENTS}
|
||||
@@ -110,10 +117,24 @@ public class AstIngestService {
|
||||
*/
|
||||
public LanguageParser.ParseResult parse(SourceFiles.Language language, String sourceFile, String content,
|
||||
CopycodeResolver copycodes) {
|
||||
if (language == SourceFiles.Language.JAVA) {
|
||||
return javaParser.parse(sourceFile, content);
|
||||
}
|
||||
return naturalParser.parse(sourceFile, content, copycodes);
|
||||
return parse(language, sourceFile, content, copycodes, NatJavDdmResolver.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link #parse(SourceFiles.Language, String, String, CopycodeResolver)}, with the project's
|
||||
* NatJav DDM index (item D) so a view resolves to its table and columns. Ignored by the other
|
||||
* languages, which have no DDM concept.
|
||||
*/
|
||||
public LanguageParser.ParseResult parse(SourceFiles.Language language, String sourceFile, String content,
|
||||
CopycodeResolver copycodes, NatJavDdmResolver ddms) {
|
||||
// Exhaustive switch, not an if/else with a Natural fallthrough: a language added without a
|
||||
// parser must fail to compile, not silently hand its files to the Natural parser and store
|
||||
// a plausible-looking but wrong graph.
|
||||
return switch (language) {
|
||||
case JAVA -> javaParser.parse(sourceFile, content);
|
||||
case NATJAV -> natJavParser.parse(sourceFile, content, ddms);
|
||||
case NATURAL -> naturalParser.parse(sourceFile, content, copycodes);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,10 +152,20 @@ public class AstIngestService {
|
||||
*/
|
||||
public LanguageParser.ParseResult coarseScan(SourceFiles.Language language, String sourceFile, String content,
|
||||
CopycodeResolver copycodes) {
|
||||
if (language == SourceFiles.Language.JAVA) {
|
||||
return javaScanner.scan(sourceFile, content);
|
||||
}
|
||||
return naturalScanner.scan(sourceFile, content, copycodes);
|
||||
return coarseScan(language, sourceFile, content, copycodes, NatJavDdmResolver.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link #coarseScan(SourceFiles.Language, String, String, CopycodeResolver)}, with the
|
||||
* project's NatJav DDM index.
|
||||
*/
|
||||
public LanguageParser.ParseResult coarseScan(SourceFiles.Language language, String sourceFile, String content,
|
||||
CopycodeResolver copycodes, NatJavDdmResolver ddms) {
|
||||
return switch (language) {
|
||||
case JAVA -> javaScanner.scan(sourceFile, content);
|
||||
case NATJAV -> natJavScanner.scan(sourceFile, content, ddms);
|
||||
case NATURAL -> naturalScanner.scan(sourceFile, content, copycodes);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +173,11 @@ public class AstIngestService {
|
||||
* the Tier-1 coarse scanners use, so a module's metrics are identical at any ingest depth.
|
||||
*/
|
||||
public LocMetrics count(SourceFiles.Language language, String content) {
|
||||
LineCounter counter = language == SourceFiles.Language.JAVA ? javaLineCounter : naturalLineCounter;
|
||||
LineCounter counter = switch (language) {
|
||||
case JAVA -> javaLineCounter;
|
||||
case NATJAV -> natJavLineCounter;
|
||||
case NATURAL -> naturalLineCounter;
|
||||
};
|
||||
return counter.count(content);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.agenticcode.codeserver.service;
|
||||
|
||||
import com.agenticcode.parsernatjav.NatJavDdm;
|
||||
import com.agenticcode.parsernatjav.NatJavDdmResolver;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A NatJav project's data definition modules, backing {@link NatJavDdmResolver}. Built once per
|
||||
* ingest from the project's {@code *_DDM.java} files (243 of them in the reference corpus), so a
|
||||
* view can be given the table and column names that live in the DDM rather than in the view's own
|
||||
* declaration.
|
||||
*
|
||||
* <p>The same split as {@link CopycodeLibrary} on the Natural side: the parser SPI stays
|
||||
* file-agnostic and the ingest layer, which has filesystem access, supplies the index. Unlike
|
||||
* copycodes these files <em>are</em> ingested in their own right — a DDM is a module with its
|
||||
* columns — so this index is a second, cheap read of files the walk already knows about.
|
||||
*/
|
||||
final class NatJavDdmLibrary implements NatJavDdmResolver {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(NatJavDdmLibrary.class);
|
||||
|
||||
/**
|
||||
* Filename suffix of a generated DDM class. Every {@code NaturalDdm} subclass in the corpus
|
||||
* carries it and nothing else does, so the scan need not read non-DDM files at all.
|
||||
*/
|
||||
private static final String SUFFIX = "_DDM.java";
|
||||
|
||||
/**
|
||||
* Filename suffix of a generated data area, whose {@code @ViewOf} declarations are indexed so a
|
||||
* program using the area can resolve a statement's operand to a table.
|
||||
*/
|
||||
private static final String AREA_SUFFIX = "_DA.java";
|
||||
|
||||
/**
|
||||
* {@code @ViewOf <ddm> <field> = view(...)} inside a data area — read off the text rather than
|
||||
* parsed, because only two identifiers are needed and the area may hold thousands of fields.
|
||||
*/
|
||||
private static final java.util.regex.Pattern AREA_VIEW = java.util.regex.Pattern.compile(
|
||||
"@ViewOf\\s+([\\w.]+)\\s+([\\w$]+)\\s*=\\s*view\\s*\\(");
|
||||
|
||||
/**
|
||||
* A data area's field declaration, reduced to what item E1 needs: the Java identifier and the
|
||||
* Natural name literal the transpiler preserved. Read off the text rather than parsed — the
|
||||
* corpus holds 739,346 of these and only two identifiers per line are wanted.
|
||||
*/
|
||||
private static final java.util.regex.Pattern AREA_FIELD = java.util.regex.Pattern.compile(
|
||||
"@L\\d\\s+(?:@\\w+\\s+[\\w.]+\\s+|Group\\s+|[A-Z]\\s+)?([\\w$]+)\\s*=\\s*[A-Za-z]+\\("
|
||||
+ "\\s*(?:\\d+\\s*(?:,\\s*\\d+\\s*)?,\\s*)?\"([^\"]+)\"");
|
||||
|
||||
private final Map<String, Ddm> byClassName;
|
||||
|
||||
/**
|
||||
* {@code <areaClass>#<viewField>} → the DDM class that view reads.
|
||||
*/
|
||||
private final Map<String, String> areaViews;
|
||||
|
||||
/**
|
||||
* {@code <areaClass>#<javaFieldName>} → the field's Natural name.
|
||||
*/
|
||||
private final Map<String, String> areaFields;
|
||||
|
||||
private NatJavDdmLibrary(Map<String, Ddm> byClassName, Map<String, String> areaViews,
|
||||
Map<String, String> areaFields) {
|
||||
this.byClassName = byClassName;
|
||||
this.areaViews = areaViews;
|
||||
this.areaFields = areaFields;
|
||||
}
|
||||
|
||||
private static String areaViewKey(String areaClassName, String viewName) {
|
||||
return areaClassName.toUpperCase(java.util.Locale.ROOT) + "#" + viewName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param candidates the project's ingestible files
|
||||
* @return the index, or {@link NatJavDdmResolver#NONE} when the project holds no DDM
|
||||
*/
|
||||
static NatJavDdmResolver scan(List<ProjectIngestService.Candidate> candidates) {
|
||||
Map<String, Ddm> byClassName = new HashMap<>();
|
||||
Map<String, String> areaViews = new HashMap<>();
|
||||
Map<String, String> areaFields = new HashMap<>();
|
||||
for (ProjectIngestService.Candidate candidate : candidates) {
|
||||
if (candidate.kind().language() != SourceFiles.Language.NATJAV) {
|
||||
continue;
|
||||
}
|
||||
String fileName = candidate.file().getFileName().toString();
|
||||
String className = SourceFiles.stem(candidate.file());
|
||||
try {
|
||||
if (fileName.endsWith(SUFFIX)) {
|
||||
Ddm ddm = NatJavDdm.parse(SourceFiles.read(candidate.file()), className);
|
||||
if (ddm != null) {
|
||||
byClassName.put(className, ddm);
|
||||
}
|
||||
} else if (fileName.endsWith(AREA_SUFFIX)) {
|
||||
String text = SourceFiles.read(candidate.file());
|
||||
java.util.regex.Matcher matcher = AREA_VIEW.matcher(text);
|
||||
while (matcher.find()) {
|
||||
String ddmClass = matcher.group(1);
|
||||
int dot = ddmClass.lastIndexOf('.');
|
||||
areaViews.put(areaViewKey(className, matcher.group(2)),
|
||||
dot >= 0 ? ddmClass.substring(dot + 1) : ddmClass);
|
||||
}
|
||||
java.util.regex.Matcher fields = AREA_FIELD.matcher(text);
|
||||
while (fields.find()) {
|
||||
areaFields.putIfAbsent(areaViewKey(className, fields.group(1)), fields.group(2));
|
||||
}
|
||||
}
|
||||
} catch (IOException | RuntimeException e) {
|
||||
// Unreadable or unparseable here is reported as a failure when the file is ingested;
|
||||
// the views referring to it simply resolve no columns.
|
||||
LOG.debugf("NatJav DDM '%s' could not be indexed: %s", className, e.toString());
|
||||
}
|
||||
}
|
||||
// All three, not just the DDMs: a project may hold data areas without a single *_DDM.java,
|
||||
// and bailing on the DDM count alone threw away the areaViews/areaFields just computed —
|
||||
// silently costing every cross-area dataflow edge, with no log line to say so.
|
||||
if (byClassName.isEmpty() && areaViews.isEmpty() && areaFields.isEmpty()) {
|
||||
return NatJavDdmResolver.NONE;
|
||||
}
|
||||
LOG.infof("NatJav: indexed %d DDMs, %d data-area views and %d data-area fields",
|
||||
byClassName.size(), areaViews.size(), areaFields.size());
|
||||
return new NatJavDdmLibrary(byClassName, areaViews, areaFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Ddm resolve(String ddmClassName) {
|
||||
return byClassName.get(ddmClassName.toUpperCase(java.util.Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String resolveAreaField(String areaClassName, String javaFieldName) {
|
||||
return areaFields.get(areaViewKey(areaClassName, javaFieldName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Ddm resolveAreaView(String areaClassName, String viewName) {
|
||||
String ddmClass = areaViews.get(areaViewKey(areaClassName, viewName));
|
||||
return ddmClass == null ? null : resolve(ddmClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package com.agenticcode.codeserver.service;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Disambiguates NatJav module names that two files claim, before the ingest either merges them into
|
||||
* one ambiguous node or drops both as duplicates.
|
||||
*
|
||||
* <p>A NatJav module's identity is the Natural name from its {@code @Natural(moduleName=...)}, which
|
||||
* is right for the other 22,790 files of the reference corpus but leaves two collision classes:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Library homonyms.</b> Natural module names are unique <em>per library</em>, not globally,
|
||||
* so {@code KORIN} and {@code ABFRAGE} may each hold an {@code ACOMMON} (and each its own GDA
|
||||
* {@code COMMON}). Both are real, different modules. They are qualified with the library —
|
||||
* the top-level package — as {@code KORIN.ACOMMON}, and only where the collision actually
|
||||
* occurs, so the other 22,790 modules stay addressable by their bare Natural name.</li>
|
||||
* <li><b>A copied header the migration did not update.</b> {@code ZB80BM6.java} declares
|
||||
* {@code moduleName="ZB80BM5"} — provably wrong: {@code ZB80BM5.java} exists, and the two files
|
||||
* differ in 178 of ~570 lines. Three module pairs and three data-area pairs in the corpus are
|
||||
* like this. Here the class name is the trustworthy half, so the file whose class name matches
|
||||
* the declared name keeps it and the impostor falls back to its own.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The distinction between the two is which file <em>owns</em> the name: the one whose class name
|
||||
* (less the transpiler's {@code _DA}/{@code _DDM} suffix) is the declared name. Exactly one owner
|
||||
* means the others are impostors; none or several means the declaration is not in dispute and the
|
||||
* files differ by library instead.
|
||||
*
|
||||
* <p>This has to be a cross-file pass — a parser looking at one file cannot know a name is contested
|
||||
* — so it reads only each file's head, which is where the annotation and the class declaration are.
|
||||
*/
|
||||
final class NatJavIdentities {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(NatJavIdentities.class);
|
||||
|
||||
/**
|
||||
* Property recording the {@code moduleName} a file declared but did not get, so the contradiction
|
||||
* stays visible in the graph instead of being silently corrected away.
|
||||
*/
|
||||
static final String NAME_CONFLICT = "naturalNameConflict";
|
||||
|
||||
/**
|
||||
* Bytes of each file read to find the annotation. The generated header (licence comment, package,
|
||||
* import, Javadoc, annotation, class declaration) is well under this in every corpus file.
|
||||
*/
|
||||
private static final int HEAD_BYTES = 8192;
|
||||
|
||||
private static final Pattern MODULE_NAME =
|
||||
Pattern.compile("@Natural\\s*\\(\\s*moduleName\\s*=\\s*\"([^\"]+)\"");
|
||||
|
||||
private NatJavIdentities() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the rename map for a NatJav project.
|
||||
*
|
||||
* @param root the project root
|
||||
* @param candidates every ingestible file of the project (the whole walk, so a collision is seen
|
||||
* even when only one of the two files is being ingested)
|
||||
* @return project-relative source file → the name its module node must carry instead of the one
|
||||
* it declares. Empty for a project with no contested name, and for every non-NatJav project.
|
||||
*/
|
||||
static Map<String, Rename> scan(Path root, List<ProjectIngestService.Candidate> candidates) {
|
||||
Map<String, List<Claim>> byDeclaredName = new LinkedHashMap<>();
|
||||
for (ProjectIngestService.Candidate candidate : candidates) {
|
||||
if (candidate.kind().language() != SourceFiles.Language.NATJAV) {
|
||||
continue;
|
||||
}
|
||||
Claim claim = claimOf(root, candidate.file());
|
||||
byDeclaredName.computeIfAbsent(claim.declaredName(), n -> new ArrayList<>()).add(claim);
|
||||
}
|
||||
|
||||
Map<String, Rename> renames = new HashMap<>();
|
||||
byDeclaredName.forEach((declaredName, claims) -> {
|
||||
if (claims.size() < 2) {
|
||||
return;
|
||||
}
|
||||
List<Claim> owners = claims.stream().filter(c -> c.ownsDeclaredName(declaredName)).toList();
|
||||
if (owners.size() == 1) {
|
||||
for (Claim claim : claims) {
|
||||
if (claim != owners.get(0)) {
|
||||
renames.put(claim.sourceFile(), new Rename(claim.baseName(), declaredName));
|
||||
LOG.infof("NatJav: '%s' also declares moduleName '%s', which %s owns — ingesting it as '%s'",
|
||||
claim.sourceFile(), declaredName, owners.get(0).sourceFile(), claim.baseName());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Not a contested declaration: the files agree, they just live in different libraries.
|
||||
long libraries = claims.stream().map(Claim::library).distinct().count();
|
||||
// A file directly in the project root has no library, and qualifying it would name the
|
||||
// module ".ACOMMON" — an identity every later query would have to be handed verbatim.
|
||||
boolean allNamed = claims.stream().noneMatch(c -> c.library().isEmpty());
|
||||
if (allNamed && libraries == claims.size()) {
|
||||
for (Claim claim : claims) {
|
||||
renames.put(claim.sourceFile(), new Rename(claim.library() + "." + declaredName, null));
|
||||
}
|
||||
}
|
||||
// Same library and no single owner: nothing here distinguishes the files, so they are left
|
||||
// to the duplicate detection, which reports them rather than binding one arbitrarily.
|
||||
});
|
||||
return renames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return every name a NatJav file's module answers to besides its filename stem: the Natural
|
||||
* name from its {@code @Natural(moduleName=...)}, and the renamed identity where this pass gave
|
||||
* it one. The by-name ingest indexes candidates by stem — the <em>Java class</em> name — while
|
||||
* the graph exposes the Natural name, so without this a targeted ingest of {@code V-MIT04} or
|
||||
* {@code WSAIVP} reports "unresolved" for a module the graph carries under exactly that name.
|
||||
*/
|
||||
static Map<String, String> graphNames(Path root, List<ProjectIngestService.Candidate> candidates,
|
||||
Map<String, Rename> renames) {
|
||||
Map<String, String> byFile = new HashMap<>();
|
||||
for (ProjectIngestService.Candidate candidate : candidates) {
|
||||
if (candidate.kind().language() != SourceFiles.Language.NATJAV) {
|
||||
continue;
|
||||
}
|
||||
Claim claim = claimOf(root, candidate.file());
|
||||
Rename rename = renames.get(claim.sourceFile());
|
||||
byFile.put(claim.sourceFile(), rename != null ? rename.name() : claim.declaredName());
|
||||
}
|
||||
return byFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@code renames} to the module/data-structure shell of one parse result.
|
||||
*
|
||||
* @return {@code result} unchanged when this file is not renamed
|
||||
*/
|
||||
static ParseResult apply(ParseResult result, String sourceFile, Map<String, Rename> renames) {
|
||||
Rename rename = renames.get(sourceFile);
|
||||
if (rename == null) {
|
||||
return result;
|
||||
}
|
||||
List<AstNode> nodes = new ArrayList<>(result.nodes().size());
|
||||
for (AstNode node : result.nodes()) {
|
||||
nodes.add(isShell(node, sourceFile) ? renamed(node, rename) : node);
|
||||
}
|
||||
return new ParseResult(nodes, result.edges());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether {@code node} is the file's own shell — the node standing for the file, which is
|
||||
* the only one whose name this pass may change. Identified the way the parser builds it: it
|
||||
* carries the file's path, starts at line 1 and holds the {@code className} property.
|
||||
*/
|
||||
private static boolean isShell(AstNode node, String sourceFile) {
|
||||
return (node.type() == NodeType.MODULE || node.type() == NodeType.DATA_STRUCTURE)
|
||||
&& node.sourceFile().equals(sourceFile)
|
||||
&& node.startLine() == 1
|
||||
&& node.properties() != null
|
||||
&& node.properties().containsKey("className");
|
||||
}
|
||||
|
||||
private static AstNode renamed(AstNode node, Rename rename) {
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
if (node.properties() != null) {
|
||||
properties.putAll(node.properties());
|
||||
}
|
||||
if (rename.conflictingName() != null) {
|
||||
properties.put(NAME_CONFLICT, rename.conflictingName());
|
||||
}
|
||||
return new AstNode(node.id(), node.type(), rename.name(), node.sourceFile(), node.language(),
|
||||
node.startLine(), node.endLine(), node.dataType(), node.value(), properties);
|
||||
}
|
||||
|
||||
private static Claim claimOf(Path root, Path file) {
|
||||
String className = SourceFiles.stem(file);
|
||||
String head = head(file);
|
||||
Matcher matcher = MODULE_NAME.matcher(head);
|
||||
String declaredName = matcher.find() ? matcher.group(1) : className;
|
||||
Path relative = root.relativize(file);
|
||||
String library = relative.getNameCount() > 1 ? relative.getName(0).toString() : "";
|
||||
return new Claim(relative.toString().replace('\\', '/'), className, declaredName, library);
|
||||
}
|
||||
|
||||
private static String head(Path file) {
|
||||
try (var stream = Files.newInputStream(file)) {
|
||||
return new String(stream.readNBytes(HEAD_BYTES), StandardCharsets.ISO_8859_1);
|
||||
} catch (IOException e) {
|
||||
// Unreadable here means unreadable at parse time too, where it is reported as a failure.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One file's claim to a module name.
|
||||
*
|
||||
* @param sourceFile the project-relative path
|
||||
* @param className the Java class name (the filename stem, upper-cased by {@link SourceFiles})
|
||||
* @param declaredName the {@code @Natural(moduleName=...)}, or the class name if it declares none
|
||||
* @param library the top-level directory, i.e. the Natural library
|
||||
*/
|
||||
private record Claim(String sourceFile, String className, String declaredName, String library) {
|
||||
|
||||
/**
|
||||
* @return the class name without the transpiler's data-area / DDM suffix — what the class name
|
||||
* says the Natural name is
|
||||
*/
|
||||
String baseName() {
|
||||
for (String suffix : List.of("_DA", "_DDM")) {
|
||||
if (className.endsWith(suffix)) {
|
||||
return className.substring(0, className.length() - suffix.length());
|
||||
}
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this file is the natural owner of {@code declaredName}: its own class name
|
||||
* says so, independently of the annotation that is in dispute
|
||||
*/
|
||||
boolean ownsDeclaredName(String name) {
|
||||
return baseName().equalsIgnoreCase(name.toUpperCase(Locale.ROOT))
|
||||
|| baseName().equals(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The name a file's module node must carry instead of the one it declares.
|
||||
*
|
||||
* @param name the replacement name
|
||||
* @param conflictingName the declared name that was rejected because another file owns it, or
|
||||
* {@code null} when the rename is a library qualification rather than a
|
||||
* correction
|
||||
*/
|
||||
record Rename(String name, @Nullable String conflictingName) {
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.agenticcode.neo4jstore.graph.ProjectInfo;
|
||||
import com.agenticcode.neo4jstore.graph.ProjectIngestInfo;
|
||||
import com.agenticcode.parsercore.ast.model.*;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import com.agenticcode.parsernatjav.NatJavDdmResolver;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -188,9 +189,20 @@ public class ProjectIngestService {
|
||||
*/
|
||||
private static List<Identity> fileIdentities(Parsed p) {
|
||||
if (p.candidate().kind().kind() == NodeType.DATA_STRUCTURE) {
|
||||
// The stem is the data area's identity for Natural (the file IS named after the area) but
|
||||
// not for NatJav, whose file is named after the Java class: `WSK386P_DA.java` holds the
|
||||
// area `WSK385P`. Keying on the stem there checked a name the graph does not use, so two
|
||||
// areas colliding under their real name passed the guard and landed as one ambiguous
|
||||
// node — WSK385P, WSSC31P and YLRY401A each did exactly that.
|
||||
if (p.candidate().kind().language() == SourceFiles.Language.NATJAV) {
|
||||
return natJavShellIdentities(p);
|
||||
}
|
||||
String stem = SourceFiles.stem(p.candidate().file());
|
||||
return List.of(new Identity("DATA_STRUCTURE " + stem, stem, NodeType.DATA_STRUCTURE));
|
||||
}
|
||||
if (p.candidate().kind().language() == SourceFiles.Language.NATJAV) {
|
||||
return natJavShellIdentities(p);
|
||||
}
|
||||
return p.result().nodes().stream()
|
||||
.filter(node -> node.type() == NodeType.MODULE && !node.sourceFile().isEmpty())
|
||||
.map(node -> identity(node, p.candidate().kind().language()))
|
||||
@@ -198,20 +210,45 @@ public class ProjectIngestService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the identity of a NatJav file, taken from the shell node the parser actually produced
|
||||
* rather than re-deduced from the filename.
|
||||
*
|
||||
* <p>The two can disagree: {@code SourceFiles.classify} decides MODULE vs DATA_STRUCTURE from the
|
||||
* {@code _DA} suffix while the parser decides it from the {@code extends} target. A {@code *_DA.java}
|
||||
* that does not extend {@code NaturalData}, or a {@code NaturalData} class named otherwise, then
|
||||
* matched neither branch's filter and contributed <b>no</b> identity at all — so a genuine name
|
||||
* collision on that file went unreported.
|
||||
*/
|
||||
private static List<Identity> natJavShellIdentities(Parsed p) {
|
||||
return p.result().nodes().stream()
|
||||
.filter(node -> (node.type() == NodeType.MODULE || node.type() == NodeType.DATA_STRUCTURE)
|
||||
&& !node.sourceFile().isEmpty()
|
||||
&& node.startLine() == 1
|
||||
&& node.properties() != null
|
||||
&& node.properties().containsKey("className"))
|
||||
.map(node -> new Identity(node.type().name() + " " + node.name(), node.name(), node.type()))
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 24: reads and parses one candidate file, applying the shell/user-exit metric enrichment.
|
||||
* Pure per-file work (no shared mutable state), so it is safe to run on a virtual thread per file.
|
||||
*/
|
||||
private Parsed parseCandidate(Candidate candidate, Path root, boolean coarse,
|
||||
CopycodeLibrary copycodes, Map<String, LocMetrics> userExit,
|
||||
ProjectInfo project) throws IOException {
|
||||
ProjectInfo project,
|
||||
Map<String, NatJavIdentities.Rename> natJavRenames,
|
||||
NatJavDdmResolver ddms) throws IOException {
|
||||
String content = SourceFiles.read(candidate.file());
|
||||
String sourceFile = relativeSourceFile(root, candidate.file());
|
||||
ParseResult result = coarse
|
||||
? astIngestService.coarseScan(candidate.kind().language(), sourceFile, content, copycodes)
|
||||
: withShellMetrics(astIngestService.parse(candidate.kind().language(), sourceFile, content, copycodes),
|
||||
content, candidate.kind().language());
|
||||
? astIngestService.coarseScan(candidate.kind().language(), sourceFile, content, copycodes, ddms)
|
||||
: withShellMetrics(astIngestService.parse(candidate.kind().language(), sourceFile, content,
|
||||
copycodes, ddms), content, candidate.kind().language());
|
||||
result = withUserExitMetrics(result, project.generatedDir(), userExit);
|
||||
result = NatJavIdentities.apply(result, sourceFile, natJavRenames);
|
||||
return new Parsed(candidate, result);
|
||||
}
|
||||
|
||||
@@ -237,7 +274,8 @@ public class ProjectIngestService {
|
||||
*/
|
||||
private static final List<String> DEFAULT_EXCLUDE_DIRS = List.of("target");
|
||||
|
||||
private static List<Candidate> walk(Path root, List<String> excludeDirs) throws IOException {
|
||||
private static List<Candidate> walk(Path root, List<String> excludeDirs,
|
||||
@Nullable String projectLanguage) throws IOException {
|
||||
List<String> effectiveExcludes = new ArrayList<>(excludeDirs);
|
||||
for (String dir : DEFAULT_EXCLUDE_DIRS) {
|
||||
if (!effectiveExcludes.contains(dir)) {
|
||||
@@ -253,7 +291,7 @@ public class ProjectIngestService {
|
||||
if (SourceFiles.isExcluded(root, file, effectiveExcludes)) {
|
||||
continue;
|
||||
}
|
||||
SourceFiles.Kind kind = SourceFiles.classify(file);
|
||||
SourceFiles.Kind kind = SourceFiles.classify(file, projectLanguage);
|
||||
if (kind != null) {
|
||||
candidates.add(new Candidate(file, kind));
|
||||
}
|
||||
@@ -268,11 +306,40 @@ public class ProjectIngestService {
|
||||
* name a by-name walk reports as a duplicate.
|
||||
*/
|
||||
private static Map<NameKey, List<Candidate>> nameIndex(List<Candidate> candidates) {
|
||||
return nameIndex(candidates, Map.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link #nameIndex(List)}, additionally indexing each NatJav file under the name its module
|
||||
* carries in the graph. The stem is the Java class name ({@code V_MIT04.java}); the graph knows
|
||||
* the module as {@code V-MIT04}, so a seed given the name every query uses would otherwise never
|
||||
* resolve. Both keys point at the same candidate.
|
||||
*
|
||||
* @param graphNames project-relative source file → the module's graph name, from
|
||||
* {@link NatJavIdentities#graphNames}
|
||||
*/
|
||||
private static Map<NameKey, List<Candidate>> nameIndex(List<Candidate> candidates,
|
||||
Map<String, String> graphNames) {
|
||||
Map<NameKey, List<Candidate>> index = new HashMap<>();
|
||||
for (Candidate candidate : candidates) {
|
||||
index.computeIfAbsent(new NameKey(candidate.kind().kind(), SourceFiles.stem(candidate.file())),
|
||||
String stem = SourceFiles.stem(candidate.file());
|
||||
index.computeIfAbsent(new NameKey(candidate.kind().kind(), stem),
|
||||
k -> new ArrayList<>()).add(candidate);
|
||||
}
|
||||
graphNames.forEach((sourceFile, graphName) -> {
|
||||
for (Candidate candidate : candidates) {
|
||||
if (!candidate.file().toString().replace('\\', '/').endsWith(sourceFile)
|
||||
|| graphName.equalsIgnoreCase(SourceFiles.stem(candidate.file()))) {
|
||||
continue;
|
||||
}
|
||||
List<Candidate> under = index.computeIfAbsent(
|
||||
new NameKey(candidate.kind().kind(), graphName.toUpperCase(Locale.ROOT)),
|
||||
k -> new ArrayList<>());
|
||||
if (!under.contains(candidate)) {
|
||||
under.add(candidate);
|
||||
}
|
||||
}
|
||||
});
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -461,7 +528,7 @@ public class ProjectIngestService {
|
||||
// must be reported, not accepted: it was silently listed as examined while
|
||||
// nothing about it could ever be ingested, which is precisely the "I ingested
|
||||
// 2 of your 3 files" invisibility this list exists to prevent.
|
||||
|| SourceFiles.classify(root.resolve(sf).normalize()) == null)
|
||||
|| SourceFiles.classify(root.resolve(sf).normalize(), project.language()) == null)
|
||||
.toList();
|
||||
List<String> ingestable = requested.stream().filter(sf -> !unresolved.contains(sf)).toList();
|
||||
if (ingestable.isEmpty()) {
|
||||
@@ -493,12 +560,19 @@ public class ProjectIngestService {
|
||||
markIngestStarted(project, coarse ? "tier1" : level.name().toLowerCase(Locale.ROOT));
|
||||
Path root = Path.of(project.root());
|
||||
List<String> excludeDirs = ingestExcludeDirs(project);
|
||||
List<Candidate> allCandidates = walk(root, excludeDirs);
|
||||
List<Candidate> allCandidates = walk(root, excludeDirs, project.language());
|
||||
// NatJav: two files may claim one module name (a library homonym, or a copied header the
|
||||
// migration never updated). Decided once over the whole walk, before anything is parsed —
|
||||
// a single file cannot tell that its name is contested. Empty for every other language.
|
||||
Map<String, NatJavIdentities.Rename> natJavRenames = NatJavIdentities.scan(root, allCandidates);
|
||||
// NatJav: a view names only its DDM class; the table and column names live in that file.
|
||||
NatJavDdmResolver ddms = NatJavDdmLibrary.scan(allCandidates);
|
||||
CopycodeLibrary copycodes = CopycodeLibrary.scan(root, excludeDirs);
|
||||
// Item 129: opt-in skip of files whose content is byte-identical to what the graph holds.
|
||||
List<Candidate> candidates = changedOnly ? changedCandidates(project, root, excludeDirs, allCandidates)
|
||||
: allCandidates;
|
||||
Map<String, LocMetrics> userExit = UserExitMetrics.scan(root, project.userExitDir(), project.excludeDirs(), astIngestService);
|
||||
Map<String, LocMetrics> userExit = UserExitMetrics.scan(root, project.userExitDir(), project.excludeDirs(), project.language(),
|
||||
astIngestService);
|
||||
|
||||
List<String> examinedFiles = candidates.stream()
|
||||
.map(c -> relativeSourceFile(root, c.file()))
|
||||
@@ -513,7 +587,7 @@ public class ProjectIngestService {
|
||||
try (ExecutorService parseExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
List<Future<Parsed>> futures = candidates.stream()
|
||||
.map(candidate -> parseExecutor.submit(
|
||||
() -> parseCandidate(candidate, root, coarse, copycodes, userExit, project)))
|
||||
() -> parseCandidate(candidate, root, coarse, copycodes, userExit, project, natJavRenames, ddms)))
|
||||
.toList();
|
||||
for (int i = 0; i < futures.size(); i++) {
|
||||
try {
|
||||
@@ -630,7 +704,9 @@ public class ProjectIngestService {
|
||||
// fixtures* that its Java walk never ingests, so they had no stored hash, counted as changed,
|
||||
// and disabled skipping entirely — 487 of 509 unchanged files re-parsed. An unknown language
|
||||
// (a legacy project) keeps the conservative behaviour.
|
||||
if (!"java".equalsIgnoreCase(project.language() == null ? "" : project.language())
|
||||
if (!SourceFiles.Language.JAVA.name().equalsIgnoreCase(project.language() == null ? "" : project.language())
|
||||
&& !SourceFiles.NATJAV_PROJECT_LANGUAGE.equalsIgnoreCase(
|
||||
project.language() == null ? "" : project.language())
|
||||
&& copycodeChanged(root, excludeDirs, stored)) {
|
||||
LOG.infof("changedOnly refresh of '%s': a copycode changed, so every file is re-parsed "
|
||||
+ "(copycode text is inlined at parse time; skipping would keep stale expansions)",
|
||||
@@ -753,8 +829,11 @@ public class ProjectIngestService {
|
||||
int depthLimit = Math.min(Math.max(1, maxDepth != null ? maxDepth : defaultDeepDepth), maxDeepDepth);
|
||||
int nodeLimit = Math.max(1, maxNodes != null ? maxNodes : defaultDeepNodes);
|
||||
Path root = Path.of(project.root());
|
||||
List<Candidate> candidates = walk(root, ingestExcludeDirs(project));
|
||||
Map<NameKey, List<Candidate>> index = nameIndex(candidates);
|
||||
List<Candidate> candidates = walk(root, ingestExcludeDirs(project), project.language());
|
||||
Map<String, NatJavIdentities.Rename> natJavRenames = NatJavIdentities.scan(root, candidates);
|
||||
NatJavDdmResolver ddms = NatJavDdmLibrary.scan(candidates);
|
||||
Map<NameKey, List<Candidate>> index =
|
||||
nameIndex(candidates, NatJavIdentities.graphNames(root, candidates, natJavRenames));
|
||||
// Reverse index (Java): interface/base simple name -> files that implement/extend it, so a
|
||||
// targeted ingest also pulls in the implementations/subclasses of any interface it reaches.
|
||||
Map<String, List<Candidate>> implementorsByBase = buildImplementorIndex(root, candidates);
|
||||
@@ -767,7 +846,7 @@ public class ProjectIngestService {
|
||||
String label = seeds.size() == 1
|
||||
? "'" + moduleNames.iterator().next() + "'"
|
||||
: seeds.size() + " modules";
|
||||
return bfsIngest(project, root, index, implementorsByBase, seeds, depthLimit, nodeLimit, label);
|
||||
return bfsIngest(project, root, index, implementorsByBase, natJavRenames, ddms, seeds, depthLimit, nodeLimit, label);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -787,8 +866,11 @@ public class ProjectIngestService {
|
||||
@Nullable Integer maxNodes) throws IOException {
|
||||
int nodeLimit = Math.max(1, maxNodes != null ? maxNodes : defaultDeepNodes);
|
||||
Path root = Path.of(project.root());
|
||||
List<Candidate> candidates = walk(root, ingestExcludeDirs(project));
|
||||
Map<NameKey, List<Candidate>> index = nameIndex(candidates);
|
||||
List<Candidate> candidates = walk(root, ingestExcludeDirs(project), project.language());
|
||||
Map<String, NatJavIdentities.Rename> natJavRenames = NatJavIdentities.scan(root, candidates);
|
||||
NatJavDdmResolver ddms = NatJavDdmLibrary.scan(candidates);
|
||||
Map<NameKey, List<Candidate>> index =
|
||||
nameIndex(candidates, NatJavIdentities.graphNames(root, candidates, natJavRenames));
|
||||
Map<String, List<Candidate>> implementorsByBase = buildImplementorIndex(root, candidates);
|
||||
Map<String, Candidate> byRelPath = new HashMap<>();
|
||||
for (Candidate candidate : candidates) {
|
||||
@@ -806,7 +888,7 @@ public class ProjectIngestService {
|
||||
if (seeds.isEmpty()) {
|
||||
return new IngestSummary(0, List.of(), List.of(), List.of(), List.of(), null);
|
||||
}
|
||||
return bfsIngest(project, root, index, implementorsByBase, seeds, defaultDeepDepth, nodeLimit,
|
||||
return bfsIngest(project, root, index, implementorsByBase, natJavRenames, ddms, seeds, defaultDeepDepth, nodeLimit,
|
||||
seeds.size() + " module(s)");
|
||||
}
|
||||
|
||||
@@ -863,10 +945,13 @@ public class ProjectIngestService {
|
||||
private IngestSummary bfsIngest(ProjectInfo project, Path root,
|
||||
Map<NameKey, List<Candidate>> index,
|
||||
Map<String, List<Candidate>> implementorsByBase,
|
||||
Map<String, NatJavIdentities.Rename> natJavRenames,
|
||||
NatJavDdmResolver ddms,
|
||||
List<QueuedRef> seeds, int depthLimit, int nodeLimit, String label) {
|
||||
long startedAt = System.nanoTime();
|
||||
CopycodeLibrary copycodes = CopycodeLibrary.scan(root, ingestExcludeDirs(project));
|
||||
Map<String, LocMetrics> userExit = UserExitMetrics.scan(root, project.userExitDir(), project.excludeDirs(), astIngestService);
|
||||
Map<String, LocMetrics> userExit = UserExitMetrics.scan(root, project.userExitDir(), project.excludeDirs(), project.language(),
|
||||
astIngestService);
|
||||
int ingested = 0;
|
||||
// Item 62 (summary surface): collected as refs, not strings, so the data-literal false
|
||||
// positives can be filtered once the whole tree is walked — see filterDataLiteralRefs.
|
||||
@@ -922,9 +1007,10 @@ public class ProjectIngestService {
|
||||
String content = SourceFiles.read(candidate.file());
|
||||
String sourceFile = relativeSourceFile(root, candidate.file());
|
||||
examinedFiles.add(sourceFile);
|
||||
ParseResult result = withUserExitMetrics(withShellMetrics(
|
||||
astIngestService.parse(candidate.kind().language(), sourceFile, content, copycodes),
|
||||
content, candidate.kind().language()), project.generatedDir(), userExit);
|
||||
ParseResult result = NatJavIdentities.apply(withUserExitMetrics(withShellMetrics(
|
||||
astIngestService.parse(candidate.kind().language(), sourceFile, content, copycodes, ddms),
|
||||
content, candidate.kind().language()), project.generatedDir(), userExit),
|
||||
sourceFile, natJavRenames);
|
||||
LOG.infof("Ingesting %s into project '%s' [%s]", moduleNames(result), project.name(), sourceFile);
|
||||
// Full deep parse of this module — reconcile so a by-name refresh purges its stale
|
||||
// nodes (renamed/removed fields) rather than shadowing them (item 58).
|
||||
@@ -1080,7 +1166,7 @@ public class ProjectIngestService {
|
||||
return implementorsByBase;
|
||||
}
|
||||
|
||||
private record Candidate(Path file, SourceFiles.Kind kind) {
|
||||
record Candidate(Path file, SourceFiles.Kind kind) {
|
||||
}
|
||||
|
||||
private record Parsed(Candidate candidate, ParseResult result) {
|
||||
|
||||
@@ -20,6 +20,9 @@ import java.util.Locale;
|
||||
* {@code MODULE}s and {@code .lda}/{@code .pda}/{@code .gda} data areas (local/parameter/global) are
|
||||
* {@link NodeType#DATA_STRUCTURE}s. Files with any other extension are not ingestible and classify
|
||||
* to {@code null}.
|
||||
*
|
||||
* <p>NatJav (Natural transpiled to Java) breaks the extension-only rule: its files are {@code .java},
|
||||
* so the <em>project's</em> declared language decides — see {@link #classify(Path, String)}.
|
||||
*/
|
||||
final class SourceFiles {
|
||||
|
||||
@@ -27,12 +30,44 @@ final class SourceFiles {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the classification of {@code file} by extension, or {@code null} if it is not an
|
||||
* ingestible source file (e.g. {@code .meta}).
|
||||
* The {@code language} of a project whose {@code .java} files are transpiled Natural rather than
|
||||
* hand-written Java, and must therefore be read by the NatJav parser.
|
||||
*/
|
||||
static final String NATJAV_PROJECT_LANGUAGE = "natjav";
|
||||
|
||||
/**
|
||||
* Suffix the maxenso transpiler appends to a Natural data area's class name. Every one of the
|
||||
* 5,818 {@code NaturalData} classes in the reference corpus carries it and no other class does,
|
||||
* so it classifies a file the way {@code .lda}/{@code .pda}/{@code .gda} does for Natural. The
|
||||
* <em>name</em> of the resulting node still comes from the parser, which reads the class's
|
||||
* {@code @Natural(moduleName=...)} — the suffix is not stripped by string surgery here.
|
||||
*/
|
||||
private static final String NATJAV_DATA_AREA_SUFFIX = "_da.java";
|
||||
|
||||
/**
|
||||
* As {@link #classify(Path, String)} for a caller with no project in scope (so {@code .java} is
|
||||
* hand-written Java).
|
||||
*/
|
||||
static @Nullable Kind classify(Path file) {
|
||||
return classify(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file the file to classify
|
||||
* @param projectLanguage the owning project's declared {@code language}, or {@code null} if the
|
||||
* caller has no project in scope. Only {@code "natjav"} changes the
|
||||
* outcome: a NatJav file is a {@code .java} file, so the extension alone
|
||||
* cannot distinguish it from hand-written Java and the project must say.
|
||||
* @return the classification of {@code file}, or {@code null} if it is not an ingestible source
|
||||
* file (e.g. {@code .meta})
|
||||
*/
|
||||
static @Nullable Kind classify(Path file, @Nullable String projectLanguage) {
|
||||
String name = file.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
if (name.endsWith(".java")) {
|
||||
if (NATJAV_PROJECT_LANGUAGE.equalsIgnoreCase(projectLanguage)) {
|
||||
return new Kind(Language.NATJAV,
|
||||
name.endsWith(NATJAV_DATA_AREA_SUFFIX) ? NodeType.DATA_STRUCTURE : NodeType.MODULE);
|
||||
}
|
||||
return new Kind(Language.JAVA, NodeType.MODULE);
|
||||
}
|
||||
if (name.endsWith(".nat") || name.endsWith(".nsn")) {
|
||||
@@ -84,7 +119,12 @@ final class SourceFiles {
|
||||
return false;
|
||||
}
|
||||
|
||||
enum Language {JAVA, NATURAL}
|
||||
/**
|
||||
* The parser a source file is read by. {@code NATJAV} is Natural transpiled to Java: its files
|
||||
* are syntactically Java but map onto the Natural half of the unified AST, which is why it is a
|
||||
* language of its own rather than a flavour of {@code JAVA}.
|
||||
*/
|
||||
enum Language {JAVA, NATURAL, NATJAV}
|
||||
|
||||
/**
|
||||
* Classification of an ingestible source file.
|
||||
|
||||
@@ -33,12 +33,16 @@ final class UserExitMetrics {
|
||||
|
||||
/**
|
||||
* @return module stem (upper-case) → the user-exit file's LoC/SLoC, for every ingestible file
|
||||
* under a {@code userExitDir} path component. Empty when {@code userExitDir} is {@code null} or the
|
||||
* under a {@code userExitDir} path component. {@code projectLanguage} is what tells a NatJav
|
||||
* project's {@code .java} files apart from hand-written Java, so the metrics are counted by the
|
||||
* right {@code LineCounter}. Empty when {@code userExitDir} is {@code null} or the
|
||||
* root cannot be walked. When two user-exit trees hold the same stem, the first in sorted walk
|
||||
* order wins (deterministic; narrow the scope via {@code excludeDirs} if undesired).
|
||||
*/
|
||||
static Map<String, LocMetrics> scan(Path root, @org.jspecify.annotations.Nullable String userExitDir,
|
||||
List<String> excludeDirs, AstIngestService astIngestService) {
|
||||
List<String> excludeDirs,
|
||||
@org.jspecify.annotations.Nullable String projectLanguage,
|
||||
AstIngestService astIngestService) {
|
||||
Map<String, LocMetrics> byStem = new HashMap<>();
|
||||
if (userExitDir == null || userExitDir.isBlank()) {
|
||||
return byStem;
|
||||
@@ -53,7 +57,7 @@ final class UserExitMetrics {
|
||||
if (!hasComponent(root, file, userExitDir) || SourceFiles.isExcluded(root, file, otherExcludes)) {
|
||||
continue;
|
||||
}
|
||||
SourceFiles.Kind kind = SourceFiles.classify(file);
|
||||
SourceFiles.Kind kind = SourceFiles.classify(file, projectLanguage);
|
||||
if (kind == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ quarkus.http.port=8787
|
||||
# AgenticCode's own release counter (not the Maven project version) — bump this by hand for each
|
||||
# release. Single source of truth for the startup log line, GET /api/version, and the OpenAPI
|
||||
# info version (referenced below via property expression, not duplicated).
|
||||
agenticcode.version=294
|
||||
agenticcode.version=346
|
||||
# OpenAPI / Swagger UI (item 48) — the generated spec is the contract the web-UI TS client
|
||||
# is generated against. Served at /q/openapi (yaml/json); Swagger UI at /q/swagger-ui in dev.
|
||||
mp.openapi.extensions.smallrye.info.title=AgenticCode API
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.agenticcode.codeserver.api;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.RestAssured;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* The first NatJav end-to-end test (roadmap item 187): a six-file NatJav project goes through
|
||||
* project creation, a whole-root deep refresh and the REST API. Until this class existed, every
|
||||
* NatJav behaviour was verified by parser unit tests and by the live reference corpus only.
|
||||
*
|
||||
* <p>The root reproduces the corpus's shape where it matters: libraries are top-level directories,
|
||||
* {@code COMMON_DA.java} exists in two of them ({@code KORIN}, {@code ABFRAGE}), and a program in a
|
||||
* third library ({@code CSTENTW}) includes the KORIN one — the case a directory heuristic would bind
|
||||
* wrong and the {@code @Using} qualifier binds right (item 184). One assertion per fix it protects.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class NatJavIngestIT {
|
||||
|
||||
private static final String PROJECT = "natjav-it";
|
||||
private static final String API = "/api/projects/" + PROJECT;
|
||||
|
||||
@TempDir
|
||||
static Path root;
|
||||
|
||||
@BeforeAll
|
||||
static void ingest() {
|
||||
RestAssured.port = Integer.getInteger("quarkus.http.test-port", 8081);
|
||||
// Libraries are directories; the same class name in two of them is the homonym under test.
|
||||
copyFixture("fixtures/natjav/COMMON_DA.java", "KORIN/COMMON_DA.java");
|
||||
copyFixture("fixtures/natjav/abfrage/COMMON_DA.java", "ABFRAGE/COMMON_DA.java");
|
||||
copyFixture("fixtures/natjav/V_MIT04.java", "KORIN/Mitarbeiter/Verwaltung/V_MIT04.java");
|
||||
copyFixture("fixtures/natjav/YPUMPRUE_DA.java", "KORIN/Mitarbeiter/Verwaltung/YPUMPRUE_DA.java");
|
||||
copyFixture("fixtures/natjav/UMKTEST.java", "KORIN/UMKTEST.java");
|
||||
copyFixture("fixtures/natjav/DISKTEST.java", "CSTENTW/DISKTEST.java");
|
||||
|
||||
given().contentType("application/json")
|
||||
.body(new ProjectResource.ProjectRequest(null, root.toString(), null, "natjav", null, null))
|
||||
.when().post(API)
|
||||
.then().statusCode(201);
|
||||
deepRefresh();
|
||||
}
|
||||
|
||||
private static void deepRefresh() {
|
||||
given().when().post(API + "/refresh?deep=true").then().statusCode(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike the Natural fixtures, these need their directory: the top-level one is the library, which
|
||||
* {@code NatJavIdentities} qualifies homonyms by.
|
||||
*/
|
||||
private static void copyFixture(String classpathResource, String relativeTarget) {
|
||||
try (InputStream in = NatJavIngestIT.class.getClassLoader().getResourceAsStream(classpathResource)) {
|
||||
if (in == null) {
|
||||
throw new IllegalStateException("Resource not found: " + classpathResource);
|
||||
}
|
||||
Path target = root.resolve(relativeTarget);
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.write(target, in.readAllBytes());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String dataStructures(String module) {
|
||||
return given().when().get(API + "/modules/" + module + "/data-structures")
|
||||
.then().statusCode(200).extract().asString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Item A: the module answers to its Natural name, not to the Java class name it was parsed from.
|
||||
* Looked up by file — there is no bare {@code GET /modules/{name}}, only sub-resources — and then
|
||||
* addressed by that name, which is what every sub-resource call below relies on.
|
||||
*/
|
||||
@Test
|
||||
void moduleIsAddressedByItsNaturalName() {
|
||||
given().when().get(API + "/modules?sourceFile=KORIN/Mitarbeiter/Verwaltung/V_MIT04.java")
|
||||
.then().statusCode(200)
|
||||
.body("size()", equalTo(1))
|
||||
.body("[0].name", equalTo("V-MIT04"))
|
||||
.body("[0].moduleKind", equalTo("SUBPROGRAM"));
|
||||
given().when().get(API + "/modules/V-MIT04/functions")
|
||||
.then().statusCode(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 184: {@code COMMON_DA} has two real candidates. A module in a THIRD library includes
|
||||
* {@code KORIN.COMMON_DA}, and the qualifier — not the module's own directory — decides.
|
||||
*/
|
||||
@Test
|
||||
void homonymResolvesToTheLibraryTheUsingNames() {
|
||||
given().when().get(API + "/modules/DISKTEST/data-structures")
|
||||
.then().statusCode(200)
|
||||
.body("find { it.name == 'KORIN.COMMON' }.relationship", equalTo("USING"))
|
||||
.body("find { it.name == 'KORIN.COMMON' }.area", equalTo("GDA"))
|
||||
.body("find { it.name == 'KORIN.COMMON' }.sourceFile", equalTo("KORIN/COMMON_DA.java"))
|
||||
.body("find { it.name == 'KORIN.COMMON' }.fieldCount", greaterThan(0))
|
||||
.body("findAll { it.name == 'ABFRAGE.COMMON' }", hasSize(0))
|
||||
.body("findAll { it.name == 'COMMON_DA' }", hasSize(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 184, the unqualified form: {@code @Using COMMON_DA} carries no package, so only Java's
|
||||
* same-package rule — the including file's own {@code package KORIN;} — can pick a candidate.
|
||||
* With one candidate the class name alone would do; this needs the homonym to be load-bearing.
|
||||
*/
|
||||
@Test
|
||||
void unqualifiedUsingResolvesThroughTheFilesOwnPackage() {
|
||||
given().when().get(API + "/modules/UMKTEST/data-structures")
|
||||
.then().statusCode(200)
|
||||
.body("find { it.name == 'KORIN.COMMON' }.area", equalTo("GDA"))
|
||||
.body("findAll { it.name == 'ABFRAGE.COMMON' }", hasSize(0))
|
||||
.body("find { it.name == 'YPUMPRUE' }.area", equalTo("LDA"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 186: {@code area} comes from the {@code @Using} scope on the INCLUDES edge, not from a file
|
||||
* extension a {@code .java} area does not have — and only for a resolved definition.
|
||||
*/
|
||||
@Test
|
||||
void areaComesFromTheUsingScopeOnResolvedRowsOnly() {
|
||||
given().when().get(API + "/modules/DISKTEST/data-structures")
|
||||
.then().statusCode(200)
|
||||
.body("find { it.name == 'YPUMPRUE' }.area", equalTo("LDA"))
|
||||
.body("find { it.name == 'YPUMPRUE' }.sourceFile", equalTo("KORIN/Mitarbeiter/Verwaltung/YPUMPRUE_DA.java"))
|
||||
.body("find { it.name == 'YPUMPRUE' }.fieldCount", greaterThan(0))
|
||||
// Unresolved: the file is not in the project, so UNKNOWN keeps meaning exactly that.
|
||||
.body("find { it.name == 'NOFILE_DA' }.area", equalTo("UNKNOWN"))
|
||||
.body("find { it.name == 'NOFILE_DA' }.sourceFile", nullValue())
|
||||
.body("find { it.name == 'NOFILE_DA' }.fieldCount", equalTo(0));
|
||||
// A PARAMETER include whose area is not in the project is unresolved too - scope alone must
|
||||
// not promote it to PDA.
|
||||
given().when().get(API + "/modules/V-MIT04/data-structures")
|
||||
.then().statusCode(200)
|
||||
.body("find { it.name == 'GWSTGP_DA' }.area", equalTo("UNKNOWN"))
|
||||
.body("find { it.name == 'GWSTGP_DA' }.sourceFile", nullValue());
|
||||
}
|
||||
|
||||
/** Item B: {@code callnat("MITZ1R", ...)} is a CALLNAT to a module the project does not hold. */
|
||||
@Test
|
||||
void callnatTargetIsAnUnresolvedCallee() {
|
||||
given().when().get(API + "/modules/V-MIT04/callees")
|
||||
.then().statusCode(200)
|
||||
.body("items.find { it.name == 'MITZ1R' }.unresolved", equalTo(true))
|
||||
.body("items.find { it.name == 'MITZ1R' }.edgeKind", equalTo("CALLNAT"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 183: a second deep refresh over unchanged input must leave the graph as it found it. The
|
||||
* leak this guards against minted one placeholder DATA_STRUCTURE per ingest, which the identifier
|
||||
* search would show as a second hit for the same name.
|
||||
*/
|
||||
@Test
|
||||
void secondDeepRefreshLeavesTheGraphIdentical() {
|
||||
String disktestBefore = dataStructures("DISKTEST");
|
||||
String umktestBefore = dataStructures("UMKTEST");
|
||||
|
||||
deepRefresh();
|
||||
|
||||
assertEquals(disktestBefore, dataStructures("DISKTEST"), "DISKTEST's data structures changed on re-ingest");
|
||||
assertEquals(umktestBefore, dataStructures("UMKTEST"), "UMKTEST's data structures changed on re-ingest");
|
||||
given().when().get(API + "/search/identifier?name=YPUMPRUE&type=DATA_STRUCTURE")
|
||||
.then().statusCode(200)
|
||||
.body("size()", equalTo(1))
|
||||
.body("[0].sourceFile", not(emptyString()));
|
||||
given().when().get(API + "/search/identifier?name=KORIN.COMMON&type=DATA_STRUCTURE")
|
||||
.then().statusCode(200)
|
||||
.body("size()", equalTo(1));
|
||||
// The class-name placeholder was renamed, redirected and, having no children, reaped.
|
||||
given().when().get(API + "/search/identifier?name=COMMON_DA&type=DATA_STRUCTURE")
|
||||
.then().statusCode(200)
|
||||
.body("size()", equalTo(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.agenticcode.codeserver.service;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* The two ways two NatJav files end up claiming one module name, and the rule that tells them apart.
|
||||
* The fixtures reproduce the reference corpus's real cases by name.
|
||||
*/
|
||||
class NatJavIdentitiesTest {
|
||||
|
||||
@TempDir
|
||||
Path root;
|
||||
|
||||
/**
|
||||
* Writes the head of a generated NatJav file — the annotation and the class declaration are all
|
||||
* the scan reads.
|
||||
*/
|
||||
private Path file(String relativePath, @Nullable String moduleName, String baseClass) throws IOException {
|
||||
Path path = root.resolve(relativePath);
|
||||
Path parent = requireNonNull(path.getParent());
|
||||
Files.createDirectories(parent);
|
||||
String className = path.getFileName().toString().replace(".java", "");
|
||||
String annotation = moduleName == null ? "@Natural" : "@Natural(moduleName=\"" + moduleName + "\")";
|
||||
Files.writeString(path, "package " + parent.getFileName() + ";\n\n"
|
||||
+ "import innowake.mee.natural.api.*;\n\n"
|
||||
+ annotation + "\npublic class " + className + " extends " + baseClass + " {\n}\n");
|
||||
return path;
|
||||
}
|
||||
|
||||
private static NatJavIdentities.Rename require(Map<String, NatJavIdentities.Rename> renames, String key) {
|
||||
NatJavIdentities.Rename rename = renames.get(key);
|
||||
assertNotNull(rename, "expected a rename for " + key + ", got " + renames.keySet());
|
||||
return rename;
|
||||
}
|
||||
|
||||
private Map<String, NatJavIdentities.Rename> scan(List<Path> files) {
|
||||
List<ProjectIngestService.Candidate> candidates = new ArrayList<>();
|
||||
for (Path path : files) {
|
||||
NodeType kind = path.getFileName().toString().endsWith("_DA.java")
|
||||
? NodeType.DATA_STRUCTURE : NodeType.MODULE;
|
||||
candidates.add(new ProjectIngestService.Candidate(path,
|
||||
new SourceFiles.Kind(SourceFiles.Language.NATJAV, kind)));
|
||||
}
|
||||
return NatJavIdentities.scan(root, candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural module names are unique per library, not globally: {@code KORIN} and {@code ABFRAGE}
|
||||
* may each hold an {@code ACOMMON}. Both are real, so both are kept — qualified by library.
|
||||
*/
|
||||
@Test
|
||||
void libraryHomonymsAreQualifiedByLibrary() throws IOException {
|
||||
Path korin = file("KORIN/ACOMMON.java", null, "NaturalProgram");
|
||||
Path abfrage = file("ABFRAGE/ACOMMON.java", null, "NaturalProgram");
|
||||
|
||||
Map<String, NatJavIdentities.Rename> renames = scan(List.of(korin, abfrage));
|
||||
|
||||
assertEquals("KORIN.ACOMMON", require(renames, "KORIN/ACOMMON.java").name());
|
||||
assertEquals("ABFRAGE.ACOMMON", require(renames, "ABFRAGE/ACOMMON.java").name());
|
||||
assertNull(require(renames, "KORIN/ACOMMON.java").conflictingName(),
|
||||
"a library qualification is not a correction — nothing was in dispute");
|
||||
}
|
||||
|
||||
/**
|
||||
* The GDA {@code COMMON} exists in both libraries as {@code COMMON_DA.java}; the qualification
|
||||
* uses the Natural name, not the class name.
|
||||
*/
|
||||
@Test
|
||||
void dataAreaHomonymsAreQualifiedToo() throws IOException {
|
||||
Path korin = file("KORIN/COMMON_DA.java", "COMMON", "NaturalData");
|
||||
Path abfrage = file("ABFRAGE/COMMON_DA.java", "COMMON", "NaturalData");
|
||||
|
||||
Map<String, NatJavIdentities.Rename> renames = scan(List.of(korin, abfrage));
|
||||
|
||||
assertEquals("KORIN.COMMON", require(renames, "KORIN/COMMON_DA.java").name());
|
||||
assertEquals("ABFRAGE.COMMON", require(renames, "ABFRAGE/COMMON_DA.java").name());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code ZB80BM6.java} declaring {@code moduleName="ZB80BM5"} is a copied header: {@code
|
||||
* ZB80BM5.java} exists and owns the name. The impostor is ingested under its own name, and the
|
||||
* declaration it lost is recorded rather than dropped.
|
||||
*/
|
||||
@Test
|
||||
void copiedHeaderLosesToTheFileThatOwnsTheName() throws IOException {
|
||||
Path owner = file("KORIN/Praemienkonto/ZB80BM5.java", null, "NaturalProgram");
|
||||
Path impostor = file("KORIN/Praemienkonto/ZB80BM6.java", "ZB80BM5", "NaturalProgram");
|
||||
|
||||
Map<String, NatJavIdentities.Rename> renames = scan(List.of(owner, impostor));
|
||||
|
||||
assertNull(renames.get("KORIN/Praemienkonto/ZB80BM5.java"), "the owner keeps its name");
|
||||
NatJavIdentities.Rename rename = require(renames, "KORIN/Praemienkonto/ZB80BM6.java");
|
||||
assertEquals("ZB80BM6", rename.name());
|
||||
assertEquals("ZB80BM5", rename.conflictingName());
|
||||
}
|
||||
|
||||
/**
|
||||
* The same defect in a data area: {@code WSK386P_DA.java} declares {@code WSK385P}, which
|
||||
* {@code WSK385P_DA.java} owns. The suffix the transpiler appends is not part of the name.
|
||||
*/
|
||||
@Test
|
||||
void copiedHeaderInADataAreaFallsBackToTheClassNameWithoutTheSuffix() throws IOException {
|
||||
Path owner = file("KORIN/Webservices/WSK385P_DA.java", "WSK385P", "NaturalData");
|
||||
Path impostor = file("KORIN/Webservices/WSK386P_DA.java", "WSK385P", "NaturalData");
|
||||
|
||||
Map<String, NatJavIdentities.Rename> renames = scan(List.of(owner, impostor));
|
||||
|
||||
assertNull(renames.get("KORIN/Webservices/WSK385P_DA.java"));
|
||||
assertEquals("WSK386P", require(renames, "KORIN/Webservices/WSK386P_DA.java").name());
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUncontestedNameIsNeverRenamed() throws IOException {
|
||||
Path a = file("KORIN/V_MIT04.java", "V-MIT04", "NaturalSubprogram");
|
||||
Path b = file("KORIN/XABBRUCH.java", "#ABBRUCH", "NaturalProgram");
|
||||
|
||||
assertTrue(scan(List.of(a, b)).isEmpty(), "nothing collides, so nothing is renamed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Two files in the <em>same</em> library with no owner between them are genuinely
|
||||
* indistinguishable. Renaming one would invent a distinction, so both are left for the duplicate
|
||||
* detection to report.
|
||||
*/
|
||||
@Test
|
||||
void indistinguishableClaimsAreLeftToDuplicateDetection() throws IOException {
|
||||
Path a = file("KORIN/AAA.java", "SHARED", "NaturalProgram");
|
||||
Path b = file("KORIN/BBB.java", "SHARED", "NaturalProgram");
|
||||
|
||||
assertTrue(scan(List.of(a, b)).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonNatJavCandidatesAreIgnored() throws IOException {
|
||||
Path path = file("KORIN/ACOMMON.java", null, "NaturalProgram");
|
||||
List<ProjectIngestService.Candidate> javaCandidates = List.of(
|
||||
new ProjectIngestService.Candidate(path,
|
||||
new SourceFiles.Kind(SourceFiles.Language.JAVA, NodeType.MODULE)));
|
||||
|
||||
assertTrue(NatJavIdentities.scan(root, javaCandidates).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
/**
|
||||
* Unit tests for {@link SourceFiles#classify} — the extension → (language, node kind) mapping that
|
||||
* gates which files the ingester picks up. Pins the Natural data-area extensions incl. {@code .gda}
|
||||
* (roadmap item 46c) and that unknown extensions are not ingestible.
|
||||
* (roadmap item 46c), that unknown extensions are not ingestible, and that a {@code .java} file is
|
||||
* read as NatJav only when its project declares that language.
|
||||
*/
|
||||
class SourceFilesTest {
|
||||
|
||||
@@ -38,6 +39,45 @@ class SourceFilesTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NatJav is the one case the extension cannot decide: its files are {@code .java}. Without a
|
||||
* project language they must stay plain Java, or every Java project would change meaning.
|
||||
*/
|
||||
@Test
|
||||
void javaStaysJavaWithoutANatJavProject() {
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.JAVA, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("KORIN/ACOMMON.java"), null));
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.JAVA, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("KORIN/ACOMMON.java"), "java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void natJavProjectReadsJavaFilesAsNatJav() {
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.NATJAV, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("KORIN/ACOMMON.java"), "natjav"));
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.NATJAV, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("KORIN/ACOMMON.java"), "NatJav"), "language match is case-insensitive");
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code _DA} suffix is to NatJav what {@code .lda}/{@code .pda}/{@code .gda} is to Natural:
|
||||
* the only file-level signal that this is a data area rather than a program.
|
||||
*/
|
||||
@Test
|
||||
void natJavDataAreasAreDataStructures() {
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.NATJAV, NodeType.DATA_STRUCTURE),
|
||||
SourceFiles.classify(Path.of("KORIN/COMMON_DA.java"), "natjav"));
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.NATJAV, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("SYSTEM/BANKEZ_DDM.java"), "natjav"),
|
||||
"a DDM is a module, not a data area");
|
||||
}
|
||||
|
||||
@Test
|
||||
void naturalExtensionsAreUnaffectedByTheProjectLanguage() {
|
||||
assertEquals(new SourceFiles.Kind(SourceFiles.Language.NATURAL, NodeType.MODULE),
|
||||
SourceFiles.classify(Path.of("WSUBPX0S.nat"), "natjav"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownExtensionsAreNotIngestible() {
|
||||
assertNull(SourceFiles.classify(Path.of("WNAUTD0S.meta")));
|
||||
|
||||
652
ac-code-server/src/test/resources/fixtures/natjav/COMMON_DA.java
Normal file
652
ac-code-server/src/test/resources/fixtures/natjav/COMMON_DA.java
Normal file
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="COMMON")
|
||||
public class COMMON_DA extends NaturalData {
|
||||
|
||||
@Global
|
||||
/*
|
||||
* GDA für LIBRARY KORIN INCLUSIVE
|
||||
* REDEFINITIONEN
|
||||
*/
|
||||
@L1 A gPROGRAMM = A(8 ,"+PROGRAMM");
|
||||
@L1 A gBPOL_PARM = A(17 ,"+BPOL-PARM");
|
||||
@L1 N gBBST_POLNR = N(9,0 ,"+BBST-POLNR");
|
||||
@L1 A gBBST_ART = A(9 ,"+BBST-ART");
|
||||
@L1 A gABTEILUNG = A(5 ,"+ABTEILUNG");
|
||||
@L1 A gPASSWORD = A(8 ,"+PASSWORD");
|
||||
@L1 A gGETMAIL = A(1 ,"+GETMAIL");
|
||||
@L1 A gERROR_PROGRAM = A(8 ,"+ERROR-PROGRAM");
|
||||
@L1 A gERROR_STAT = A(1 ,"+ERROR-STAT");
|
||||
@L1 N gERROR_LINE = N(4,0 ,"+ERROR-LINE");
|
||||
@L1 N gERROR_NR = N(4,0 ,"+ERROR-NR");
|
||||
@L1 A gERGEBNIS = A(79 ,"+ERGEBNIS");
|
||||
@L1 A gSPOOL_PARM = A(80 ,"+SPOOL-PARM");
|
||||
@L1 Redefine gSPOOL_PARM;
|
||||
@L2 A xSPUSER = A(8 ,"#SPUSER");
|
||||
@L2 A xSPDEST = A(8 ,"#SPDEST");
|
||||
@L2 A xSPFORM = A(1 ,"#SPFORM");
|
||||
@L1 A gINIT_VAR = A(240 ,"+INIT-VAR");
|
||||
@L1 Redefine gINIT_VAR; // REDEF. BEGIN : +INIT-VAR
|
||||
@L2 A xRA_INITVAR_GETMAIL = A(1 ,"#RA-INITVAR-GETMAIL");// GETMAIL duchgeführt Y/N
|
||||
@L2 N xINIT_DATIME = N(15,0 ,"#INIT-DATIME");// STARTUP Datum/Zeitder
|
||||
@L2 Redefine xINIT_DATIME; // letzten Startup-Durchführung
|
||||
@L3 N xINIT_DATE = N(8,0 ,"#INIT-DATE");// "YYYYMMDDHHMMSSZ"
|
||||
@L3 Redefine xINIT_DATE;
|
||||
@L4 A xINIT_DATE_ALPHA = A(8 ,"#INIT-DATE-ALPHA");
|
||||
@L3 N xINIT_TIME = N(7,0 ,"#INIT-TIME");
|
||||
@L3 Redefine xINIT_TIME;
|
||||
@L4 A xINIT_TIME_ALPHA = A(7 ,"#INIT-TIME-ALPHA");
|
||||
@L2 A xINIT_USER = A(8 ,"#INIT-USER"); // STARTUP U-ID d. Current LOGON
|
||||
@L2 A xINIT_ID = A(8 ,"#INIT-ID"); // STARTUP Terminal-ID
|
||||
@L2 A xINIT_GROUP = A(8 ,"#INIT-GROUP"); // STARTUP GROUP-ID, über die
|
||||
/* /* gelinkt */
|
||||
@L2 A xSTARTUP_PROGRAM = A(8 ,"#STARTUP-PROGRAM");// STARTUP Startup-Programm
|
||||
@L2 A xRESTART_PROGRAM = A(8 ,"#RESTART-PROGRAM");// STARTUP Restart-Programm
|
||||
@L2 A xERROR_PROGRAM = A(8 ,"#ERROR-PROGRAM");// #ERR letztes fehlerhaftes PRG
|
||||
/* /* STARTUP Error-Programm */
|
||||
@L2 A xRA_INITVAR_CICS_NAME = A(8 ,"#RA-INITVAR-CICS-NAME");// STARTUP und APPLID
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME; // "CSWVCICS"/"CSWVCICT"
|
||||
@L3 A xCICSNAME = A(8 ,"#CICSNAME"); // Redef. aus Namensgründen
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME;
|
||||
@L3 A xCICS_NAME = A(8 ,"#CICS-NAME"); // Redef. aus Namensgründen
|
||||
@L2 A FILLER = A(8 ,"FILLER");
|
||||
@L2 A xUSER_NAME = A(32 ,"#USER-NAME"); // STARTUP User-Name
|
||||
@L2 A xAPPL_NAME = A(32 ,"#APPL-NAME"); // STARTUP Anwendungs-Name
|
||||
@L2 A xSYS_SCHALTER = A(8 ,"#SYS-SCHALTER");// DIV SYSTEM-Schalter
|
||||
@L2 Redefine xSYS_SCHALTER;
|
||||
@L3 A xSYS_RESTART = A(1 ,"#SYS-RESTART");// #ERR bei Absturz Restart Y/N
|
||||
@L3 A xSYS_CANCEL = A(1 ,"#SYS-CANCEL");// #CAN BATCH CANCELNin #ERROR
|
||||
@L3 A xERROR_STAT = A(1 ,"#ERROR-STAT");// #ERR letzter Fehler-Status
|
||||
@L3 A xERROR_OPTN = A(1 ,"#ERROR-OPTN");// #ERR RETRY ON ERRORS Y/N
|
||||
@L3 A xERROR_SAME = A(1 ,"#ERROR-SAME");// #ERR same ERROR wie zuvor Y/N
|
||||
/* 0=NEIN 1=PROG 2=NUMMER 3=BEIDE */
|
||||
@L3 A xERROR_LOG = A(1 ,"#ERROR-LOG"); // #ERR N=kein ERROR-Logging
|
||||
@L3 A xMONITOR = A(1 ,"#MONITOR"); // STARTUP B=BATCH, C=CICS
|
||||
/* /* P=COMPLETE */
|
||||
@L3 A FILLER2 = A(1 ,"FILLER2");
|
||||
@L2 A xERROR_PROG = A(8 ,"#ERROR-PROG");
|
||||
@L2 N xERROR_NR = N(4,0 ,"#ERROR-NR"); // #ERR letzte Fehlernummer
|
||||
@L2 Redefine xERROR_NR;
|
||||
@L3 A xERRAX = A(4 ,"#ERRAX");
|
||||
@L2 N xERROR_LINE = N(4,0 ,"#ERROR-LINE"); // #ERR letzte fehlerhafte Zeile
|
||||
@L2 A xMESSAGE = A(72 ,"#MESSAGE"); // #ERR Fehlermeldung
|
||||
@L2 Redefine xMESSAGE;
|
||||
@L3 A xMANF = A(1 ,"#MANF");
|
||||
@L3 A STAR = A(1 ,"STAR");
|
||||
@L3 A RESTMSG = A(70 ,"RESTMSG");
|
||||
@L1 A gBERECHTIGUNGEN = A("+BERECHTIGUNGEN");
|
||||
@L1 A gVSU = A(250 ,"+VSU");
|
||||
@L1 A gKIS = A(250 ,"+KIS");
|
||||
@L1 Redefine gKIS; // REDEF. BEGIN : +KIS
|
||||
@L2 N xRN_KIS_ZAHL = N(3,0 ,"#RN-KIS-ZAHL");
|
||||
@L2 B xRA_KIS_KUISNT = B(3 ,"#RA-KIS-KUISNT").defineArray(range(1, 20));
|
||||
@L2 Redefine xRA_KIS_KUISNT; // REDEF. BEGIN : #RA-KIS-KUISNT
|
||||
@L3 B xRA_KIS_KUISN = B(3 ,"#RA-KIS-KUISN");
|
||||
@L2 N xRN_KIS_OVZL = N(3,0 ,"#RN-KIS-OVZL");
|
||||
@L2 B xRA_KIS_OVISN = B(3 ,"#RA-KIS-OVISN").defineArray(range(1, 60));
|
||||
@L1 A gUMS = A(250 ,"+UMS");
|
||||
@L1 Redefine gUMS; // REDEF. BEGIN : +UMS
|
||||
@L2 A xRA_UMS_CODE = A(8 ,"#RA-UMS-CODE");
|
||||
@L2 P xRN_UMS_LAST_VR = P(3,0 ,"#RN-UMS-LAST-VR");
|
||||
@L2 A xRA_UMS_MD_KZ1_BKLZ_KZ = A(1 ,"#RA-UMS-MD-KZ1-BKLZ-KZ");
|
||||
@L2 A xRA_UMS_MD_KZ1_TARIF = A(1 ,"#RA-UMS-MD-KZ1-TARIF");
|
||||
@L2 A xRA_UMS_WAEHRUNG_MD = A(3 ,"#RA-UMS-WAEHRUNG-MD");
|
||||
@L2 N xRN_UMS_LAST_LOAD = N(6,0 ,"#RN-UMS-LAST-LOAD");
|
||||
@L2 A xUMS_VORG_OPID = A(3 ,"#UMS-VORG-OPID");
|
||||
@L2 A xUMS_FORCE = A(1 ,"#UMS-FORCE"); // Beharrung auf eigene
|
||||
/* * /* PF-Tasten-Belegung */
|
||||
@L2 N xUMS_SNR_FIL = N(2,0 ,"#UMS-SNR-FIL");// Schaden-Filial-Nummer
|
||||
@L2 A xUMS_SNR_KREIS = A(2 ,"#UMS-SNR-KREIS");// Schaden-Kreis zB.:'SA'
|
||||
@L2 N xUMS_SNR_JAHR = N(4,0 ,"#UMS-SNR-JAHR");// Schaden-Jahr
|
||||
@L2 N xUMS_SNR_LFD_NR = N(6,0 ,"#UMS-SNR-LFD-NR");// Schaden-Laufende-Nummer
|
||||
@L2 A xUMS_SNR_STATUS = A(1 ,"#UMS-SNR-STATUS");// Status-Byte 'O','S','H'
|
||||
@L2 A xUMS_ART = A(2 ,"#UMS-ART"); // Schadenart
|
||||
@L2 A xUMS_REFERENT = A(8 ,"#UMS-REFERENT");// Referent
|
||||
@L2 A xUMS_EVI_TYP = A(1 ,"#UMS-EVI-TYP"); // eigen oder fremd-Vi
|
||||
@L2 P xUMS_IDENTCOUNTER = P(5,0 ,"#UMS-IDENTCOUNTER");
|
||||
@L2 P xUMS_OUTPUTNR = P(5,0 ,"#UMS-OUTPUTNR");
|
||||
@L2 P xUMS_EREIGNIS_DT = P(8,0 ,"#UMS-EREIGNIS-DT");// nur für LV (= Stornodatum)
|
||||
@L2 P xUMS_SCN_HIST = P(3,0 ,"#UMS-SCN-HIST");// merken Hist-Lesestand
|
||||
@L2 B xUMS_ANZEIGEDAT_KPL = B(3 ,"#UMS-ANZEIGEDAT-KPL");// für Aufruf SK10
|
||||
@L2 N xUMS_SKT_FIL = N(2,0 ,"#UMS-SKT-FIL");// Filial-Nummer Kontostamm
|
||||
@L2 A xUMS_SKT_KREIS = A(2 ,"#UMS-SKT-KREIS");// Schaden-Kreis Kontostamm
|
||||
@L2 N xUMS_SKT_JAHR = N(4,0 ,"#UMS-SKT-JAHR");// Schaden-Jahr Kontostamm
|
||||
@L2 N xUMS_SKT_LFD_NR = N(6,0 ,"#UMS-SKT-LFD-NR");// Schaden-Lfd-Nr Kontostamm
|
||||
@L2 N xUMS_SKT_BRAN = N(2,0 ,"#UMS-SKT-BRAN");// Schaden-Branche Kontostamm
|
||||
@L2 A xUMS_WORKSTATION_ID = A(8 ,"#UMS-WORKSTATION-ID");// Workstation (OD) *** frei ***
|
||||
@L2 A xRA_UMS_BRGR = A(1 ,"#RA-UMS-BRGR").defineArray(range(1,3));
|
||||
@L2 A xUMS_WF_ABT = A(8 ,"#UMS-WF-ABT"); // Workflow-Abteilung(für OD)
|
||||
@L2 A xUMS_WF_STAT = A(8 ,"#UMS-WF-STAT"); // Workflow-Station(für OD)
|
||||
@L2 A xUMS_WF_OPID = A(8 ,"#UMS-WF-OPID"); // Workflow-Opid(für OD)
|
||||
@L2 P xUMS_WF_SORT_DOKU_ART = P(3,0 ,"#UMS-WF-SORT-DOKU-ART").defineArray(range(1,5));// WF-Dokument(für OD)
|
||||
@L2 P xUMS_KEYCOUNTER = P(5,0 ,"#UMS-KEYCOUNTER").defineArray(range(1,10));// Identcounter aus File111
|
||||
@L2 A xUMS_SKT_BELEGTYP = A(1 ,"#UMS-SKT-BELEGTYP");// Schaden-Konto-Belegtyp
|
||||
@L2 P xUMS_SKT_POS_IDC = P(5,0 ,"#UMS-SKT-POS-IDC");// Identcounter Pos.Buchung-SKT
|
||||
@L2 P xUMS_SKT_BRAN_IDC = P(5,0 ,"#UMS-SKT-BRAN-IDC");// Identcounter Bran.Buchung-SKT
|
||||
@L2 P xUMS_SKT_ABST_IDC = P(5,0 ,"#UMS-SKT-ABST-IDC");// Identcounter Abst.Buchung-SKT
|
||||
@L2 A xUMS_TEXT_FN95 = A(1 ,"#UMS-TEXT-FN95");// Text-Option im FN95
|
||||
@L2 A xUMS_REGRESS_RATE = A(2 ,"#UMS-REGRESS-RATE");// Schaden-Regreß oder -Rate
|
||||
@L2 P xUMS_REGRESS_NR = P(6,0 ,"#UMS-REGRESS-NR");// Schaden-Regreß-Nr.
|
||||
@L2 A xUMS_DAT2_FN95 = A(1 ,"#UMS-DAT2-FN95");// 2. Datumszeile FN95 optional
|
||||
@L2 N xUMS_PC_DAT = N(8,0 ,"#UMS-PC-DAT"); // Pc-Antragsdatum
|
||||
@L2 N xUMS_PC_NUM = N(4,0 ,"#UMS-PC-NUM"); // Pc-Antragsnummer
|
||||
@L2 A xUMS_DRUCKER_ID = A(10 ,"#UMS-DRUCKER-ID");// Drucker-Id (OD)
|
||||
@L2 A xUMS_KEY_STAT_DI32 = A(8 ,"#UMS-KEY-STAT-DI32");// Station für AufrufDI32 (OD)
|
||||
@L2 B xUMS_SKT_ANZEIGEDAT_KPL = B(3 ,"#UMS-SKT-ANZEIGEDAT-KPL");// Anzeigedatum-KPL für Sammelb.
|
||||
@L2 P xUMS_WINDOW_NUM = P(2,0 ,"#UMS-WINDOW-NUM");// Image-Display (OD)
|
||||
@L2 P xUMS_STORNO_GRUND = P(2,0 ,"#UMS-STORNO-GRUND");// stornogrund (nur LV)
|
||||
@L2 A xUMS_DOI_KEY_ART = A(2 ,"#UMS-DOI-KEY-ART");// Kz f. Einschränkung Di30 (OD)
|
||||
@L2 P xUMS_SKT_WERTEDATUM = P(8,0 ,"#UMS-SKT-WERTEDATUM");// Schaden-Konto-Wertedatum
|
||||
@L2 P xUMS_SKT_BANKSCHLUESSEL = P(2,0 ,"#UMS-SKT-BANKSCHLUESSEL");// Schaden-Konto-Bankschlüssel
|
||||
@L2 A xUMS_REST = A(28 ,"#UMS-REST"); // Bytes frei
|
||||
@L1 A gPEL = A(250 ,"+PEL");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 A xRA_PEL_ABT = A(1 ,"#RA-PEL-ABT");
|
||||
@L2 N xRN_PEL_VPOL = N(8,0 ,"#RN-PEL-VPOL");
|
||||
@L2 N xRN_PEL_KPNR = N(8,0 ,"#RN-PEL-KPNR");
|
||||
@L2 N xRN_PEL_GT = N(8,0 ,"#RN-PEL-GT");
|
||||
@L2 N xRN_PEL_BR1 = N(8,0 ,"#RN-PEL-BR1");
|
||||
@L2 N xRN_PEL_BR2 = N(8,0 ,"#RN-PEL-BR2");
|
||||
@L2 N xRN_PEL_BUCH = N(8,0 ,"#RN-PEL-BUCH");
|
||||
@L2 N xRN_PEL_EINL = N(6,0 ,"#RN-PEL-EINL");
|
||||
@L2 A xRA_PEL_FREI = A(2 ,"#RA-PEL-FREI");
|
||||
@L2 N xRN_PEL_TEXT = N(8,0 ,"#RN-PEL-TEXT");
|
||||
@L2 N xRN_PEL_STAT = N(8,0 ,"#RN-PEL-STAT");
|
||||
@L2 N xRN_PEL_AGT = N(8,0 ,"#RN-PEL-AGT");
|
||||
@L2 N xRN_PEL_ABR1 = N(8,0 ,"#RN-PEL-ABR1");
|
||||
@L2 N xRN_PEL_ABR2 = N(8,0 ,"#RN-PEL-ABR2");
|
||||
@L2 N xRN_PEL_ATXT = N(8,0 ,"#RN-PEL-ATXT");
|
||||
@L2 N xRN_PEL_INDX = N(2,0 ,"#RN-PEL-INDX");
|
||||
@L2 N xRN_PEL_VDAT = N(6,0 ,"#RN-PEL-VDAT");
|
||||
@L2 A xRA_PEL_EFKZ = A(1 ,"#RA-PEL-EFKZ");
|
||||
@L2 A xRA_PEL_DRUC = A(4 ,"#RA-PEL-DRUC");
|
||||
@L2 A xRA_PEL_VART = A(3 ,"#RA-PEL-VART");
|
||||
@L2 N xRN_PEL_KREI = N(3,0 ,"#RN-PEL-KREI");
|
||||
@L2 A xRA_PEL_VAE = A(3 ,"#RA-PEL-VAE");
|
||||
@L2 A xRA_PEL_BNEU = A(3 ,"#RA-PEL-BNEU");
|
||||
@L2 A xRA_PEL_FILL118 = A(118 ,"#RA-PEL-FILL118");
|
||||
@L2 A xRA_PEL_MENU = A(2 ,"#RA-PEL-MENU");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 N xRN_KFZ_MENR = N(1,0 ,"#RN-KFZ-MENR");
|
||||
@L2 N xRN_KFZ_IEVI = N(3,0 ,"#RN-KFZ-IEVI");
|
||||
@L2 N xRN_KFZ_IVV = N(3,0 ,"#RN-KFZ-IVV");
|
||||
@L2 N xRN_KFZ_HIST = N(3,0 ,"#RN-KFZ-HIST");
|
||||
@L2 N xRN_KFZ_STND = N(3,0 ,"#RN-KFZ-STND");
|
||||
@L2 A xRA_KFZ_EVBR = A(30 ,"#RA-KFZ-EVBR");
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 A xRA_KFZ_BRA = A(3 ,"#RA-KFZ-BRA").defineArray(range(1, 10));
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 N xRN_KFZ_BRA1 = N(3,0 ,"#RN-KFZ-BRA1");
|
||||
@L3 N xRN_KFZ_BRA2 = N(3,0 ,"#RN-KFZ-BRA2");
|
||||
@L3 N xRN_KFZ_BRA3 = N(3,0 ,"#RN-KFZ-BRA3");
|
||||
@L3 N xRN_KFZ_BRA4 = N(3,0 ,"#RN-KFZ-BRA4");
|
||||
@L3 N xRN_KFZ_BRA5 = N(3,0 ,"#RN-KFZ-BRA5");
|
||||
@L2 N xRN_KFZ_VDAT = N(8,0 ,"#RN-KFZ-VDAT");
|
||||
@L2 A xRA_KFZ_AREV = A(1 ,"#RA-KFZ-AREV");
|
||||
@L2 A xRA_KFZ_SABE = A(3 ,"#RA-KFZ-SABE");
|
||||
@L2 A xRA_KFZ_RAG = A(1 ,"#RA-KFZ-RAG");
|
||||
@L2 P xRN_KFZ_OZW = P(3,0 ,"#RN-KFZ-OZW");
|
||||
@L2 N xRN_KFZ_VVAE = N(8,0 ,"#RN-KFZ-VVAE");
|
||||
@L2 A xRA_KFZ_VEF = A(1 ,"#RA-KFZ-VEF");
|
||||
@L2 A xRA_KFZ_VVAR = A(3 ,"#RA-KFZ-VVAR");
|
||||
@L2 N xRN_KFZ_VEGD = N(8,0 ,"#RN-KFZ-VEGD");
|
||||
@L2 N xRN_KFZ_VELD = N(8,0 ,"#RN-KFZ-VELD");
|
||||
@L2 A xRA_KFZ_VDRU = A(4 ,"#RA-KFZ-VDRU");
|
||||
@L2 A xRA_KFZ_PZSG = A(3 ,"#RA-KFZ-PZSG");
|
||||
@L2 N xRN_KFZ_LESE = N(3,0 ,"#RN-KFZ-LESE");
|
||||
@L2 N xRN_KFZ_FZW = N(2,0 ,"#RN-KFZ-FZW");
|
||||
@L2 N xRN_KFZ_VI10 = N(2,0 ,"#RN-KFZ-VI10");
|
||||
@L2 N xRN_KFZ_VD = N(8,0 ,"#RN-KFZ-VD");
|
||||
@L2 N xRN_KFZ_VB = N(8,0 ,"#RN-KFZ-VB");
|
||||
@L2 A xRA_KFZ_KFKZ = A(11 ,"#RA-KFZ-KFKZ");
|
||||
@L2 N xRN_KFZ_AWDT = N(8,0 ,"#RN-KFZ-AWDT");
|
||||
@L2 A xRA_KFZ_AWKZ = A(1 ,"#RA-KFZ-AWKZ");
|
||||
@L2 N xRN_KFZ_AEND = N(8,0 ,"#RN-KFZ-AEND");
|
||||
@L2 A xRA_KFZ_VABT = A(3 ,"#RA-KFZ-VABT");
|
||||
@L2 A xRA_KFZ_OPID = A(3 ,"#RA-KFZ-OPID");
|
||||
@L2 N xRN_KFZ_DATV = N(8,0 ,"#RN-KFZ-DATV");
|
||||
@L2 N xRN_KFZ_DATB = N(8,0 ,"#RN-KFZ-DATB");
|
||||
@L2 A xRA_KFZ_FIL = A(2 ,"#RA-KFZ-FIL");
|
||||
@L2 N xRN_KFZ_KRBR = N(4,0 ,"#RN-KFZ-KRBR");
|
||||
@L2 Redefine xRN_KFZ_KRBR; // REDEF. BEGIN : #RN-KFZ-KRBR
|
||||
@L3 N xRN_KFZ_KR = N(2,0 ,"#RN-KFZ-KR");
|
||||
@L3 N xRN_KFZ_BR = N(2,0 ,"#RN-KFZ-BR");
|
||||
@L2 A xRA_KFZ_VAE1 = A(3 ,"#RA-KFZ-VAE1");
|
||||
@L2 A xRA_KFZ_VAE2 = A(3 ,"#RA-KFZ-VAE2");
|
||||
@L2 A xRA_KFZ_SUMM = A(1 ,"#RA-KFZ-SUMM");
|
||||
@L2 N xRN_KFZ_STAT = N(2,0 ,"#RN-KFZ-STAT");
|
||||
@L2 A xRA_KFZ_GSNA = A(10 ,"#RA-KFZ-GSNA");
|
||||
@L2 A xRA_KFZ_BVNR = A(7 ,"#RA-KFZ-BVNR");
|
||||
@L2 N xRN_KFZ_ANTR = N(8,0 ,"#RN-KFZ-ANTR");
|
||||
@L2 N xRN_KFZ_VINK = N(8,0 ,"#RN-KFZ-VINK");
|
||||
@L2 N xRN_KFZ_FPNR = N(3,0 ,"#RN-KFZ-FPNR");
|
||||
@L2 A xRA_KFZ_MAPA = A(1 ,"#RA-KFZ-MAPA");
|
||||
@L2 L xRL_KFZ_PRVV = L("#RL-KFZ-PRVV");
|
||||
@L2 L xRL_KFZ_ST01 = L("#RL-KFZ-ST01");
|
||||
@L2 L xRL_KFZ_AUTO = L("#RL-KFZ-AUTO");
|
||||
@L2 A xRA_KFZ_OPI2 = A(3 ,"#RA-KFZ-OPI2");
|
||||
@L2 L xRL_KFZ_BLND = L("#RL-KFZ-BLND");
|
||||
@L2 C xRC_KFZ_BLND = C("#RC-KFZ-BLND");
|
||||
@L2 A xRA_KFZ_ATXT = A(10 ,"#RA-KFZ-ATXT");
|
||||
@L2 A xRA_KFZ_KTXT = A(3 ,"#RA-KFZ-KTXT");
|
||||
@L2 A xRA_KFZ_AMNU = A(8 ,"#RA-KFZ-AMNU");
|
||||
@L2 A xRA_KFZ_MENU = A(2 ,"#RA-KFZ-MENU");
|
||||
@L1 A gISN = A(250 ,"+ISN");
|
||||
@L1 Redefine gISN; // REDEF. BEGIN : +ISN
|
||||
@L2 A xRA_ISN_ISN = A(5 ,"#RA-ISN-ISN");
|
||||
@L2 Redefine xRA_ISN_ISN; // REDEF. BEGIN : #RA-ISN-ISN
|
||||
@L3 P xRN_ISN_AKUN = P(9,0 ,"#RN-ISN-AKUN");
|
||||
@L2 P xRN_ISN_HKUN = P(9,0 ,"#RN-ISN-HKUN");
|
||||
@L2 P xRN_ISN_VKUN = P(9,0 ,"#RN-ISN-VKUN");
|
||||
@L2 P xRN_ISN_ORTE = P(9,0 ,"#RN-ISN-ORTE");
|
||||
@L2 P xRN_ISN_KORP = P(9,0 ,"#RN-ISN-KORP");
|
||||
@L2 P xRN_ISN_KTAB = P(9,0 ,"#RN-ISN-KTAB");
|
||||
@L2 P xRN_ISN_ABKU = P(9,0 ,"#RN-ISN-ABKU");
|
||||
@L2 P xRN_ISN_OPOL = P(9,0 ,"#RN-ISN-OPOL");
|
||||
@L2 P xRN_ISN_SPOL = P(9,0 ,"#RN-ISN-SPOL");
|
||||
@L2 P xRN_ISN_OEVI = P(9,0 ,"#RN-ISN-OEVI");
|
||||
@L2 P xRN_ISN_SEVI = P(9,0 ,"#RN-ISN-SEVI");
|
||||
@L2 P xRN_ISN_OVV = P(9,0 ,"#RN-ISN-OVV");
|
||||
@L2 P xRN_ISN_SVV = P(9,0 ,"#RN-ISN-SVV");
|
||||
@L2 P xRN_ISN_OBU = P(9,0 ,"#RN-ISN-OBU");
|
||||
@L2 P xRN_ISN_SBU = P(9,0 ,"#RN-ISN-SBU");
|
||||
@L2 P xRN_ISN_BTS = P(9,0 ,"#RN-ISN-BTS");
|
||||
@L2 P xRN_ISN_BTK = P(9,0 ,"#RN-ISN-BTK");
|
||||
@L2 P xRN_ISN_EKPO = P(9,0 ,"#RN-ISN-EKPO");
|
||||
@L2 P xRN_ISN_EKVV = P(9,0 ,"#RN-ISN-EKVV");
|
||||
@L2 P xRN_ISN_EKK1 = P(9,0 ,"#RN-ISN-EKK1");
|
||||
@L2 P xRN_ISN_OPOS = P(9,0 ,"#RN-ISN-OPOS");
|
||||
@L2 P xRN_ISN_SPOS = P(9,0 ,"#RN-ISN-SPOS");
|
||||
@L2 P xRN_ISN_EKG1 = P(9,0 ,"#RN-ISN-EKG1");
|
||||
@L2 P xRN_ISN_HGPO = P(9,0 ,"#RN-ISN-HGPO");
|
||||
@L2 P xRN_ISN_HGKO = P(9,0 ,"#RN-ISN-HGKO");
|
||||
@L2 P xRN_ISN_OLS = P(9,0 ,"#RN-ISN-OLS");
|
||||
@L2 P xRN_ISN_SLS = P(9,0 ,"#RN-ISN-SLS");
|
||||
@L2 P xRN_ISN_HLS = P(9,0 ,"#RN-ISN-HLS");
|
||||
@L2 P xRN_ISN_VDNR = P(9,0 ,"#RN-ISN-VDNR");
|
||||
@L2 P xRN_ISN_MDST = P(9,0 ,"#RN-ISN-MDST");
|
||||
@L2 P xRN_ISN_KTOS = P(9,0 ,"#RN-ISN-KTOS");
|
||||
@L2 P xRN_ISN_KTOO = P(9,0 ,"#RN-ISN-KTOO");
|
||||
@L2 P xRN_ISN_ORTS = P(9,0 ,"#RN-ISN-ORTS");
|
||||
@L2 P xRN_ISN_FPNR = P(9,0 ,"#RN-ISN-FPNR");
|
||||
@L2 P xRN_ISN_WA49 = P(9,0 ,"#RN-ISN-WA49");
|
||||
@L2 P xRN_ISN_SD34 = P(8,0 ,"#RN-ISN-SD34");
|
||||
@L2 P xRN_ISN_SPER = P(9,0 ,"#RN-ISN-SPER");
|
||||
@L2 P xRN_ISN_MDS = P(9,0 ,"#RN-ISN-MDS");
|
||||
@L2 P xRN_ISN_SUCH = P(9,0 ,"#RN-ISN-SUCH");
|
||||
@L2 P xRN_ISN_ABST = P(9,0 ,"#RN-ISN-ABST");
|
||||
@L2 P xRN_ISN_UMKON = P(9,0 ,"#RN-ISN-UMKON");
|
||||
@L2 P xRN_ISN_MUBU = P(9,0 ,"#RN-ISN-MUBU");// MUTTERBUCHUNG (SK)
|
||||
@L2 P xRN_ISN_RESERVE = P(9,0 ,"#RN-ISN-RESERVE").defineArray(range(1, 5));
|
||||
@L1 A gKUN = A(250 ,"+KUN");
|
||||
@L1 Redefine gKUN; // REDEF. BEGIN : +KUN
|
||||
@L2 A xRA_KUN_KUNA = A(27 ,"#RA-KUN-KUNA");
|
||||
@L2 A xRA_KUN_GHKZ = A(1 ,"#RA-KUN-GHKZ");
|
||||
@L2 N xRN_KUN_GHK1 = N(8,0 ,"#RN-KUN-GHK1");
|
||||
@L2 N xRN_KUN_GHK2 = N(8,0 ,"#RN-KUN-GHK2");
|
||||
@L2 N xRN_KUN_DATV = N(8,0 ,"#RN-KUN-DATV");
|
||||
@L2 N xRN_KUN_DATB = N(8,0 ,"#RN-KUN-DATB");
|
||||
@L2 A xRA_KUN_BRAX = A(30 ,"#RA-KUN-BRAX");
|
||||
@L2 N xRN_KUN_FILV = N(2,0 ,"#RN-KUN-FILV");
|
||||
@L2 N xRN_KUN_FILB = N(2,0 ,"#RN-KUN-FILB");
|
||||
@L2 N xRN_KUN_ANZEI = N(2,0 ,"#RN-KUN-ANZEI");
|
||||
@L2 N xRN_KUN_BILKR = N(2,0 ,"#RN-KUN-BILKR");
|
||||
@L2 N xRN_KUN_POLK = N(2,0 ,"#RN-KUN-POLK");
|
||||
@L2 N xRN_KUN_POLB = N(2,0 ,"#RN-KUN-POLB");
|
||||
@L2 A xRA_KUN_BANKKU = A(5 ,"#RA-KUN-BANKKU");
|
||||
@L2 A xRA_KUN_BANKOR = A(27 ,"#RA-KUN-BANKOR");
|
||||
@L2 N xRN_KUN_BANKLE = N(5,0 ,"#RN-KUN-BANKLE");
|
||||
@L2 N xRN_KUN_TDAT = N(8,0 ,"#RN-KUN-TDAT");
|
||||
@L2 N xRN_KUN_VR = N(3,0 ,"#RN-KUN-VR");
|
||||
@L2 N xRN_KUN_VGVR = N(3,0 ,"#RN-KUN-VGVR");
|
||||
@L2 N xRN_KUN_STAT = N(3,0 ,"#RN-KUN-STAT");
|
||||
@L2 N xRN_KUN_HGV = N(2,0 ,"#RN-KUN-HGV");
|
||||
@L2 A xRA_KUN_UG1V = A(2 ,"#RA-KUN-UG1V");
|
||||
@L2 A xRA_KUN_UG2V = A(2 ,"#RA-KUN-UG2V");
|
||||
@L2 A xRA_KUN_UG3V = A(2 ,"#RA-KUN-UG3V");
|
||||
@L2 A xRA_KUN_SPV = A(1 ,"#RA-KUN-SPV");
|
||||
@L2 N xRN_KUN_HGB = N(2,0 ,"#RN-KUN-HGB");
|
||||
@L2 A xRA_KUN_UG1B = A(2 ,"#RA-KUN-UG1B");
|
||||
@L2 A xRA_KUN_UG2B = A(2 ,"#RA-KUN-UG2B");
|
||||
@L2 A xRA_KUN_UG3B = A(2 ,"#RA-KUN-UG3B");
|
||||
@L2 A xRA_KUN_SPB = A(1 ,"#RA-KUN-SPB");
|
||||
@L2 A xRA_KUN_LEER1 = A(38 ,"#RA-KUN-LEER1");
|
||||
@L2 N xRN_KUN_OVGEM = N(5,0 ,"#RN-KUN-OVGEM");
|
||||
@L2 N xRN_KUN_OVOKZ = N(6,0 ,"#RN-KUN-OVOKZ");
|
||||
@L2 N xRN_KUN_OVOBK = N(4,0 ,"#RN-KUN-OVOBK");
|
||||
@L2 A xRA_KUN_OVNAM = A(15 ,"#RA-KUN-OVNAM");
|
||||
@L2 A xRA_KUN_LEER2 = A(4 ,"#RA-KUN-LEER2");
|
||||
@L2 A xRA_KUN_OVFIL = A(2 ,"#RA-KUN-OVFIL");
|
||||
@L2 A xRA_KUN_OVSA = A(1 ,"#RA-KUN-OVSA");
|
||||
@L1 A gKEY = A(250 ,"+KEY");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 N xRN_KEY_VKKZ = N(8,0 ,"#RN-KEY-VKKZ");
|
||||
@L2 A xRA_KEY_VVA = A(2 ,"#RA-KEY-VVA");
|
||||
@L2 N xRN_KEY_VVNR = N(8,0 ,"#RN-KEY-VVNR");
|
||||
@L2 N xRN_KEY_VHNR = N(3,0 ,"#RN-KEY-VHNR");
|
||||
@L2 N xRN_KEY_VGES = N(3,0 ,"#RN-KEY-VGES");
|
||||
@L2 N xRN_KEY_VRES = N(1,0 ,"#RN-KEY-VRES");
|
||||
@L2 N xRN_KEY_VVIN = N(2,0 ,"#RN-KEY-VVIN");
|
||||
@L2 N xRN_KEY_VBRA = N(2,0 ,"#RN-KEY-VBRA");
|
||||
@L2 N xRN_KEY_VBRN = N(1,0 ,"#RN-KEY-VBRN");
|
||||
@L2 A xRA_KEY_VBTART = A(1 ,"#RA-KEY-VBTART");
|
||||
@L2 A xRA_KEY_VBSTAT = A(1 ,"#RA-KEY-VBSTAT");
|
||||
@L2 N xRN_KEY_VUGES = N(3,0 ,"#RN-KEY-VUGES");
|
||||
@L2 N xRN_KEY_VBJAHR = N(2,0 ,"#RN-KEY-VBJAHR");
|
||||
@L2 N xRN_KEY_AKTONR = N(7,0 ,"#RN-KEY-AKTONR");
|
||||
@L2 Redefine xRN_KEY_AKTONR; // REDEF. BEGIN : #RN-KEY-AKTONR
|
||||
@L3 A xRA_KEY_VKTO = A(7 ,"#RA-KEY-VKTO");
|
||||
@L2 A xRA_KEY_KOSEL = A(1 ,"#RA-KEY-KOSEL");
|
||||
@L2 A xRA_KEY_LEER1 = A(4 ,"#RA-KEY-LEER1");
|
||||
@L2 N xRN_KEY_AKKZ = N(8,0 ,"#RN-KEY-AKKZ");
|
||||
@L2 A xRA_KEY_AVA = A(2 ,"#RA-KEY-AVA");
|
||||
@L2 N xRN_KEY_AVNR = N(8,0 ,"#RN-KEY-AVNR");
|
||||
@L2 N xRN_KEY_AHNR = N(3,0 ,"#RN-KEY-AHNR");
|
||||
@L2 N xRN_KEY_AGES = N(3,0 ,"#RN-KEY-AGES");
|
||||
@L2 N xRN_KEY_ARES = N(1,0 ,"#RN-KEY-ARES");
|
||||
@L2 N xRN_KEY_AVIN = N(2,0 ,"#RN-KEY-AVIN");
|
||||
@L2 N xRN_KEY_ABRA = N(2,0 ,"#RN-KEY-ABRA");
|
||||
@L2 N xRN_KEY_ABRN = N(1,0 ,"#RN-KEY-ABRN");
|
||||
@L2 A xRA_KEY_ABTART = A(1 ,"#RA-KEY-ABTART");
|
||||
@L2 A xRA_KEY_ABSTAT = A(1 ,"#RA-KEY-ABSTAT");
|
||||
@L2 N xRN_KEY_AUGES = N(3,0 ,"#RN-KEY-AUGES");
|
||||
@L2 N xRN_KEY_ABJAHR = N(2,0 ,"#RN-KEY-ABJAHR");
|
||||
@L2 N xRN_KEY_KTONR = N(7,0 ,"#RN-KEY-KTONR");
|
||||
@L2 N xRN_KEY_PROV = N(2,0 ,"#RN-KEY-PROV");
|
||||
@L2 N xRN_KEY_PROD = N(2,0 ,"#RN-KEY-PROD");
|
||||
@L2 A xRA_KEY_PRKZ = A(1 ,"#RA-KEY-PRKZ");
|
||||
@L2 N xRN_KEY_AFGS = N(3,0 ,"#RN-KEY-AFGS");
|
||||
@L2 A xRA_KEY_AFPO = A(14 ,"#RA-KEY-AFPO");
|
||||
@L2 N xRN_KEY_VFGS = N(3,0 ,"#RN-KEY-VFGS");
|
||||
@L2 A xRA_KEY_VFPO = A(14 ,"#RA-KEY-VFPO");
|
||||
@L2 N xRN_KEY_FBRA = N(2,0 ,"#RN-KEY-FBRA");
|
||||
@L2 N xRN_KEY_NOTD = N(6,0 ,"#RN-KEY-NOTD");
|
||||
@L2 Redefine xRN_KEY_NOTD;
|
||||
@L3 N xRN_KEY_NOTD_YYYY = N(4,0 ,"#RN-KEY-NOTD-YYYY");
|
||||
@L3 N xRN_KEY_NOTD_MM = N(2,0 ,"#RN-KEY-NOTD-MM");
|
||||
@L2 N xRN_KEY_LSNR = N(2,0 ,"#RN-KEY-LSNR");
|
||||
@L2 N xRN_KEY_KBJ = N(4,0 ,"#RN-KEY-KBJ");
|
||||
@L2 N xRN_KEY_KBJNR = N(1,0 ,"#RN-KEY-KBJNR");
|
||||
@L2 A xRA_KEY_ZNAM = A(31 ,"#RA-KEY-ZNAM");
|
||||
@L2 A xRA_KEY_VNAM = A(2 ,"#RA-KEY-VNAM");
|
||||
@L2 A xRA_KEY_STRA = A(1 ,"#RA-KEY-STRA");
|
||||
@L2 N xRN_KEY_HAUS = N(4,0 ,"#RN-KEY-HAUS");
|
||||
@L2 N xRN_KEY_POST = N(6,0 ,"#RN-KEY-POST");
|
||||
@L2 A xRA_KEY_KZEX = A(1 ,"#RA-KEY-KZEX");
|
||||
@L2 A xRA_KEY_ACHT = A(1 ,"#RA-KEY-ACHT");
|
||||
@L2 N xRN_KEY_ALFA = N(2,0 ,"#RN-KEY-ALFA");
|
||||
@L2 N xRN_KEY_BERU = N(5,0 ,"#RN-KEY-BERU");
|
||||
@L2 A xRA_KEY_LEER2 = A(4 ,"#RA-KEY-LEER2");
|
||||
@L2 A xRA_KEY_BRSL = A(1 ,"#RA-KEY-BRSL");
|
||||
@L2 A xRA_KEY_PSTA = A(1 ,"#RA-KEY-PSTA");
|
||||
@L2 A xRA_KEY_BRAW = A(1 ,"#RA-KEY-BRAW");
|
||||
@L2 N xRN_KEY_BRVO = N(2,0 ,"#RN-KEY-BRVO");
|
||||
@L2 N xRN_KEY_BRBI = N(2,0 ,"#RN-KEY-BRBI");
|
||||
@L2 N xRN_KEY_JRVO = N(2,0 ,"#RN-KEY-JRVO");
|
||||
@L2 N xRN_KEY_JRBI = N(2,0 ,"#RN-KEY-JRBI");
|
||||
@L2 A xRA_KEY_VNAW = A(1 ,"#RA-KEY-VNAW");
|
||||
@L2 A xRA_KEY_KEIG = A(1 ,"#RA-KEY-KEIG");
|
||||
@L2 A xRA_KEY_SBID = A(3 ,"#RA-KEY-SBID");
|
||||
@L2 A xRA_KEY_KURO = A(2 ,"#RA-KEY-KURO");
|
||||
@L2 A xRA_KEY_PRAE = A(1 ,"#RA-KEY-PRAE");
|
||||
@L2 A xRA_KEY_PROV = A(1 ,"#RA-KEY-PROV");
|
||||
@L2 A xRA_KEY_OFFE = A(1 ,"#RA-KEY-OFFE");
|
||||
@L2 A xRA_KEY_AKTU = A(1 ,"#RA-KEY-AKTU");
|
||||
@L2 A xRA_KEY_ALLE = A(1 ,"#RA-KEY-ALLE");
|
||||
@L2 A xRA_KEY_AOFA = A(1 ,"#RA-KEY-AOFA");
|
||||
@L2 N xRN_KEY_BUDT = N(8,0 ,"#RN-KEY-BUDT");
|
||||
@L2 P xRN_KEY_BUNR = P(9,0 ,"#RN-KEY-BUNR");
|
||||
@L2 A xRA_KEY_VKZ = A(1 ,"#RA-KEY-VKZ");
|
||||
@L2 A xRA_KEY_ABGA = A(1 ,"#RA-KEY-ABGA");
|
||||
@L2 A xRA_KEY_SART = A(2 ,"#RA-KEY-SART");
|
||||
@L2 A xRA_KEY_PFIL = A(2 ,"#RA-KEY-PFIL");
|
||||
@L2 L xRL_KEY_UHR = L("#RL-KEY-UHR");
|
||||
@L2 A xRA_KEY_STAT = A(1 ,"#RA-KEY-STAT");
|
||||
@L2 A xRA_KEY_GSMT = A(1 ,"#RA-KEY-GSMT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_1 = A(8 ,"#RA-KEY-08X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVO = A(22 ,"#RA-KEY-VVO");
|
||||
@L2 A xRA_KEY_27X_1 = A(27 ,"#RA-KEY-27X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVO = A(22 ,"#RA-KEY-AVO");
|
||||
@L2 A xRA_KEY_27X_2 = A(27 ,"#RA-KEY-27X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVO = A(22 ,"#RA-KEY-GVO");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_2 = A(8 ,"#RA-KEY-08X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VBT = A(24 ,"#RA-KEY-VBT");
|
||||
@L2 A xRA_KEY_24X = A(24 ,"#RA-KEY-24X"); // ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_ABT = A(24 ,"#RA-KEY-ABT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_3 = A(8 ,"#RA-KEY-08X-3");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVK = A(16 ,"#RA-KEY-VVK");
|
||||
@L2 A xRA_KEY_33X_1 = A(33 ,"#RA-KEY-33X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVK = A(16 ,"#RA-KEY-AVK");
|
||||
@L2 A xRA_KEY_33X_2 = A(33 ,"#RA-KEY-33X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVK = A(16 ,"#RA-KEY-GVK");
|
||||
@L1 A gSTV = A(250 ,"+STV");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_APRG = A(8 ,"#RA-STV-APRG");
|
||||
@L2 A xRA_STV_VPRG = A(8 ,"#RA-STV-VPRG");
|
||||
@L2 A xRA_STV_FUNK = A(4 ,"#RA-STV-FUNK");
|
||||
@L2 Redefine xRA_STV_FUNK; // REDEF. BEGIN : #RA-STV-FUNK
|
||||
@L3 N xRN_STV_FUNR = N(2,0 ,"#RN-STV-FUNR");
|
||||
@L3 A xRA_STV_FUNR = A(2 ,"#RA-STV-FUNR");
|
||||
@L2 N xRN_STV_AUSW = N(2,0 ,"#RN-STV-AUSW");
|
||||
@L2 A xRA_STV_CODE = A(4 ,"#RA-STV-CODE"); // 30
|
||||
@L2 N xRN_STV_OBNR = N(8,0 ,"#RN-STV-OBNR");
|
||||
@L2 A xRA_STV_OART = A(2 ,"#RA-STV-OART");
|
||||
@L2 N xRN_STV_HINR = N(3,0 ,"#RN-STV-HINR");
|
||||
@L2 N xRN_STV_VINR = N(2,0 ,"#RN-STV-VINR");
|
||||
@L2 N xRN_STV_BRAN = N(2,0 ,"#RN-STV-BRAN");
|
||||
@L2 N xRN_STV_BRNR = N(1,0 ,"#RN-STV-BRNR");
|
||||
@L2 N xRN_STV_GENR = N(3,0 ,"#RN-STV-GENR");
|
||||
@L2 A xRA_STV_GMOD = A(1 ,"#RA-STV-GMOD");
|
||||
@L2 A xRA_STV_AMOD = A(1 ,"#RA-STV-AMOD");
|
||||
@L2 A xRA_STV_VIKZ = A(1 ,"#RA-STV-VIKZ");
|
||||
@L2 A xRA_STV_DATI = A(8 ,"#RA-STV-DATI");
|
||||
@L2 N xRN_STV_DATA = N(3,0 ,"#RN-STV-DATA");
|
||||
@L2 A xRA_STV_OPID = A(3 ,"#RA-STV-OPID");
|
||||
@L2 A xRA_STV_PFKY = A(96 ,"#RA-STV-PFKY");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PFF = A(4 ,"#RA-STV-PFF");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PF = A(4 ,"#RA-STV-PF").defineArray(range(1, 24));
|
||||
@L2 N xRN_STV_DATN = N(6,0 ,"#RN-STV-DATN");
|
||||
@L2 A xRA_STV_MMOD = A(3 ,"#RA-STV-MMOD");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_241X = A(241 ,"#RA-STV-241X");
|
||||
@L2 A xRA_STV_APAR = A(1 ,"#RA-STV-APAR");
|
||||
@L2 P xRN_STV_CURS = P(6,0 ,"#RN-STV-CURS");
|
||||
@L2 P xRN_STV_MARK = P(3,0 ,"#RN-STV-MARK");
|
||||
@L2 B xRN_STV_FNUM = B(2 ,"#RN-STV-FNUM");
|
||||
@L1 A gKEY1 = A(250 ,"+KEY1");
|
||||
@L1 Redefine gKEY1; // REDEF. BEGIN : +KEY1
|
||||
@L2 A xRA_KEY1_ASSTART = A(4 ,"#RA-KEY1-ASSTART");
|
||||
@L2 N xRN_KEY1_POSVI = N(2,0 ,"#RN-KEY1-POSVI");
|
||||
@L2 N xRN_KEY1_POSVII = N(3,0 ,"#RN-KEY1-POSVII");
|
||||
@L2 A xRA_KEY1_RVART = A(4 ,"#RA-KEY1-RVART");
|
||||
@L2 N xRN_KEY1_RVVON = N(8,0 ,"#RN-KEY1-RVVON");
|
||||
@L2 N xRN_KEY1_RVBHJAHR = N(4,0 ,"#RN-KEY1-RVBHJAHR");
|
||||
@L2 N xRN_KEY1_RVBBNR = N(2,0 ,"#RN-KEY1-RVBBNR");
|
||||
@L2 P xRN_KEY1_SBVANZ = P(1,0 ,"#RN-KEY1-SBVANZ");
|
||||
@L2 P xRN_KEY1_SBVINR = P(2,0 ,"#RN-KEY1-SBVINR").defineArray(range(1, 2));
|
||||
@L2 P xRN_KEY1_SBVII = P(2,0 ,"#RN-KEY1-SBVII").defineArray(range(1, 2));
|
||||
@L2 N xRN_KEY1_BRA = N(2,0 ,"#RN-KEY1-BRA").defineArray(range(1, 3));
|
||||
@L2 N xRN_KEY1_GEBDT = N(8,0 ,"#RN-KEY1-GEBDT");
|
||||
@L2 A xRA_KEY1_2X = A(2 ,"#RA-KEY1-2X");
|
||||
@L2 A xRA_KEY1_WAEHRUNG = A(3 ,"#RA-KEY1-WAEHRUNG");
|
||||
@L2 A xRA_KEY1_SK_ALLE = A(1 ,"#RA-KEY1-SK-ALLE");
|
||||
@L2 N xRN_KEY1_HNR = N(4,0 ,"#RN-KEY1-HNR");
|
||||
@L2 A xRA_KEY1_MTVKZ = A(1 ,"#RA-KEY1-MTVKZ");
|
||||
@L2 A xRA_KEY1_VIL = A(1 ,"#RA-KEY1-VIL");
|
||||
@L2 A xRA_KEY1_OKZ = A(3 ,"#RA-KEY1-OKZ");
|
||||
@L2 N xRN_KEY1_OPLZ = N(6,0 ,"#RN-KEY1-OPLZ");
|
||||
@L2 N xRN_KEY1_OHNR = N(4,0 ,"#RN-KEY1-OHNR");
|
||||
@L2 P xRN_KEY1_SUBVR = P(3,0 ,"#RN-KEY1-SUBVR");
|
||||
@L2 P xRN_KEY1_SUBKTO = P(7,0 ,"#RN-KEY1-SUBKTO");
|
||||
@L2 P xRN_KEY1_SUBZWST = P(3,0 ,"#RN-KEY1-SUBZWST");
|
||||
@L2 A xRA_KEY1_SKART = A(3 ,"#RA-KEY1-SKART");
|
||||
@L2 A xRA_KEY1_TCSART = A(1 ,"#RA-KEY1-TCSART");
|
||||
@L2 A xRA_KEY1_TCZ1 = A(2 ,"#RA-KEY1-TCZ1");
|
||||
@L2 Redefine xRA_KEY1_TCZ1; // REDEF. BEGIN : #RA-KEY1-TCZ1
|
||||
@L3 N xRN_KEY1_TCZ1 = N(2,0 ,"#RN-KEY1-TCZ1");
|
||||
@L2 P xRN_KEY1_TCODE = P(5,0 ,"#RN-KEY1-TCODE");
|
||||
@L2 P xRN_KEY1_TCZ2 = P(2,0 ,"#RN-KEY1-TCZ2");
|
||||
@L2 A xRA_KEY1_NATV = A(3 ,"#RA-KEY1-NATV");
|
||||
@L2 A xRA_KEY1_NATO = A(3 ,"#RA-KEY1-NATO");
|
||||
@L2 N xRN_KEY1_SNR = N(8,0 ,"#RN-KEY1-SNR");
|
||||
@L2 N xRN_KEY1_LNR = N(5,0 ,"#RN-KEY1-LNR");
|
||||
@L2 A xRA_KEY1_ZISB = A(3 ,"#RA-KEY1-ZISB");
|
||||
@L2 N xRN_KEY1_STAT = N(5,0 ,"#RN-KEY1-STAT");
|
||||
@L2 N xRN_KEY1_LZ = N(2,0 ,"#RN-KEY1-LZ");
|
||||
@L2 A xRA_KEY1_GRDHA = A(2 ,"#RA-KEY1-GRDHA");
|
||||
@L2 A xRA_KEY1_NATOV = A(3 ,"#RA-KEY1-NATOV");
|
||||
@L2 N xRN_KEY1_AWDT = N(8,0 ,"#RN-KEY1-AWDT");
|
||||
@L2 A xRA_KEY1_KOSP = A(1 ,"#RA-KEY1-KOSP");
|
||||
@L2 N xRN_KEY1_EDSTAT = N(2,0 ,"#RN-KEY1-EDSTAT");
|
||||
@L2 A xRA_KEY1_FREI = A(7 ,"#RA-KEY1-FREI");
|
||||
@L2 N xRN_KEY1_KDAT = N(8,0 ,"#RN-KEY1-KDAT");
|
||||
@L2 N xRN_KEY1_KGRD = N(2,0 ,"#RN-KEY1-KGRD");
|
||||
@L2 N xRN_KEY1_PERSNR = N(5,0 ,"#RN-KEY1-PERSNR");
|
||||
@L2 A xRA_KEY1_RPART = A(2 ,"#RA-KEY1-RPART");
|
||||
@L2 N xRN_KEY1_RPDAT = N(8,0 ,"#RN-KEY1-RPDAT");
|
||||
@L2 A xRA_KEY1_RVKZ = A(1 ,"#RA-KEY1-RVKZ");
|
||||
@L2 A xRA_KEY1_RVKZ2 = A(1 ,"#RA-KEY1-RVKZ2");
|
||||
@L2 A xRA_KEY1_LK = A(30 ,"#RA-KEY1-LK");
|
||||
@L2 A xRA_KEY1_SCHEMA = A(2 ,"#RA-KEY1-SCHEMA");
|
||||
@L2 N xRN_KEY1_RSOBNR = N(8,0 ,"#RN-KEY1-RSOBNR");
|
||||
@L2 A xRA_KEY1_RSKZ = A(1 ,"#RA-KEY1-RSKZ");
|
||||
@L2 A xRA_KEY1_BRFA = A(3 ,"#RA-KEY1-BRFA");
|
||||
@L2 A xRA_KEY1_BUCHART = A(3 ,"#RA-KEY1-BUCHART");
|
||||
@L2 N xRN_KEY1_BANKSCHL = N(3,0 ,"#RN-KEY1-BANKSCHL");
|
||||
@L2 N xRN_KEY1_WERTEDAT = N(8,0 ,"#RN-KEY1-WERTEDAT");
|
||||
@L2 A xRA_KEY1_VORPROG = A(8 ,"#RA-KEY1-VORPROG");
|
||||
@L2 N xRN_KEY1_GILT_BIS = N(8,0 ,"#RN-KEY1-GILT-BIS");
|
||||
@L2 P xRN_KEY1_FNR = P(3,0 ,"#RN-KEY1-FNR");
|
||||
@L2 A xRA_KEY1_KO55MELD = A(1 ,"#RA-KEY1-KO55MELD");
|
||||
@L1 A gKEY2 = A(250 ,"+KEY2");
|
||||
@L1 Redefine gKEY2; // REDEF. BEGIN : +KEY2
|
||||
@L2 A xRA_KEY2_TARIF = A(7 ,"#RA-KEY2-TARIF");
|
||||
@L2 A xRA_KEY2_BANKNA = A(27 ,"#RA-KEY2-BANKNA");
|
||||
@L2 A xRA_KEY2_USERGRUPPE = A(8 ,"#RA-KEY2-USERGRUPPE");
|
||||
@L2 N xRN_KEY2_KOSTST = N(5,0 ,"#RN-KEY2-KOSTST");
|
||||
@L2 A xRA_KEY2_AVISO_OP = A(3 ,"#RA-KEY2-AVISO-OP");
|
||||
@L2 N xRN_KEY2_AVISO_DT = N(8,0 ,"#RN-KEY2-AVISO-DT");
|
||||
@L2 N xRN_KEY2_AVISO_GD = N(3,0 ,"#RN-KEY2-AVISO-GD");
|
||||
@L2 N xRN_KEY2_AVISO_DTB = N(8,0 ,"#RN-KEY2-AVISO-DTB");
|
||||
@L2 A xRA_KEY2_SPRACH_KZ = A(2 ,"#RA-KEY2-SPRACH-KZ");
|
||||
@L2 A xRA_KEY2_GEWINN_VB = A(4 ,"#RA-KEY2-GEWINN-VB");
|
||||
@L2 N xRN_KEY2_GEWINN_JAHR = N(4,0 ,"#RN-KEY2-GEWINN-JAHR");
|
||||
@L2 A xRA_KEY2_ABST_SA = A(2 ,"#RA-KEY2-ABST-SA");
|
||||
@L2 A xRA_KEY2_ABST_ZA = A(1 ,"#RA-KEY2-ABST-ZA");
|
||||
@L2 P xRN_KEY2_ABST_ZR = P(9,0 ,"#RN-KEY2-ABST-ZR");
|
||||
@L2 A xRA_KEY2_ABST_NK = A(1 ,"#RA-KEY2-ABST-NK");
|
||||
@L2 A xRA_KEY2_ABST_NA = A(8 ,"#RA-KEY2-ABST-NA");
|
||||
@L2 A xRA_KEY2_FAB = A(3 ,"#RA-KEY2-FAB");
|
||||
@L2 A xRA_KEY2_TYP = A(10 ,"#RA-KEY2-TYP");
|
||||
@L2 A xRA_KEY2_FZA = A(1 ,"#RA-KEY2-FZA");
|
||||
@L2 N xRN_KEY2_LST = N(1,0 ,"#RN-KEY2-LST");
|
||||
@L2 N xRN_KEY2_KTBJ = N(4,0 ,"#RN-KEY2-KTBJ");
|
||||
@L2 N xRN_KEY2_KW = N(3,0 ,"#RN-KEY2-KW");
|
||||
@L2 P xRN_KEY2_UEBWNR = P(6,0 ,"#RN-KEY2-UEBWNR");
|
||||
@L2 P xRN_KEY2_UNTNR = P(4,0 ,"#RN-KEY2-UNTNR");
|
||||
@L2 A xRA_KEY2_SERIE = A(1 ,"#RA-KEY2-SERIE");
|
||||
@L2 P xRN_KEY2_PAKNR = P(9,0 ,"#RN-KEY2-PAKNR");
|
||||
@L2 A xRA_KEY2_WHG_VON = A(3 ,"#RA-KEY2-WHG-VON");
|
||||
@L2 A xRA_KEY2_WHG_NACH = A(3 ,"#RA-KEY2-WHG-NACH");
|
||||
@L2 A xRA_KEY2_EXTSA = A(3 ,"#RA-KEY2-EXTSA");
|
||||
@L2 N xRN_KEY2_GKNR = N(8,0 ,"#RN-KEY2-GKNR");
|
||||
@L1 C xCV_WHBS = C("#CV-WHBS"); // GW FUER WIEDERHOLTE BS-AUSG.
|
||||
@L1 C xCV_OUTP = C("#CV-OUTP"); // PROTECT BEI A+B - PROGRAMMEN
|
||||
@L1 C xCV_STV = C("#CV-STV"); // KEY-FELDER STEUERLEISTE
|
||||
@L1 L xWL_KEYWECHS = L("#WL-KEYWECHS"); // SCHALTER, OB KEYWECHSEL
|
||||
@L1 L xWL_INTERN = L("#WL-INTERN"); // SCHALTER, OB BLINDER DURCHL.
|
||||
@L1 A xWA_HELP_TASTE = A(4 ,"#WA-HELP-TASTE"); // PF-TASTE FUER 'HELP'
|
||||
@L1 A xWA_MENU_TASTE = A(4 ,"#WA-MENU-TASTE"); // PF-TASTE FUER 'MENU'
|
||||
@L1 P xWN_SEL_ISN = P(9,0 ,"#WN-SEL-ISN").defineArray(range(1,24));
|
||||
@L1 A xWA_FUNKTION = A(5 ,"#WA-FUNKTION").defineArray(range(1,100));// FUNKTIONSTABELLE
|
||||
@L1 A xWA_VORWEG_AUFRUF = A(8 ,"#WA-VORWEG-AUFRUF");// VORWEG-AUFRUF (Z.B. SCHWEBE)
|
||||
@L1 L xWL_TEST = L("#WL-TEST"); // KENNZEICHEN, OB TEST
|
||||
@L1 N xWN_WARTEN_SUMME = N(5,0 ,"#WN-WARTEN-SUMME");// ZAEHLER FUER SUSPEND
|
||||
@L1 A xWA_AUSWAHL_KZ = A(1 ,"#WA-AUSWAHL-KZ").defineArray(range(1,24));// MARKIERUNGSLEISTE
|
||||
@L1 N xWN_AUSWAHL_ANZAHL = N(2,0 ,"#WN-AUSWAHL-ANZAHL");
|
||||
@L1 A xWA_AUSWAHL_GUELTIG = A(20 ,"#WA-AUSWAHL-GUELTIG");
|
||||
/*
|
||||
* Platz für neue globale Variable
|
||||
* Definitionen in:
|
||||
* YLNEUGLO/YCNEUGLO
|
||||
*/
|
||||
@L1 A gNEUGLO_01 = A(250 ,"+NEUGLO-01");
|
||||
@L1 Redefine gNEUGLO_01;
|
||||
@L2 A xBTC_STEUERUNG = A(8 ,"#BTC-STEUERUNG");
|
||||
@L2 A xUMS_MIS_BILANZKREIS = A(1 ,"#UMS-MIS-BILANZKREIS").defineArray(range(1, 7));
|
||||
@L2 A xUMS_MIS_BRAN = A(2 ,"#UMS-MIS-BRAN").defineArray(range(1, 10));
|
||||
@L2 P xKEY1_SUCH_BETRAG = P(9,2 ,"#KEY1-SUCH-BETRAG");
|
||||
@L2 P xKEY2_ARCHIV_DB = P(3,0 ,"#KEY2-ARCHIV-DB");
|
||||
@L2 A xKEY2_EXTKZ = A(1 ,"#KEY2-EXTKZ");
|
||||
@L2 N xRN_KEY_JAHRV = N(4,0 ,"#RN-KEY-JAHRV");
|
||||
@L2 N xRN_KEY_JAHRB = N(4,0 ,"#RN-KEY-JAHRB");
|
||||
@L2 A xRA_KEY_EXT_GRUND_NAME = A(31 ,"#RA-KEY-EXT-GRUND-NAME");
|
||||
@L2 P xRN_KEY_EXT_BB_VON = P(8,0 ,"#RN-KEY-EXT-BB-VON");
|
||||
@L2 P xRN_KEY_EXT_BB_BIS = P(8,0 ,"#RN-KEY-EXT-BB-BIS");
|
||||
@L2 P xUMS_MA_SUB_SATZART_1 = P(3,0 ,"#UMS-MA-SUB-SATZART-1");
|
||||
@L2 A xRA_KEY_EXT_ANTRAGSART = A(3 ,"#RA-KEY-EXT-ANTRAGSART");
|
||||
@L2 P xFLV_TRANS = P(7,0 ,"#FLV-TRANS");
|
||||
@L2 A xFLV_ISIN = A(12 ,"#FLV-ISIN");
|
||||
@L2 P xFLV_BUNR = P(7,0 ,"#FLV-BUNR");
|
||||
@L2 P xFLV_GILT_BIS = P(8,0 ,"#FLV-GILT-BIS");
|
||||
@L2 P xFLV_DATUM = P(8,0 ,"#FLV-DATUM");
|
||||
@L2 P xFLV_VV_VINR = P(3,0 ,"#FLV-VV-VINR");
|
||||
@L2 P xFLV_VV_BRAN = P(3,0 ,"#FLV-VV-BRAN");
|
||||
@L2 P xFLV_VV_BRNR = P(3,0 ,"#FLV-VV-BRNR");
|
||||
@L2 P xFLV_VV_HINR = P(3,0 ,"#FLV-VV-HINR");
|
||||
@L2 A xVLK_EXVERARB = A(30 ,"#VLK-EXVERARB");
|
||||
@L2 P xRN_KEY_IVKNR = P(8,0 ,"#RN-KEY-IVKNR");
|
||||
@L2 A xRA_SC_MELDUNGS_ART = A(1 ,"#RA-SC-MELDUNGS-ART");
|
||||
@L2 A xRA_OS_GFALL = A(2 ,"#RA-OS-GFALL");
|
||||
@L2 A xRA_OS_DOKART = A(4 ,"#RA-OS-DOKART");
|
||||
@L2 A xRA_OS_BR_ART = A(3 ,"#RA-OS-BR-ART");
|
||||
@L2 N xRN_KEY_YU_PERSNR = N(13,0 ,"#RN-KEY-YU-PERSNR");
|
||||
@L2 N xSCN_VERS_BEGINN = N(8,0 ,"#SCN-VERS-BEGINN");
|
||||
@L2 A xDI_ABTEILUNG = A(8 ,"#DI-ABTEILUNG");
|
||||
@L2 P xDI_STAT_DZT = P(3,0 ,"#DI-STAT-DZT");
|
||||
@L2 P xDI_STAT_DZT_BIS = P(3,0 ,"#DI-STAT-DZT-BIS");
|
||||
@L2 A xDI_SPARTE = A(2 ,"#DI-SPARTE");
|
||||
@L2 A xDI_STATION = A(8 ,"#DI-STATION");
|
||||
@L2 A xDI_WM_OPID = A(8 ,"#DI-WM-OPID");
|
||||
@L2 A xDI_AW_POL_FIL = A(1 ,"#DI-AW-POL-FIL");
|
||||
@L2 A xDI_VAE_ART = A(3 ,"#DI-VAE-ART");
|
||||
@L2 A xDI_WK_VAE_ART = A(3 ,"#DI-WK-VAE-ART");
|
||||
@L2 A xDI_AW_SUB_VR = A(1 ,"#DI-AW-SUB-VR");
|
||||
@L2 P xDI_SUB_VR = P(3,0 ,"#DI-SUB-VR");
|
||||
@L2 P xDI_POL_KREIS = P(2,0 ,"#DI-POL-KREIS");
|
||||
@L2 P xDI_POL_BRAN = P(2,0 ,"#DI-POL-BRAN");
|
||||
@L2 A xDI_VAE_HINW = A(3 ,"#DI-VAE-HINW");
|
||||
@L1 A gNEUGLO_02 = A(250 ,"+NEUGLO-02");
|
||||
@L1 Redefine gNEUGLO_02;
|
||||
@L2 A xDI_BEARB_GRP = A(3 ,"#DI-BEARB-GRP");
|
||||
@L2 A xDI_AW_DRF = A(1 ,"#DI-AW-DRF");
|
||||
@L2 A xDI_AW_E_ANT = A(1 ,"#DI-AW-E-ANT");
|
||||
@L2 P xDI_PC_ANT_VKTO = P(7,0 ,"#DI-PC-ANT-VKTO");
|
||||
@L2 P xDI_PLZ_VON = P(7,0 ,"#DI-PLZ-VON");
|
||||
@L2 P xDI_PLZ_BIS = P(7,0 ,"#DI-PLZ-BIS");
|
||||
@L2 A xDI_POSTNATION = A(3 ,"#DI-POSTNATION");
|
||||
@L2 P xDI_WM_DATUM = P(8,0 ,"#DI-WM-DATUM");
|
||||
@L2 P xDI_POL_FILIALE = P(2,0 ,"#DI-POL-FILIALE");
|
||||
@L2 A xDI_AW_VAE_HINW = A(1 ,"#DI-AW-VAE-HINW");
|
||||
@L2 A xDI_AW_VKTO = A(1 ,"#DI-AW-VKTO");
|
||||
@L2 A xDI_AW_BEARB_GRP = A(1 ,"#DI-AW-BEARB-GRP");
|
||||
@L2 A xDI_AW_KR_BR = A(1 ,"#DI-AW-KR-BR");
|
||||
@L2 A xDI_AW_VAE_ART = A(1 ,"#DI-AW-VAE-ART");
|
||||
@L2 P xDI_VR_KEY = P(3,0 ,"#DI-VR-KEY");
|
||||
@L2 A xDI_OART = A(2 ,"#DI-OART");
|
||||
@L2 P xDI_WM_DATUM_VON = P(8,0 ,"#DI-WM-DATUM-VON");
|
||||
@L2 A xDI_WM_AUSWAHL = A(1 ,"#DI-WM-AUSWAHL");
|
||||
@L2 A xRA_KEY_OIB = A(20 ,"#RA-KEY-OIB");
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package CSTENTW;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* IT fixture (roadmap item 187), shaped like the corpus's CSTENTW/DISKRECH.java: a program in one
|
||||
* library that includes the GDA of another. COMMON_DA exists in KORIN and ABFRAGE; only the qualifier
|
||||
* says which one this module means.
|
||||
*/
|
||||
@Natural(moduleName="DISKTEST")
|
||||
public class DISKTEST extends NaturalProgram {
|
||||
|
||||
@Using KORIN.COMMON_DA COMMON_DA = globalUsing(KORIN.COMMON_DA.class);
|
||||
@Using KORIN.Mitarbeiter.Verwaltung.YPUMPRUE_DA YPUMPRUE_DA = localUsing(KORIN.Mitarbeiter.Verwaltung.YPUMPRUE_DA.class);
|
||||
@Using KORIN.NOFILE_DA NOFILE_DA = localUsing(KORIN.NOFILE_DA.class);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* IT fixture (roadmap item 187): the unqualified form. COMMON_DA is named without a package, so only
|
||||
* Java's same-package rule — this file's own package, KORIN — can pick KORIN.COMMON over
|
||||
* ABFRAGE.COMMON.
|
||||
*/
|
||||
@Natural(moduleName="UMKTEST")
|
||||
public class UMKTEST extends NaturalProgram {
|
||||
|
||||
@Using COMMON_DA COMMON_DA = globalUsing(COMMON_DA.class);
|
||||
@Using KORIN.Mitarbeiter.Verwaltung.YPUMPRUE_DA YPUMPRUE_DA = localUsing(KORIN.Mitarbeiter.Verwaltung.YPUMPRUE_DA.class);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
113
ac-code-server/src/test/resources/fixtures/natjav/V_MIT04.java
Normal file
113
ac-code-server/src/test/resources/fixtures/natjav/V_MIT04.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* * header created during migration process *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION Rule : Vkto-Daten
|
||||
* * @OWNER P07
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* </pre>
|
||||
*/
|
||||
@Natural(moduleName="V-MIT04")
|
||||
public class V_MIT04 extends NaturalSubprogram {
|
||||
|
||||
//
|
||||
// VKTO-Daten zu Original od. Schwebe
|
||||
//
|
||||
@Using KORIN.Diverse.Framework.GWSTGP_DA GWSTGP_DA = parameterUsing(KORIN.Diverse.Framework.GWSTGP_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWSTGP_DA) */
|
||||
public final KORIN.Diverse.Framework.GWSTGP_DA.GWSTGP GWSTGP = GWSTGP_DA.GWSTGP;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWSTGP_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWPDA_M_DA GWPDA_M_DA = parameterUsing(KORIN.Diverse.Framework.GWPDA_M_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
public final KORIN.Diverse.Framework.GWPDA_M_DA.MSG_INFO MSG_INFO = GWPDA_M_DA.MSG_INFO;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
@Using V_MIT04P_DA V_MIT04P_DA = parameterUsing(V_MIT04P_DA.class);
|
||||
/* @@USING:BEGIN(V_MIT04P_DA) */
|
||||
public final V_MIT04P_DA.V_MIT04P V_MIT04P = V_MIT04P_DA.V_MIT04P;
|
||||
/* @@USING:END(V_MIT04P_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWLOGNAM_DA GWLOGNAM_DA = localUsing(KORIN.Diverse.Framework.GWLOGNAM_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_RETCODE xSTD_RETCODE = GWLOGNAM_DA.xSTD_RETCODE;
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_LOOPCODE xSTD_LOOPCODE = GWLOGNAM_DA.xSTD_LOOPCODE;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
/* * */
|
||||
// Daten für: MITZ1R
|
||||
@Using MITZ1RP_DA MITZ1RP_DA = localUsing(MITZ1RP_DA.class);
|
||||
/* @@USING:BEGIN(MITZ1RP_DA) */
|
||||
public final MITZ1RP_DA.MITZ1RR MITZ1RR = MITZ1RP_DA.MITZ1RR;
|
||||
public final MITZ1RP_DA.MITZ1RP MITZ1RP = MITZ1RP_DA.MITZ1RP;
|
||||
/* @@USING:END(MITZ1RP_DA) */
|
||||
@Local
|
||||
@L1 Group xVON_MITZ1R = group("#VON-MITZ1R"); // <PRIME-KEY-VON>
|
||||
@L2 A xGESAMT = A(7 ,"#GESAMT");
|
||||
@L2 Redefine xGESAMT;
|
||||
@L3 A SVKTO = A(7 ,"SVKTO");
|
||||
@L3 Redefine SVKTO;
|
||||
@L4 P VR_KEY = P(3,0 ,"VR-KEY");
|
||||
@L4 P VKTO = P(7,0 ,"VKTO");
|
||||
@L4 A STATUS_BYTE = A(1 ,"STATUS-BYTE");
|
||||
/* * */
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
/* * */
|
||||
reset(V_MIT04P.OUTPUTS);
|
||||
/* * */
|
||||
if (((NE(V_MIT04P.xVR_KEY, 0)
|
||||
&& NE(V_MIT04P.xVKTO, 0))
|
||||
&& NE(V_MIT04P.xSTATUS_BYTE, " "))) {
|
||||
assign(xVON_MITZ1R.VR_KEY, V_MIT04P.xVR_KEY);
|
||||
assign(xVON_MITZ1R.VKTO, V_MIT04P.xVKTO);
|
||||
assign(xVON_MITZ1R.STATUS_BYTE, V_MIT04P.xSTATUS_BYTE);
|
||||
//
|
||||
//
|
||||
assign(MITZ1RR.xVON_KEY, xVON_MITZ1R.xGESAMT);
|
||||
assign(MITZ1RR.xFUNCTION, "Read");
|
||||
callnat("MITZ1R", GWSTGP, MSG_INFO, MITZ1RR, MITZ1RP);
|
||||
if (NE(MSG_INFO.xxRETURN_CODE, xSTD_RETCODE.OK)) {
|
||||
escapeRoutine();
|
||||
}
|
||||
if ((NE(MITZ1RR.xSW_FOUND, "J")
|
||||
&& EQ(V_MIT04P.xSCHWEBE_GILT, "J"))) {
|
||||
assign(xVON_MITZ1R.STATUS_BYTE, "S");
|
||||
assign(MITZ1RR.xVON_KEY, xVON_MITZ1R.xGESAMT);
|
||||
assign(MITZ1RR.xFUNCTION, "Read");
|
||||
callnat("MITZ1R", GWSTGP, MSG_INFO, MITZ1RR, MITZ1RP);
|
||||
if (NE(MSG_INFO.xxRETURN_CODE, xSTD_RETCODE.OK)) {
|
||||
escapeRoutine();
|
||||
}
|
||||
}
|
||||
if (EQ(MITZ1RR.xSW_FOUND, "J")) {
|
||||
assign(V_MIT04P.xZS_NAME, MITZ1RP.ZS_NAME.get(1));
|
||||
assign(V_MIT04P.xVKTO_ORGBEZEICHNUNG, MITZ1RP.VKTO_ORGBEZEICHNUNG);
|
||||
assign(V_MIT04P.xVKTO_STATUS_TEXT, MITZ1RP.VKTO_STATUS_TEXT);
|
||||
assign(V_MIT04P.xVKTO_VERWENDUNG, MITZ1RP.VKTO_VERWENDUNG);
|
||||
assign(V_MIT04P.xVKTO_VERWENDUNG_TEXT, MITZ1RP.VKTO_VERWENDUNG_TEXT);
|
||||
if (GT(V_MIT04P.xVKTO_ORGBEZEICHNUNG, " ")) {
|
||||
assign(V_MIT04P.xMAP_NAME, V_MIT04P.xVKTO_ORGBEZEICHNUNG);
|
||||
} else {
|
||||
assign(V_MIT04P.xMAP_NAME, V_MIT04P.xZS_NAME);
|
||||
}
|
||||
} else {
|
||||
// KONTONUMMER :1: NICHT IN MITARBEITER-DATEI GESPEICHERT
|
||||
assign(MSG_INFO.xxMSG_NR, 684);
|
||||
assign(MSG_INFO.xxMSG_DATA.get(1), V_MIT04P.xVKTO);
|
||||
escapeRoutine();
|
||||
}
|
||||
}
|
||||
/* * */
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="YPUMPRUE")
|
||||
public class YPUMPRUE_DA extends NaturalData {
|
||||
|
||||
@Parameter
|
||||
@L1 Group YPUMPRUE = group("YPUMPRUE");
|
||||
@L2 Group INPUTS = group("INPUTS");
|
||||
@L3 N VR_KEY = N(3,0 ,"VR-KEY");
|
||||
@L3 N VKTO = N(7,0 ,"VKTO");
|
||||
@L3 N VKTO_ZIEL = N(7,0 ,"VKTO-ZIEL");
|
||||
@L3 A KZ_PR_UMK_ABWEICH_KVA = A(1 ,"KZ-PR-UMK-ABWEICH-KVA");
|
||||
@L2 Group OUTPUTS = group("OUTPUTS");
|
||||
@L3 A VKTO_NAME = A(31 ,"VKTO-NAME");
|
||||
@L3 N FNUM = N(4,0 ,"FNUM");
|
||||
@L3 A FEHLER = A(78 ,"FEHLER");
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package ABFRAGE;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="COMMON")
|
||||
public class COMMON_DA extends NaturalData {
|
||||
|
||||
@Global
|
||||
/*
|
||||
* GDA für LIBRARY KORIN INCLUSIVE
|
||||
* REDEFINITIONEN
|
||||
*/
|
||||
@L1 A gPROGRAMM = A(8 ,"+PROGRAMM");
|
||||
@L1 A gBPOL_PARM = A(17 ,"+BPOL-PARM");
|
||||
@L1 N gBBST_POLNR = N(9,0 ,"+BBST-POLNR");
|
||||
@L1 A gBBST_ART = A(9 ,"+BBST-ART");
|
||||
@L1 A gABTEILUNG = A(5 ,"+ABTEILUNG");
|
||||
@L1 A gPASSWORD = A(8 ,"+PASSWORD");
|
||||
@L1 A gGETMAIL = A(1 ,"+GETMAIL");
|
||||
@L1 A gERROR_PROGRAM = A(8 ,"+ERROR-PROGRAM");
|
||||
@L1 A gERROR_STAT = A(1 ,"+ERROR-STAT");
|
||||
@L1 N gERROR_LINE = N(4,0 ,"+ERROR-LINE");
|
||||
@L1 N gERROR_NR = N(4,0 ,"+ERROR-NR");
|
||||
@L1 A gERGEBNIS = A(79 ,"+ERGEBNIS");
|
||||
@L1 A gSPOOL_PARM = A(80 ,"+SPOOL-PARM");
|
||||
@L1 Redefine gSPOOL_PARM;
|
||||
@L2 A xSPUSER = A(8 ,"#SPUSER");
|
||||
@L2 A xSPDEST = A(8 ,"#SPDEST");
|
||||
@L2 A xSPFORM = A(1 ,"#SPFORM");
|
||||
@L1 A gINIT_VAR = A(240 ,"+INIT-VAR");
|
||||
@L1 Redefine gINIT_VAR; // REDEF. BEGIN : +INIT-VAR
|
||||
@L2 A xRA_INITVAR_GETMAIL = A(1 ,"#RA-INITVAR-GETMAIL");// GETMAIL duchgeführt Y/N
|
||||
@L2 N xINIT_DATIME = N(15,0 ,"#INIT-DATIME");// STARTUP Datum/Zeitder
|
||||
@L2 Redefine xINIT_DATIME; // letzten Startup-Durchführung
|
||||
@L3 N xINIT_DATE = N(8,0 ,"#INIT-DATE");// "YYYYMMDDHHMMSSZ"
|
||||
@L3 Redefine xINIT_DATE;
|
||||
@L4 A xINIT_DATE_ALPHA = A(8 ,"#INIT-DATE-ALPHA");
|
||||
@L3 N xINIT_TIME = N(7,0 ,"#INIT-TIME");
|
||||
@L3 Redefine xINIT_TIME;
|
||||
@L4 A xINIT_TIME_ALPHA = A(7 ,"#INIT-TIME-ALPHA");
|
||||
@L2 A xINIT_USER = A(8 ,"#INIT-USER"); // STARTUP U-ID d. Current LOGON
|
||||
@L2 A xINIT_ID = A(8 ,"#INIT-ID"); // STARTUP Terminal-ID
|
||||
@L2 A xINIT_GROUP = A(8 ,"#INIT-GROUP"); // STARTUP GROUP-ID, über die
|
||||
/* /* gelinkt */
|
||||
@L2 A xSTARTUP_PROGRAM = A(8 ,"#STARTUP-PROGRAM");// STARTUP Startup-Programm
|
||||
@L2 A xRESTART_PROGRAM = A(8 ,"#RESTART-PROGRAM");// STARTUP Restart-Programm
|
||||
@L2 A xERROR_PROGRAM = A(8 ,"#ERROR-PROGRAM");// #ERR letztes fehlerhaftes PRG
|
||||
/* /* STARTUP Error-Programm */
|
||||
@L2 A xRA_INITVAR_CICS_NAME = A(8 ,"#RA-INITVAR-CICS-NAME");// STARTUP und APPLID
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME; // "CSWVCICS"/"CSWVCICT"
|
||||
@L3 A xCICSNAME = A(8 ,"#CICSNAME"); // Redef. aus Namensgründen
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME;
|
||||
@L3 A xCICS_NAME = A(8 ,"#CICS-NAME"); // Redef. aus Namensgründen
|
||||
@L2 A FILLER = A(8 ,"FILLER");
|
||||
@L2 A xUSER_NAME = A(32 ,"#USER-NAME"); // STARTUP User-Name
|
||||
@L2 A xAPPL_NAME = A(32 ,"#APPL-NAME"); // STARTUP Anwendungs-Name
|
||||
@L2 A xSYS_SCHALTER = A(8 ,"#SYS-SCHALTER");// DIV SYSTEM-Schalter
|
||||
@L2 Redefine xSYS_SCHALTER;
|
||||
@L3 A xSYS_RESTART = A(1 ,"#SYS-RESTART");// #ERR bei Absturz Restart Y/N
|
||||
@L3 A xSYS_CANCEL = A(1 ,"#SYS-CANCEL");// #CAN BATCH CANCELNin #ERROR
|
||||
@L3 A xERROR_STAT = A(1 ,"#ERROR-STAT");// #ERR letzter Fehler-Status
|
||||
@L3 A xERROR_OPTN = A(1 ,"#ERROR-OPTN");// #ERR RETRY ON ERRORS Y/N
|
||||
@L3 A xERROR_SAME = A(1 ,"#ERROR-SAME");// #ERR same ERROR wie zuvor Y/N
|
||||
/* 0=NEIN 1=PROG 2=NUMMER 3=BEIDE */
|
||||
@L3 A xERROR_LOG = A(1 ,"#ERROR-LOG"); // #ERR N=kein ERROR-Logging
|
||||
@L3 A xMONITOR = A(1 ,"#MONITOR"); // STARTUP B=BATCH, C=CICS
|
||||
/* /* P=COMPLETE */
|
||||
@L3 A FILLER2 = A(1 ,"FILLER2");
|
||||
@L2 A xERROR_PROG = A(8 ,"#ERROR-PROG");
|
||||
@L2 N xERROR_NR = N(4,0 ,"#ERROR-NR"); // #ERR letzte Fehlernummer
|
||||
@L2 Redefine xERROR_NR;
|
||||
@L3 A xERRAX = A(4 ,"#ERRAX");
|
||||
@L2 N xERROR_LINE = N(4,0 ,"#ERROR-LINE"); // #ERR letzte fehlerhafte Zeile
|
||||
@L2 A xMESSAGE = A(72 ,"#MESSAGE"); // #ERR Fehlermeldung
|
||||
@L2 Redefine xMESSAGE;
|
||||
@L3 A xMANF = A(1 ,"#MANF");
|
||||
@L3 A STAR = A(1 ,"STAR");
|
||||
@L3 A RESTMSG = A(70 ,"RESTMSG");
|
||||
@L1 A gBERECHTIGUNGEN = A("+BERECHTIGUNGEN");
|
||||
@L1 A gVSU = A(250 ,"+VSU");
|
||||
@L1 A gKIS = A(250 ,"+KIS");
|
||||
@L1 Redefine gKIS; // REDEF. BEGIN : +KIS
|
||||
@L2 N xRN_KIS_ZAHL = N(3,0 ,"#RN-KIS-ZAHL");
|
||||
@L2 B xRA_KIS_KUISNT = B(3 ,"#RA-KIS-KUISNT").defineArray(range(1, 20));
|
||||
@L2 Redefine xRA_KIS_KUISNT; // REDEF. BEGIN : #RA-KIS-KUISNT
|
||||
@L3 B xRA_KIS_KUISN = B(3 ,"#RA-KIS-KUISN");
|
||||
@L2 N xRN_KIS_OVZL = N(3,0 ,"#RN-KIS-OVZL");
|
||||
@L2 B xRA_KIS_OVISN = B(3 ,"#RA-KIS-OVISN").defineArray(range(1, 60));
|
||||
@L1 A gUMS = A(250 ,"+UMS");
|
||||
@L1 Redefine gUMS; // REDEF. BEGIN : +UMS
|
||||
@L2 A xRA_UMS_CODE = A(8 ,"#RA-UMS-CODE");
|
||||
@L2 P xRN_UMS_LAST_VR = P(3,0 ,"#RN-UMS-LAST-VR");
|
||||
@L2 A xRA_UMS_MD_KZ1_BKLZ_KZ = A(1 ,"#RA-UMS-MD-KZ1-BKLZ-KZ");
|
||||
@L2 A xRA_UMS_MD_KZ1_TARIF = A(1 ,"#RA-UMS-MD-KZ1-TARIF");
|
||||
@L2 A xRA_UMS_WAEHRUNG_MD = A(3 ,"#RA-UMS-WAEHRUNG-MD");
|
||||
@L2 N xRN_UMS_LAST_LOAD = N(6,0 ,"#RN-UMS-LAST-LOAD");
|
||||
@L2 A xUMS_VORG_OPID = A(3 ,"#UMS-VORG-OPID");
|
||||
@L2 A xUMS_FORCE = A(1 ,"#UMS-FORCE"); // Beharrung auf eigene
|
||||
/* * /* PF-Tasten-Belegung */
|
||||
@L2 N xUMS_SNR_FIL = N(2,0 ,"#UMS-SNR-FIL");// Schaden-Filial-Nummer
|
||||
@L2 A xUMS_SNR_KREIS = A(2 ,"#UMS-SNR-KREIS");// Schaden-Kreis zB.:'SA'
|
||||
@L2 N xUMS_SNR_JAHR = N(4,0 ,"#UMS-SNR-JAHR");// Schaden-Jahr
|
||||
@L2 N xUMS_SNR_LFD_NR = N(6,0 ,"#UMS-SNR-LFD-NR");// Schaden-Laufende-Nummer
|
||||
@L2 A xUMS_SNR_STATUS = A(1 ,"#UMS-SNR-STATUS");// Status-Byte 'O','S','H'
|
||||
@L2 A xUMS_ART = A(2 ,"#UMS-ART"); // Schadenart
|
||||
@L2 A xUMS_REFERENT = A(8 ,"#UMS-REFERENT");// Referent
|
||||
@L2 A xUMS_EVI_TYP = A(1 ,"#UMS-EVI-TYP"); // eigen oder fremd-Vi
|
||||
@L2 P xUMS_IDENTCOUNTER = P(5,0 ,"#UMS-IDENTCOUNTER");
|
||||
@L2 P xUMS_OUTPUTNR = P(5,0 ,"#UMS-OUTPUTNR");
|
||||
@L2 P xUMS_EREIGNIS_DT = P(8,0 ,"#UMS-EREIGNIS-DT");// nur für LV (= Stornodatum)
|
||||
@L2 P xUMS_SCN_HIST = P(3,0 ,"#UMS-SCN-HIST");// merken Hist-Lesestand
|
||||
@L2 B xUMS_ANZEIGEDAT_KPL = B(3 ,"#UMS-ANZEIGEDAT-KPL");// für Aufruf SK10
|
||||
@L2 N xUMS_SKT_FIL = N(2,0 ,"#UMS-SKT-FIL");// Filial-Nummer Kontostamm
|
||||
@L2 A xUMS_SKT_KREIS = A(2 ,"#UMS-SKT-KREIS");// Schaden-Kreis Kontostamm
|
||||
@L2 N xUMS_SKT_JAHR = N(4,0 ,"#UMS-SKT-JAHR");// Schaden-Jahr Kontostamm
|
||||
@L2 N xUMS_SKT_LFD_NR = N(6,0 ,"#UMS-SKT-LFD-NR");// Schaden-Lfd-Nr Kontostamm
|
||||
@L2 N xUMS_SKT_BRAN = N(2,0 ,"#UMS-SKT-BRAN");// Schaden-Branche Kontostamm
|
||||
@L2 A xUMS_WORKSTATION_ID = A(8 ,"#UMS-WORKSTATION-ID");// Workstation (OD) *** frei ***
|
||||
@L2 A xRA_UMS_BRGR = A(1 ,"#RA-UMS-BRGR").defineArray(range(1,3));
|
||||
@L2 A xUMS_WF_ABT = A(8 ,"#UMS-WF-ABT"); // Workflow-Abteilung(für OD)
|
||||
@L2 A xUMS_WF_STAT = A(8 ,"#UMS-WF-STAT"); // Workflow-Station(für OD)
|
||||
@L2 A xUMS_WF_OPID = A(8 ,"#UMS-WF-OPID"); // Workflow-Opid(für OD)
|
||||
@L2 P xUMS_WF_SORT_DOKU_ART = P(3,0 ,"#UMS-WF-SORT-DOKU-ART").defineArray(range(1,5));// WF-Dokument(für OD)
|
||||
@L2 P xUMS_KEYCOUNTER = P(5,0 ,"#UMS-KEYCOUNTER").defineArray(range(1,10));// Identcounter aus File111
|
||||
@L2 A xUMS_SKT_BELEGTYP = A(1 ,"#UMS-SKT-BELEGTYP");// Schaden-Konto-Belegtyp
|
||||
@L2 P xUMS_SKT_POS_IDC = P(5,0 ,"#UMS-SKT-POS-IDC");// Identcounter Pos.Buchung-SKT
|
||||
@L2 P xUMS_SKT_BRAN_IDC = P(5,0 ,"#UMS-SKT-BRAN-IDC");// Identcounter Bran.Buchung-SKT
|
||||
@L2 P xUMS_SKT_ABST_IDC = P(5,0 ,"#UMS-SKT-ABST-IDC");// Identcounter Abst.Buchung-SKT
|
||||
@L2 A xUMS_TEXT_FN95 = A(1 ,"#UMS-TEXT-FN95");// Text-Option im FN95
|
||||
@L2 A xUMS_REGRESS_RATE = A(2 ,"#UMS-REGRESS-RATE");// Schaden-Regreß oder -Rate
|
||||
@L2 P xUMS_REGRESS_NR = P(6,0 ,"#UMS-REGRESS-NR");// Schaden-Regreß-Nr.
|
||||
@L2 A xUMS_DAT2_FN95 = A(1 ,"#UMS-DAT2-FN95");// 2. Datumszeile FN95 optional
|
||||
@L2 N xUMS_PC_DAT = N(8,0 ,"#UMS-PC-DAT"); // Pc-Antragsdatum
|
||||
@L2 N xUMS_PC_NUM = N(4,0 ,"#UMS-PC-NUM"); // Pc-Antragsnummer
|
||||
@L2 A xUMS_DRUCKER_ID = A(10 ,"#UMS-DRUCKER-ID");// Drucker-Id (OD)
|
||||
@L2 A xUMS_KEY_STAT_DI32 = A(8 ,"#UMS-KEY-STAT-DI32");// Station für AufrufDI32 (OD)
|
||||
@L2 B xUMS_SKT_ANZEIGEDAT_KPL = B(3 ,"#UMS-SKT-ANZEIGEDAT-KPL");// Anzeigedatum-KPL für Sammelb.
|
||||
@L2 P xUMS_WINDOW_NUM = P(2,0 ,"#UMS-WINDOW-NUM");// Image-Display (OD)
|
||||
@L2 P xUMS_STORNO_GRUND = P(2,0 ,"#UMS-STORNO-GRUND");// stornogrund (nur LV)
|
||||
@L2 A xUMS_DOI_KEY_ART = A(2 ,"#UMS-DOI-KEY-ART");// Kz f. Einschränkung Di30 (OD)
|
||||
@L2 P xUMS_SKT_WERTEDATUM = P(8,0 ,"#UMS-SKT-WERTEDATUM");// Schaden-Konto-Wertedatum
|
||||
@L2 P xUMS_SKT_BANKSCHLUESSEL = P(2,0 ,"#UMS-SKT-BANKSCHLUESSEL");// Schaden-Konto-Bankschlüssel
|
||||
@L2 A xUMS_REST = A(28 ,"#UMS-REST"); // Bytes frei
|
||||
@L1 A gPEL = A(250 ,"+PEL");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 A xRA_PEL_ABT = A(1 ,"#RA-PEL-ABT");
|
||||
@L2 N xRN_PEL_VPOL = N(8,0 ,"#RN-PEL-VPOL");
|
||||
@L2 N xRN_PEL_KPNR = N(8,0 ,"#RN-PEL-KPNR");
|
||||
@L2 N xRN_PEL_GT = N(8,0 ,"#RN-PEL-GT");
|
||||
@L2 N xRN_PEL_BR1 = N(8,0 ,"#RN-PEL-BR1");
|
||||
@L2 N xRN_PEL_BR2 = N(8,0 ,"#RN-PEL-BR2");
|
||||
@L2 N xRN_PEL_BUCH = N(8,0 ,"#RN-PEL-BUCH");
|
||||
@L2 N xRN_PEL_EINL = N(6,0 ,"#RN-PEL-EINL");
|
||||
@L2 A xRA_PEL_FREI = A(2 ,"#RA-PEL-FREI");
|
||||
@L2 N xRN_PEL_TEXT = N(8,0 ,"#RN-PEL-TEXT");
|
||||
@L2 N xRN_PEL_STAT = N(8,0 ,"#RN-PEL-STAT");
|
||||
@L2 N xRN_PEL_AGT = N(8,0 ,"#RN-PEL-AGT");
|
||||
@L2 N xRN_PEL_ABR1 = N(8,0 ,"#RN-PEL-ABR1");
|
||||
@L2 N xRN_PEL_ABR2 = N(8,0 ,"#RN-PEL-ABR2");
|
||||
@L2 N xRN_PEL_ATXT = N(8,0 ,"#RN-PEL-ATXT");
|
||||
@L2 N xRN_PEL_INDX = N(2,0 ,"#RN-PEL-INDX");
|
||||
@L2 N xRN_PEL_VDAT = N(6,0 ,"#RN-PEL-VDAT");
|
||||
@L2 A xRA_PEL_EFKZ = A(1 ,"#RA-PEL-EFKZ");
|
||||
@L2 A xRA_PEL_DRUC = A(4 ,"#RA-PEL-DRUC");
|
||||
@L2 A xRA_PEL_VART = A(3 ,"#RA-PEL-VART");
|
||||
@L2 N xRN_PEL_KREI = N(3,0 ,"#RN-PEL-KREI");
|
||||
@L2 A xRA_PEL_VAE = A(3 ,"#RA-PEL-VAE");
|
||||
@L2 A xRA_PEL_BNEU = A(3 ,"#RA-PEL-BNEU");
|
||||
@L2 A xRA_PEL_FILL118 = A(118 ,"#RA-PEL-FILL118");
|
||||
@L2 A xRA_PEL_MENU = A(2 ,"#RA-PEL-MENU");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 N xRN_KFZ_MENR = N(1,0 ,"#RN-KFZ-MENR");
|
||||
@L2 N xRN_KFZ_IEVI = N(3,0 ,"#RN-KFZ-IEVI");
|
||||
@L2 N xRN_KFZ_IVV = N(3,0 ,"#RN-KFZ-IVV");
|
||||
@L2 N xRN_KFZ_HIST = N(3,0 ,"#RN-KFZ-HIST");
|
||||
@L2 N xRN_KFZ_STND = N(3,0 ,"#RN-KFZ-STND");
|
||||
@L2 A xRA_KFZ_EVBR = A(30 ,"#RA-KFZ-EVBR");
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 A xRA_KFZ_BRA = A(3 ,"#RA-KFZ-BRA").defineArray(range(1, 10));
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 N xRN_KFZ_BRA1 = N(3,0 ,"#RN-KFZ-BRA1");
|
||||
@L3 N xRN_KFZ_BRA2 = N(3,0 ,"#RN-KFZ-BRA2");
|
||||
@L3 N xRN_KFZ_BRA3 = N(3,0 ,"#RN-KFZ-BRA3");
|
||||
@L3 N xRN_KFZ_BRA4 = N(3,0 ,"#RN-KFZ-BRA4");
|
||||
@L3 N xRN_KFZ_BRA5 = N(3,0 ,"#RN-KFZ-BRA5");
|
||||
@L2 N xRN_KFZ_VDAT = N(8,0 ,"#RN-KFZ-VDAT");
|
||||
@L2 A xRA_KFZ_AREV = A(1 ,"#RA-KFZ-AREV");
|
||||
@L2 A xRA_KFZ_SABE = A(3 ,"#RA-KFZ-SABE");
|
||||
@L2 A xRA_KFZ_RAG = A(1 ,"#RA-KFZ-RAG");
|
||||
@L2 P xRN_KFZ_OZW = P(3,0 ,"#RN-KFZ-OZW");
|
||||
@L2 N xRN_KFZ_VVAE = N(8,0 ,"#RN-KFZ-VVAE");
|
||||
@L2 A xRA_KFZ_VEF = A(1 ,"#RA-KFZ-VEF");
|
||||
@L2 A xRA_KFZ_VVAR = A(3 ,"#RA-KFZ-VVAR");
|
||||
@L2 N xRN_KFZ_VEGD = N(8,0 ,"#RN-KFZ-VEGD");
|
||||
@L2 N xRN_KFZ_VELD = N(8,0 ,"#RN-KFZ-VELD");
|
||||
@L2 A xRA_KFZ_VDRU = A(4 ,"#RA-KFZ-VDRU");
|
||||
@L2 A xRA_KFZ_PZSG = A(3 ,"#RA-KFZ-PZSG");
|
||||
@L2 N xRN_KFZ_LESE = N(3,0 ,"#RN-KFZ-LESE");
|
||||
@L2 N xRN_KFZ_FZW = N(2,0 ,"#RN-KFZ-FZW");
|
||||
@L2 N xRN_KFZ_VI10 = N(2,0 ,"#RN-KFZ-VI10");
|
||||
@L2 N xRN_KFZ_VD = N(8,0 ,"#RN-KFZ-VD");
|
||||
@L2 N xRN_KFZ_VB = N(8,0 ,"#RN-KFZ-VB");
|
||||
@L2 A xRA_KFZ_KFKZ = A(11 ,"#RA-KFZ-KFKZ");
|
||||
@L2 N xRN_KFZ_AWDT = N(8,0 ,"#RN-KFZ-AWDT");
|
||||
@L2 A xRA_KFZ_AWKZ = A(1 ,"#RA-KFZ-AWKZ");
|
||||
@L2 N xRN_KFZ_AEND = N(8,0 ,"#RN-KFZ-AEND");
|
||||
@L2 A xRA_KFZ_VABT = A(3 ,"#RA-KFZ-VABT");
|
||||
@L2 A xRA_KFZ_OPID = A(3 ,"#RA-KFZ-OPID");
|
||||
@L2 N xRN_KFZ_DATV = N(8,0 ,"#RN-KFZ-DATV");
|
||||
@L2 N xRN_KFZ_DATB = N(8,0 ,"#RN-KFZ-DATB");
|
||||
@L2 A xRA_KFZ_FIL = A(2 ,"#RA-KFZ-FIL");
|
||||
@L2 N xRN_KFZ_KRBR = N(4,0 ,"#RN-KFZ-KRBR");
|
||||
@L2 Redefine xRN_KFZ_KRBR; // REDEF. BEGIN : #RN-KFZ-KRBR
|
||||
@L3 N xRN_KFZ_KR = N(2,0 ,"#RN-KFZ-KR");
|
||||
@L3 N xRN_KFZ_BR = N(2,0 ,"#RN-KFZ-BR");
|
||||
@L2 A xRA_KFZ_VAE1 = A(3 ,"#RA-KFZ-VAE1");
|
||||
@L2 A xRA_KFZ_VAE2 = A(3 ,"#RA-KFZ-VAE2");
|
||||
@L2 A xRA_KFZ_SUMM = A(1 ,"#RA-KFZ-SUMM");
|
||||
@L2 N xRN_KFZ_STAT = N(2,0 ,"#RN-KFZ-STAT");
|
||||
@L2 A xRA_KFZ_GSNA = A(10 ,"#RA-KFZ-GSNA");
|
||||
@L2 A xRA_KFZ_BVNR = A(7 ,"#RA-KFZ-BVNR");
|
||||
@L2 N xRN_KFZ_ANTR = N(8,0 ,"#RN-KFZ-ANTR");
|
||||
@L2 N xRN_KFZ_VINK = N(8,0 ,"#RN-KFZ-VINK");
|
||||
@L2 N xRN_KFZ_FPNR = N(3,0 ,"#RN-KFZ-FPNR");
|
||||
@L2 A xRA_KFZ_MAPA = A(1 ,"#RA-KFZ-MAPA");
|
||||
@L2 L xRL_KFZ_PRVV = L("#RL-KFZ-PRVV");
|
||||
@L2 L xRL_KFZ_ST01 = L("#RL-KFZ-ST01");
|
||||
@L2 L xRL_KFZ_AUTO = L("#RL-KFZ-AUTO");
|
||||
@L2 A xRA_KFZ_OPI2 = A(3 ,"#RA-KFZ-OPI2");
|
||||
@L2 L xRL_KFZ_BLND = L("#RL-KFZ-BLND");
|
||||
@L2 C xRC_KFZ_BLND = C("#RC-KFZ-BLND");
|
||||
@L2 A xRA_KFZ_ATXT = A(10 ,"#RA-KFZ-ATXT");
|
||||
@L2 A xRA_KFZ_KTXT = A(3 ,"#RA-KFZ-KTXT");
|
||||
@L2 A xRA_KFZ_AMNU = A(8 ,"#RA-KFZ-AMNU");
|
||||
@L2 A xRA_KFZ_MENU = A(2 ,"#RA-KFZ-MENU");
|
||||
@L1 A gISN = A(250 ,"+ISN");
|
||||
@L1 Redefine gISN; // REDEF. BEGIN : +ISN
|
||||
@L2 A xRA_ISN_ISN = A(5 ,"#RA-ISN-ISN");
|
||||
@L2 Redefine xRA_ISN_ISN; // REDEF. BEGIN : #RA-ISN-ISN
|
||||
@L3 P xRN_ISN_AKUN = P(9,0 ,"#RN-ISN-AKUN");
|
||||
@L2 P xRN_ISN_HKUN = P(9,0 ,"#RN-ISN-HKUN");
|
||||
@L2 P xRN_ISN_VKUN = P(9,0 ,"#RN-ISN-VKUN");
|
||||
@L2 P xRN_ISN_ORTE = P(9,0 ,"#RN-ISN-ORTE");
|
||||
@L2 P xRN_ISN_KORP = P(9,0 ,"#RN-ISN-KORP");
|
||||
@L2 P xRN_ISN_KTAB = P(9,0 ,"#RN-ISN-KTAB");
|
||||
@L2 P xRN_ISN_ABKU = P(9,0 ,"#RN-ISN-ABKU");
|
||||
@L2 P xRN_ISN_OPOL = P(9,0 ,"#RN-ISN-OPOL");
|
||||
@L2 P xRN_ISN_SPOL = P(9,0 ,"#RN-ISN-SPOL");
|
||||
@L2 P xRN_ISN_OEVI = P(9,0 ,"#RN-ISN-OEVI");
|
||||
@L2 P xRN_ISN_SEVI = P(9,0 ,"#RN-ISN-SEVI");
|
||||
@L2 P xRN_ISN_OVV = P(9,0 ,"#RN-ISN-OVV");
|
||||
@L2 P xRN_ISN_SVV = P(9,0 ,"#RN-ISN-SVV");
|
||||
@L2 P xRN_ISN_OBU = P(9,0 ,"#RN-ISN-OBU");
|
||||
@L2 P xRN_ISN_SBU = P(9,0 ,"#RN-ISN-SBU");
|
||||
@L2 P xRN_ISN_BTS = P(9,0 ,"#RN-ISN-BTS");
|
||||
@L2 P xRN_ISN_BTK = P(9,0 ,"#RN-ISN-BTK");
|
||||
@L2 P xRN_ISN_EKPO = P(9,0 ,"#RN-ISN-EKPO");
|
||||
@L2 P xRN_ISN_EKVV = P(9,0 ,"#RN-ISN-EKVV");
|
||||
@L2 P xRN_ISN_EKK1 = P(9,0 ,"#RN-ISN-EKK1");
|
||||
@L2 P xRN_ISN_OPOS = P(9,0 ,"#RN-ISN-OPOS");
|
||||
@L2 P xRN_ISN_SPOS = P(9,0 ,"#RN-ISN-SPOS");
|
||||
@L2 P xRN_ISN_EKG1 = P(9,0 ,"#RN-ISN-EKG1");
|
||||
@L2 P xRN_ISN_HGPO = P(9,0 ,"#RN-ISN-HGPO");
|
||||
@L2 P xRN_ISN_HGKO = P(9,0 ,"#RN-ISN-HGKO");
|
||||
@L2 P xRN_ISN_OLS = P(9,0 ,"#RN-ISN-OLS");
|
||||
@L2 P xRN_ISN_SLS = P(9,0 ,"#RN-ISN-SLS");
|
||||
@L2 P xRN_ISN_HLS = P(9,0 ,"#RN-ISN-HLS");
|
||||
@L2 P xRN_ISN_VDNR = P(9,0 ,"#RN-ISN-VDNR");
|
||||
@L2 P xRN_ISN_MDST = P(9,0 ,"#RN-ISN-MDST");
|
||||
@L2 P xRN_ISN_KTOS = P(9,0 ,"#RN-ISN-KTOS");
|
||||
@L2 P xRN_ISN_KTOO = P(9,0 ,"#RN-ISN-KTOO");
|
||||
@L2 P xRN_ISN_ORTS = P(9,0 ,"#RN-ISN-ORTS");
|
||||
@L2 P xRN_ISN_FPNR = P(9,0 ,"#RN-ISN-FPNR");
|
||||
@L2 P xRN_ISN_WA49 = P(9,0 ,"#RN-ISN-WA49");
|
||||
@L2 P xRN_ISN_SD34 = P(8,0 ,"#RN-ISN-SD34");
|
||||
@L2 P xRN_ISN_SPER = P(9,0 ,"#RN-ISN-SPER");
|
||||
@L2 P xRN_ISN_MDS = P(9,0 ,"#RN-ISN-MDS");
|
||||
@L2 P xRN_ISN_SUCH = P(9,0 ,"#RN-ISN-SUCH");
|
||||
@L2 P xRN_ISN_ABST = P(9,0 ,"#RN-ISN-ABST");
|
||||
@L2 P xRN_ISN_UMKON = P(9,0 ,"#RN-ISN-UMKON");
|
||||
@L2 P xRN_ISN_MUBU = P(9,0 ,"#RN-ISN-MUBU");// MUTTERBUCHUNG (SK)
|
||||
@L2 P xRN_ISN_RESERVE = P(9,0 ,"#RN-ISN-RESERVE").defineArray(range(1, 5));
|
||||
@L1 A gKUN = A(250 ,"+KUN");
|
||||
@L1 Redefine gKUN; // REDEF. BEGIN : +KUN
|
||||
@L2 A xRA_KUN_KUNA = A(27 ,"#RA-KUN-KUNA");
|
||||
@L2 A xRA_KUN_GHKZ = A(1 ,"#RA-KUN-GHKZ");
|
||||
@L2 N xRN_KUN_GHK1 = N(8,0 ,"#RN-KUN-GHK1");
|
||||
@L2 N xRN_KUN_GHK2 = N(8,0 ,"#RN-KUN-GHK2");
|
||||
@L2 N xRN_KUN_DATV = N(8,0 ,"#RN-KUN-DATV");
|
||||
@L2 N xRN_KUN_DATB = N(8,0 ,"#RN-KUN-DATB");
|
||||
@L2 A xRA_KUN_BRAX = A(30 ,"#RA-KUN-BRAX");
|
||||
@L2 N xRN_KUN_FILV = N(2,0 ,"#RN-KUN-FILV");
|
||||
@L2 N xRN_KUN_FILB = N(2,0 ,"#RN-KUN-FILB");
|
||||
@L2 N xRN_KUN_ANZEI = N(2,0 ,"#RN-KUN-ANZEI");
|
||||
@L2 N xRN_KUN_BILKR = N(2,0 ,"#RN-KUN-BILKR");
|
||||
@L2 N xRN_KUN_POLK = N(2,0 ,"#RN-KUN-POLK");
|
||||
@L2 N xRN_KUN_POLB = N(2,0 ,"#RN-KUN-POLB");
|
||||
@L2 A xRA_KUN_BANKKU = A(5 ,"#RA-KUN-BANKKU");
|
||||
@L2 A xRA_KUN_BANKOR = A(27 ,"#RA-KUN-BANKOR");
|
||||
@L2 N xRN_KUN_BANKLE = N(5,0 ,"#RN-KUN-BANKLE");
|
||||
@L2 N xRN_KUN_TDAT = N(8,0 ,"#RN-KUN-TDAT");
|
||||
@L2 N xRN_KUN_VR = N(3,0 ,"#RN-KUN-VR");
|
||||
@L2 N xRN_KUN_VGVR = N(3,0 ,"#RN-KUN-VGVR");
|
||||
@L2 N xRN_KUN_STAT = N(3,0 ,"#RN-KUN-STAT");
|
||||
@L2 N xRN_KUN_HGV = N(2,0 ,"#RN-KUN-HGV");
|
||||
@L2 A xRA_KUN_UG1V = A(2 ,"#RA-KUN-UG1V");
|
||||
@L2 A xRA_KUN_UG2V = A(2 ,"#RA-KUN-UG2V");
|
||||
@L2 A xRA_KUN_UG3V = A(2 ,"#RA-KUN-UG3V");
|
||||
@L2 A xRA_KUN_SPV = A(1 ,"#RA-KUN-SPV");
|
||||
@L2 N xRN_KUN_HGB = N(2,0 ,"#RN-KUN-HGB");
|
||||
@L2 A xRA_KUN_UG1B = A(2 ,"#RA-KUN-UG1B");
|
||||
@L2 A xRA_KUN_UG2B = A(2 ,"#RA-KUN-UG2B");
|
||||
@L2 A xRA_KUN_UG3B = A(2 ,"#RA-KUN-UG3B");
|
||||
@L2 A xRA_KUN_SPB = A(1 ,"#RA-KUN-SPB");
|
||||
@L2 A xRA_KUN_LEER1 = A(38 ,"#RA-KUN-LEER1");
|
||||
@L2 N xRN_KUN_OVGEM = N(5,0 ,"#RN-KUN-OVGEM");
|
||||
@L2 N xRN_KUN_OVOKZ = N(6,0 ,"#RN-KUN-OVOKZ");
|
||||
@L2 N xRN_KUN_OVOBK = N(4,0 ,"#RN-KUN-OVOBK");
|
||||
@L2 A xRA_KUN_OVNAM = A(15 ,"#RA-KUN-OVNAM");
|
||||
@L2 A xRA_KUN_LEER2 = A(4 ,"#RA-KUN-LEER2");
|
||||
@L2 A xRA_KUN_OVFIL = A(2 ,"#RA-KUN-OVFIL");
|
||||
@L2 A xRA_KUN_OVSA = A(1 ,"#RA-KUN-OVSA");
|
||||
@L1 A gKEY = A(250 ,"+KEY");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 N xRN_KEY_VKKZ = N(8,0 ,"#RN-KEY-VKKZ");
|
||||
@L2 A xRA_KEY_VVA = A(2 ,"#RA-KEY-VVA");
|
||||
@L2 N xRN_KEY_VVNR = N(8,0 ,"#RN-KEY-VVNR");
|
||||
@L2 N xRN_KEY_VHNR = N(3,0 ,"#RN-KEY-VHNR");
|
||||
@L2 N xRN_KEY_VGES = N(3,0 ,"#RN-KEY-VGES");
|
||||
@L2 N xRN_KEY_VRES = N(1,0 ,"#RN-KEY-VRES");
|
||||
@L2 N xRN_KEY_VVIN = N(2,0 ,"#RN-KEY-VVIN");
|
||||
@L2 N xRN_KEY_VBRA = N(2,0 ,"#RN-KEY-VBRA");
|
||||
@L2 N xRN_KEY_VBRN = N(1,0 ,"#RN-KEY-VBRN");
|
||||
@L2 A xRA_KEY_VBTART = A(1 ,"#RA-KEY-VBTART");
|
||||
@L2 A xRA_KEY_VBSTAT = A(1 ,"#RA-KEY-VBSTAT");
|
||||
@L2 N xRN_KEY_VUGES = N(3,0 ,"#RN-KEY-VUGES");
|
||||
@L2 N xRN_KEY_VBJAHR = N(2,0 ,"#RN-KEY-VBJAHR");
|
||||
@L2 N xRN_KEY_AKTONR = N(7,0 ,"#RN-KEY-AKTONR");
|
||||
@L2 Redefine xRN_KEY_AKTONR; // REDEF. BEGIN : #RN-KEY-AKTONR
|
||||
@L3 A xRA_KEY_VKTO = A(7 ,"#RA-KEY-VKTO");
|
||||
@L2 A xRA_KEY_KOSEL = A(1 ,"#RA-KEY-KOSEL");
|
||||
@L2 A xRA_KEY_LEER1 = A(4 ,"#RA-KEY-LEER1");
|
||||
@L2 N xRN_KEY_AKKZ = N(8,0 ,"#RN-KEY-AKKZ");
|
||||
@L2 A xRA_KEY_AVA = A(2 ,"#RA-KEY-AVA");
|
||||
@L2 N xRN_KEY_AVNR = N(8,0 ,"#RN-KEY-AVNR");
|
||||
@L2 N xRN_KEY_AHNR = N(3,0 ,"#RN-KEY-AHNR");
|
||||
@L2 N xRN_KEY_AGES = N(3,0 ,"#RN-KEY-AGES");
|
||||
@L2 N xRN_KEY_ARES = N(1,0 ,"#RN-KEY-ARES");
|
||||
@L2 N xRN_KEY_AVIN = N(2,0 ,"#RN-KEY-AVIN");
|
||||
@L2 N xRN_KEY_ABRA = N(2,0 ,"#RN-KEY-ABRA");
|
||||
@L2 N xRN_KEY_ABRN = N(1,0 ,"#RN-KEY-ABRN");
|
||||
@L2 A xRA_KEY_ABTART = A(1 ,"#RA-KEY-ABTART");
|
||||
@L2 A xRA_KEY_ABSTAT = A(1 ,"#RA-KEY-ABSTAT");
|
||||
@L2 N xRN_KEY_AUGES = N(3,0 ,"#RN-KEY-AUGES");
|
||||
@L2 N xRN_KEY_ABJAHR = N(2,0 ,"#RN-KEY-ABJAHR");
|
||||
@L2 N xRN_KEY_KTONR = N(7,0 ,"#RN-KEY-KTONR");
|
||||
@L2 N xRN_KEY_PROV = N(2,0 ,"#RN-KEY-PROV");
|
||||
@L2 N xRN_KEY_PROD = N(2,0 ,"#RN-KEY-PROD");
|
||||
@L2 A xRA_KEY_PRKZ = A(1 ,"#RA-KEY-PRKZ");
|
||||
@L2 N xRN_KEY_AFGS = N(3,0 ,"#RN-KEY-AFGS");
|
||||
@L2 A xRA_KEY_AFPO = A(14 ,"#RA-KEY-AFPO");
|
||||
@L2 N xRN_KEY_VFGS = N(3,0 ,"#RN-KEY-VFGS");
|
||||
@L2 A xRA_KEY_VFPO = A(14 ,"#RA-KEY-VFPO");
|
||||
@L2 N xRN_KEY_FBRA = N(2,0 ,"#RN-KEY-FBRA");
|
||||
@L2 N xRN_KEY_NOTD = N(6,0 ,"#RN-KEY-NOTD");
|
||||
@L2 Redefine xRN_KEY_NOTD;
|
||||
@L3 N xRN_KEY_NOTD_YYYY = N(4,0 ,"#RN-KEY-NOTD-YYYY");
|
||||
@L3 N xRN_KEY_NOTD_MM = N(2,0 ,"#RN-KEY-NOTD-MM");
|
||||
@L2 N xRN_KEY_LSNR = N(2,0 ,"#RN-KEY-LSNR");
|
||||
@L2 N xRN_KEY_KBJ = N(4,0 ,"#RN-KEY-KBJ");
|
||||
@L2 N xRN_KEY_KBJNR = N(1,0 ,"#RN-KEY-KBJNR");
|
||||
@L2 A xRA_KEY_ZNAM = A(31 ,"#RA-KEY-ZNAM");
|
||||
@L2 A xRA_KEY_VNAM = A(2 ,"#RA-KEY-VNAM");
|
||||
@L2 A xRA_KEY_STRA = A(1 ,"#RA-KEY-STRA");
|
||||
@L2 N xRN_KEY_HAUS = N(4,0 ,"#RN-KEY-HAUS");
|
||||
@L2 N xRN_KEY_POST = N(6,0 ,"#RN-KEY-POST");
|
||||
@L2 A xRA_KEY_KZEX = A(1 ,"#RA-KEY-KZEX");
|
||||
@L2 A xRA_KEY_ACHT = A(1 ,"#RA-KEY-ACHT");
|
||||
@L2 N xRN_KEY_ALFA = N(2,0 ,"#RN-KEY-ALFA");
|
||||
@L2 N xRN_KEY_BERU = N(5,0 ,"#RN-KEY-BERU");
|
||||
@L2 A xRA_KEY_LEER2 = A(4 ,"#RA-KEY-LEER2");
|
||||
@L2 A xRA_KEY_BRSL = A(1 ,"#RA-KEY-BRSL");
|
||||
@L2 A xRA_KEY_PSTA = A(1 ,"#RA-KEY-PSTA");
|
||||
@L2 A xRA_KEY_BRAW = A(1 ,"#RA-KEY-BRAW");
|
||||
@L2 N xRN_KEY_BRVO = N(2,0 ,"#RN-KEY-BRVO");
|
||||
@L2 N xRN_KEY_BRBI = N(2,0 ,"#RN-KEY-BRBI");
|
||||
@L2 N xRN_KEY_JRVO = N(2,0 ,"#RN-KEY-JRVO");
|
||||
@L2 N xRN_KEY_JRBI = N(2,0 ,"#RN-KEY-JRBI");
|
||||
@L2 A xRA_KEY_VNAW = A(1 ,"#RA-KEY-VNAW");
|
||||
@L2 A xRA_KEY_KEIG = A(1 ,"#RA-KEY-KEIG");
|
||||
@L2 A xRA_KEY_SBID = A(3 ,"#RA-KEY-SBID");
|
||||
@L2 A xRA_KEY_KURO = A(2 ,"#RA-KEY-KURO");
|
||||
@L2 A xRA_KEY_PRAE = A(1 ,"#RA-KEY-PRAE");
|
||||
@L2 A xRA_KEY_PROV = A(1 ,"#RA-KEY-PROV");
|
||||
@L2 A xRA_KEY_OFFE = A(1 ,"#RA-KEY-OFFE");
|
||||
@L2 A xRA_KEY_AKTU = A(1 ,"#RA-KEY-AKTU");
|
||||
@L2 A xRA_KEY_ALLE = A(1 ,"#RA-KEY-ALLE");
|
||||
@L2 A xRA_KEY_AOFA = A(1 ,"#RA-KEY-AOFA");
|
||||
@L2 N xRN_KEY_BUDT = N(8,0 ,"#RN-KEY-BUDT");
|
||||
@L2 P xRN_KEY_BUNR = P(9,0 ,"#RN-KEY-BUNR");
|
||||
@L2 A xRA_KEY_VKZ = A(1 ,"#RA-KEY-VKZ");
|
||||
@L2 A xRA_KEY_ABGA = A(1 ,"#RA-KEY-ABGA");
|
||||
@L2 A xRA_KEY_SART = A(2 ,"#RA-KEY-SART");
|
||||
@L2 A xRA_KEY_PFIL = A(2 ,"#RA-KEY-PFIL");
|
||||
@L2 L xRL_KEY_UHR = L("#RL-KEY-UHR");
|
||||
@L2 A xRA_KEY_STAT = A(1 ,"#RA-KEY-STAT");
|
||||
@L2 A xRA_KEY_GSMT = A(1 ,"#RA-KEY-GSMT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_1 = A(8 ,"#RA-KEY-08X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVO = A(22 ,"#RA-KEY-VVO");
|
||||
@L2 A xRA_KEY_27X_1 = A(27 ,"#RA-KEY-27X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVO = A(22 ,"#RA-KEY-AVO");
|
||||
@L2 A xRA_KEY_27X_2 = A(27 ,"#RA-KEY-27X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVO = A(22 ,"#RA-KEY-GVO");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_2 = A(8 ,"#RA-KEY-08X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VBT = A(24 ,"#RA-KEY-VBT");
|
||||
@L2 A xRA_KEY_24X = A(24 ,"#RA-KEY-24X"); // ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_ABT = A(24 ,"#RA-KEY-ABT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_3 = A(8 ,"#RA-KEY-08X-3");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVK = A(16 ,"#RA-KEY-VVK");
|
||||
@L2 A xRA_KEY_33X_1 = A(33 ,"#RA-KEY-33X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVK = A(16 ,"#RA-KEY-AVK");
|
||||
@L2 A xRA_KEY_33X_2 = A(33 ,"#RA-KEY-33X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVK = A(16 ,"#RA-KEY-GVK");
|
||||
@L1 A gSTV = A(250 ,"+STV");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_APRG = A(8 ,"#RA-STV-APRG");
|
||||
@L2 A xRA_STV_VPRG = A(8 ,"#RA-STV-VPRG");
|
||||
@L2 A xRA_STV_FUNK = A(4 ,"#RA-STV-FUNK");
|
||||
@L2 Redefine xRA_STV_FUNK; // REDEF. BEGIN : #RA-STV-FUNK
|
||||
@L3 N xRN_STV_FUNR = N(2,0 ,"#RN-STV-FUNR");
|
||||
@L3 A xRA_STV_FUNR = A(2 ,"#RA-STV-FUNR");
|
||||
@L2 N xRN_STV_AUSW = N(2,0 ,"#RN-STV-AUSW");
|
||||
@L2 A xRA_STV_CODE = A(4 ,"#RA-STV-CODE"); // 30
|
||||
@L2 N xRN_STV_OBNR = N(8,0 ,"#RN-STV-OBNR");
|
||||
@L2 A xRA_STV_OART = A(2 ,"#RA-STV-OART");
|
||||
@L2 N xRN_STV_HINR = N(3,0 ,"#RN-STV-HINR");
|
||||
@L2 N xRN_STV_VINR = N(2,0 ,"#RN-STV-VINR");
|
||||
@L2 N xRN_STV_BRAN = N(2,0 ,"#RN-STV-BRAN");
|
||||
@L2 N xRN_STV_BRNR = N(1,0 ,"#RN-STV-BRNR");
|
||||
@L2 N xRN_STV_GENR = N(3,0 ,"#RN-STV-GENR");
|
||||
@L2 A xRA_STV_GMOD = A(1 ,"#RA-STV-GMOD");
|
||||
@L2 A xRA_STV_AMOD = A(1 ,"#RA-STV-AMOD");
|
||||
@L2 A xRA_STV_VIKZ = A(1 ,"#RA-STV-VIKZ");
|
||||
@L2 A xRA_STV_DATI = A(8 ,"#RA-STV-DATI");
|
||||
@L2 N xRN_STV_DATA = N(3,0 ,"#RN-STV-DATA");
|
||||
@L2 A xRA_STV_OPID = A(3 ,"#RA-STV-OPID");
|
||||
@L2 A xRA_STV_PFKY = A(96 ,"#RA-STV-PFKY");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PFF = A(4 ,"#RA-STV-PFF");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PF = A(4 ,"#RA-STV-PF").defineArray(range(1, 24));
|
||||
@L2 N xRN_STV_DATN = N(6,0 ,"#RN-STV-DATN");
|
||||
@L2 A xRA_STV_MMOD = A(3 ,"#RA-STV-MMOD");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_241X = A(241 ,"#RA-STV-241X");
|
||||
@L2 A xRA_STV_APAR = A(1 ,"#RA-STV-APAR");
|
||||
@L2 P xRN_STV_CURS = P(6,0 ,"#RN-STV-CURS");
|
||||
@L2 P xRN_STV_MARK = P(3,0 ,"#RN-STV-MARK");
|
||||
@L2 B xRN_STV_FNUM = B(2 ,"#RN-STV-FNUM");
|
||||
@L1 A gKEY1 = A(250 ,"+KEY1");
|
||||
@L1 Redefine gKEY1; // REDEF. BEGIN : +KEY1
|
||||
@L2 A xRA_KEY1_ASSTART = A(4 ,"#RA-KEY1-ASSTART");
|
||||
@L2 N xRN_KEY1_POSVI = N(2,0 ,"#RN-KEY1-POSVI");
|
||||
@L2 N xRN_KEY1_POSVII = N(3,0 ,"#RN-KEY1-POSVII");
|
||||
@L2 A xRA_KEY1_RVART = A(4 ,"#RA-KEY1-RVART");
|
||||
@L2 N xRN_KEY1_RVVON = N(8,0 ,"#RN-KEY1-RVVON");
|
||||
@L2 N xRN_KEY1_RVBHJAHR = N(4,0 ,"#RN-KEY1-RVBHJAHR");
|
||||
@L2 N xRN_KEY1_RVBBNR = N(2,0 ,"#RN-KEY1-RVBBNR");
|
||||
@L2 P xRN_KEY1_SBVANZ = P(1,0 ,"#RN-KEY1-SBVANZ");
|
||||
@L2 P xRN_KEY1_SBVINR = P(2,0 ,"#RN-KEY1-SBVINR").defineArray(range(1, 2));
|
||||
@L2 P xRN_KEY1_SBVII = P(2,0 ,"#RN-KEY1-SBVII").defineArray(range(1, 2));
|
||||
@L2 N xRN_KEY1_BRA = N(2,0 ,"#RN-KEY1-BRA").defineArray(range(1, 3));
|
||||
@L2 N xRN_KEY1_GEBDT = N(8,0 ,"#RN-KEY1-GEBDT");
|
||||
@L2 A xRA_KEY1_2X = A(2 ,"#RA-KEY1-2X");
|
||||
@L2 A xRA_KEY1_WAEHRUNG = A(3 ,"#RA-KEY1-WAEHRUNG");
|
||||
@L2 A xRA_KEY1_SK_ALLE = A(1 ,"#RA-KEY1-SK-ALLE");
|
||||
@L2 N xRN_KEY1_HNR = N(4,0 ,"#RN-KEY1-HNR");
|
||||
@L2 A xRA_KEY1_MTVKZ = A(1 ,"#RA-KEY1-MTVKZ");
|
||||
@L2 A xRA_KEY1_VIL = A(1 ,"#RA-KEY1-VIL");
|
||||
@L2 A xRA_KEY1_OKZ = A(3 ,"#RA-KEY1-OKZ");
|
||||
@L2 N xRN_KEY1_OPLZ = N(6,0 ,"#RN-KEY1-OPLZ");
|
||||
@L2 N xRN_KEY1_OHNR = N(4,0 ,"#RN-KEY1-OHNR");
|
||||
@L2 P xRN_KEY1_SUBVR = P(3,0 ,"#RN-KEY1-SUBVR");
|
||||
@L2 P xRN_KEY1_SUBKTO = P(7,0 ,"#RN-KEY1-SUBKTO");
|
||||
@L2 P xRN_KEY1_SUBZWST = P(3,0 ,"#RN-KEY1-SUBZWST");
|
||||
@L2 A xRA_KEY1_SKART = A(3 ,"#RA-KEY1-SKART");
|
||||
@L2 A xRA_KEY1_TCSART = A(1 ,"#RA-KEY1-TCSART");
|
||||
@L2 A xRA_KEY1_TCZ1 = A(2 ,"#RA-KEY1-TCZ1");
|
||||
@L2 Redefine xRA_KEY1_TCZ1; // REDEF. BEGIN : #RA-KEY1-TCZ1
|
||||
@L3 N xRN_KEY1_TCZ1 = N(2,0 ,"#RN-KEY1-TCZ1");
|
||||
@L2 P xRN_KEY1_TCODE = P(5,0 ,"#RN-KEY1-TCODE");
|
||||
@L2 P xRN_KEY1_TCZ2 = P(2,0 ,"#RN-KEY1-TCZ2");
|
||||
@L2 A xRA_KEY1_NATV = A(3 ,"#RA-KEY1-NATV");
|
||||
@L2 A xRA_KEY1_NATO = A(3 ,"#RA-KEY1-NATO");
|
||||
@L2 N xRN_KEY1_SNR = N(8,0 ,"#RN-KEY1-SNR");
|
||||
@L2 N xRN_KEY1_LNR = N(5,0 ,"#RN-KEY1-LNR");
|
||||
@L2 A xRA_KEY1_ZISB = A(3 ,"#RA-KEY1-ZISB");
|
||||
@L2 N xRN_KEY1_STAT = N(5,0 ,"#RN-KEY1-STAT");
|
||||
@L2 N xRN_KEY1_LZ = N(2,0 ,"#RN-KEY1-LZ");
|
||||
@L2 A xRA_KEY1_GRDHA = A(2 ,"#RA-KEY1-GRDHA");
|
||||
@L2 A xRA_KEY1_NATOV = A(3 ,"#RA-KEY1-NATOV");
|
||||
@L2 N xRN_KEY1_AWDT = N(8,0 ,"#RN-KEY1-AWDT");
|
||||
@L2 A xRA_KEY1_KOSP = A(1 ,"#RA-KEY1-KOSP");
|
||||
@L2 N xRN_KEY1_EDSTAT = N(2,0 ,"#RN-KEY1-EDSTAT");
|
||||
@L2 A xRA_KEY1_FREI = A(7 ,"#RA-KEY1-FREI");
|
||||
@L2 N xRN_KEY1_KDAT = N(8,0 ,"#RN-KEY1-KDAT");
|
||||
@L2 N xRN_KEY1_KGRD = N(2,0 ,"#RN-KEY1-KGRD");
|
||||
@L2 N xRN_KEY1_PERSNR = N(5,0 ,"#RN-KEY1-PERSNR");
|
||||
@L2 A xRA_KEY1_RPART = A(2 ,"#RA-KEY1-RPART");
|
||||
@L2 N xRN_KEY1_RPDAT = N(8,0 ,"#RN-KEY1-RPDAT");
|
||||
@L2 A xRA_KEY1_RVKZ = A(1 ,"#RA-KEY1-RVKZ");
|
||||
@L2 A xRA_KEY1_RVKZ2 = A(1 ,"#RA-KEY1-RVKZ2");
|
||||
@L2 A xRA_KEY1_LK = A(30 ,"#RA-KEY1-LK");
|
||||
@L2 A xRA_KEY1_SCHEMA = A(2 ,"#RA-KEY1-SCHEMA");
|
||||
@L2 N xRN_KEY1_RSOBNR = N(8,0 ,"#RN-KEY1-RSOBNR");
|
||||
@L2 A xRA_KEY1_RSKZ = A(1 ,"#RA-KEY1-RSKZ");
|
||||
@L2 A xRA_KEY1_BRFA = A(3 ,"#RA-KEY1-BRFA");
|
||||
@L2 A xRA_KEY1_BUCHART = A(3 ,"#RA-KEY1-BUCHART");
|
||||
@L2 N xRN_KEY1_BANKSCHL = N(3,0 ,"#RN-KEY1-BANKSCHL");
|
||||
@L2 N xRN_KEY1_WERTEDAT = N(8,0 ,"#RN-KEY1-WERTEDAT");
|
||||
@L2 A xRA_KEY1_VORPROG = A(8 ,"#RA-KEY1-VORPROG");
|
||||
@L2 N xRN_KEY1_GILT_BIS = N(8,0 ,"#RN-KEY1-GILT-BIS");
|
||||
@L2 P xRN_KEY1_FNR = P(3,0 ,"#RN-KEY1-FNR");
|
||||
@L2 A xRA_KEY1_KO55MELD = A(1 ,"#RA-KEY1-KO55MELD");
|
||||
@L1 A gKEY2 = A(250 ,"+KEY2");
|
||||
@L1 Redefine gKEY2; // REDEF. BEGIN : +KEY2
|
||||
@L2 A xRA_KEY2_TARIF = A(7 ,"#RA-KEY2-TARIF");
|
||||
@L2 A xRA_KEY2_BANKNA = A(27 ,"#RA-KEY2-BANKNA");
|
||||
@L2 A xRA_KEY2_USERGRUPPE = A(8 ,"#RA-KEY2-USERGRUPPE");
|
||||
@L2 N xRN_KEY2_KOSTST = N(5,0 ,"#RN-KEY2-KOSTST");
|
||||
@L2 A xRA_KEY2_AVISO_OP = A(3 ,"#RA-KEY2-AVISO-OP");
|
||||
@L2 N xRN_KEY2_AVISO_DT = N(8,0 ,"#RN-KEY2-AVISO-DT");
|
||||
@L2 N xRN_KEY2_AVISO_GD = N(3,0 ,"#RN-KEY2-AVISO-GD");
|
||||
@L2 N xRN_KEY2_AVISO_DTB = N(8,0 ,"#RN-KEY2-AVISO-DTB");
|
||||
@L2 A xRA_KEY2_SPRACH_KZ = A(2 ,"#RA-KEY2-SPRACH-KZ");
|
||||
@L2 A xRA_KEY2_GEWINN_VB = A(4 ,"#RA-KEY2-GEWINN-VB");
|
||||
@L2 N xRN_KEY2_GEWINN_JAHR = N(4,0 ,"#RN-KEY2-GEWINN-JAHR");
|
||||
@L2 A xRA_KEY2_ABST_SA = A(2 ,"#RA-KEY2-ABST-SA");
|
||||
@L2 A xRA_KEY2_ABST_ZA = A(1 ,"#RA-KEY2-ABST-ZA");
|
||||
@L2 P xRN_KEY2_ABST_ZR = P(9,0 ,"#RN-KEY2-ABST-ZR");
|
||||
@L2 A xRA_KEY2_ABST_NK = A(1 ,"#RA-KEY2-ABST-NK");
|
||||
@L2 A xRA_KEY2_ABST_NA = A(8 ,"#RA-KEY2-ABST-NA");
|
||||
@L2 A xRA_KEY2_FAB = A(3 ,"#RA-KEY2-FAB");
|
||||
@L2 A xRA_KEY2_TYP = A(10 ,"#RA-KEY2-TYP");
|
||||
@L2 A xRA_KEY2_FZA = A(1 ,"#RA-KEY2-FZA");
|
||||
@L2 N xRN_KEY2_LST = N(1,0 ,"#RN-KEY2-LST");
|
||||
@L2 N xRN_KEY2_KTBJ = N(4,0 ,"#RN-KEY2-KTBJ");
|
||||
@L2 N xRN_KEY2_KW = N(3,0 ,"#RN-KEY2-KW");
|
||||
@L2 P xRN_KEY2_UEBWNR = P(6,0 ,"#RN-KEY2-UEBWNR");
|
||||
@L2 P xRN_KEY2_UNTNR = P(4,0 ,"#RN-KEY2-UNTNR");
|
||||
@L2 A xRA_KEY2_SERIE = A(1 ,"#RA-KEY2-SERIE");
|
||||
@L2 P xRN_KEY2_PAKNR = P(9,0 ,"#RN-KEY2-PAKNR");
|
||||
@L2 A xRA_KEY2_WHG_VON = A(3 ,"#RA-KEY2-WHG-VON");
|
||||
@L2 A xRA_KEY2_WHG_NACH = A(3 ,"#RA-KEY2-WHG-NACH");
|
||||
@L2 A xRA_KEY2_EXTSA = A(3 ,"#RA-KEY2-EXTSA");
|
||||
@L2 N xRN_KEY2_GKNR = N(8,0 ,"#RN-KEY2-GKNR");
|
||||
@L1 C xCV_WHBS = C("#CV-WHBS"); // GW FUER WIEDERHOLTE BS-AUSG.
|
||||
@L1 C xCV_OUTP = C("#CV-OUTP"); // PROTECT BEI A+B - PROGRAMMEN
|
||||
@L1 C xCV_STV = C("#CV-STV"); // KEY-FELDER STEUERLEISTE
|
||||
@L1 L xWL_KEYWECHS = L("#WL-KEYWECHS"); // SCHALTER, OB KEYWECHSEL
|
||||
@L1 L xWL_INTERN = L("#WL-INTERN"); // SCHALTER, OB BLINDER DURCHL.
|
||||
@L1 A xWA_HELP_TASTE = A(4 ,"#WA-HELP-TASTE"); // PF-TASTE FUER 'HELP'
|
||||
@L1 A xWA_MENU_TASTE = A(4 ,"#WA-MENU-TASTE"); // PF-TASTE FUER 'MENU'
|
||||
@L1 P xWN_SEL_ISN = P(9,0 ,"#WN-SEL-ISN").defineArray(range(1,24));
|
||||
@L1 A xWA_FUNKTION = A(5 ,"#WA-FUNKTION").defineArray(range(1,100));// FUNKTIONSTABELLE
|
||||
@L1 A xWA_VORWEG_AUFRUF = A(8 ,"#WA-VORWEG-AUFRUF");// VORWEG-AUFRUF (Z.B. SCHWEBE)
|
||||
@L1 L xWL_TEST = L("#WL-TEST"); // KENNZEICHEN, OB TEST
|
||||
@L1 N xWN_WARTEN_SUMME = N(5,0 ,"#WN-WARTEN-SUMME");// ZAEHLER FUER SUSPEND
|
||||
@L1 A xWA_AUSWAHL_KZ = A(1 ,"#WA-AUSWAHL-KZ").defineArray(range(1,24));// MARKIERUNGSLEISTE
|
||||
@L1 N xWN_AUSWAHL_ANZAHL = N(2,0 ,"#WN-AUSWAHL-ANZAHL");
|
||||
@L1 A xWA_AUSWAHL_GUELTIG = A(20 ,"#WA-AUSWAHL-GUELTIG");
|
||||
/*
|
||||
* Platz für neue globale Variable
|
||||
* Definitionen in:
|
||||
* YLNEUGLO/YCNEUGLO
|
||||
*/
|
||||
@L1 A gNEUGLO_01 = A(250 ,"+NEUGLO-01");
|
||||
@L1 Redefine gNEUGLO_01;
|
||||
@L2 A xBTC_STEUERUNG = A(8 ,"#BTC-STEUERUNG");
|
||||
@L2 A xUMS_MIS_BILANZKREIS = A(1 ,"#UMS-MIS-BILANZKREIS").defineArray(range(1, 7));
|
||||
@L2 A xUMS_MIS_BRAN = A(2 ,"#UMS-MIS-BRAN").defineArray(range(1, 10));
|
||||
@L2 P xKEY1_SUCH_BETRAG = P(9,2 ,"#KEY1-SUCH-BETRAG");
|
||||
@L2 P xKEY2_ARCHIV_DB = P(3,0 ,"#KEY2-ARCHIV-DB");
|
||||
@L2 A xKEY2_EXTKZ = A(1 ,"#KEY2-EXTKZ");
|
||||
@L2 N xRN_KEY_JAHRV = N(4,0 ,"#RN-KEY-JAHRV");
|
||||
@L2 N xRN_KEY_JAHRB = N(4,0 ,"#RN-KEY-JAHRB");
|
||||
@L2 A xRA_KEY_EXT_GRUND_NAME = A(31 ,"#RA-KEY-EXT-GRUND-NAME");
|
||||
@L2 P xRN_KEY_EXT_BB_VON = P(8,0 ,"#RN-KEY-EXT-BB-VON");
|
||||
@L2 P xRN_KEY_EXT_BB_BIS = P(8,0 ,"#RN-KEY-EXT-BB-BIS");
|
||||
@L2 P xUMS_MA_SUB_SATZART_1 = P(3,0 ,"#UMS-MA-SUB-SATZART-1");
|
||||
@L2 A xRA_KEY_EXT_ANTRAGSART = A(3 ,"#RA-KEY-EXT-ANTRAGSART");
|
||||
@L2 P xFLV_TRANS = P(7,0 ,"#FLV-TRANS");
|
||||
@L2 A xFLV_ISIN = A(12 ,"#FLV-ISIN");
|
||||
@L2 P xFLV_BUNR = P(7,0 ,"#FLV-BUNR");
|
||||
@L2 P xFLV_GILT_BIS = P(8,0 ,"#FLV-GILT-BIS");
|
||||
@L2 P xFLV_DATUM = P(8,0 ,"#FLV-DATUM");
|
||||
@L2 P xFLV_VV_VINR = P(3,0 ,"#FLV-VV-VINR");
|
||||
@L2 P xFLV_VV_BRAN = P(3,0 ,"#FLV-VV-BRAN");
|
||||
@L2 P xFLV_VV_BRNR = P(3,0 ,"#FLV-VV-BRNR");
|
||||
@L2 P xFLV_VV_HINR = P(3,0 ,"#FLV-VV-HINR");
|
||||
@L2 A xVLK_EXVERARB = A(30 ,"#VLK-EXVERARB");
|
||||
@L2 P xRN_KEY_IVKNR = P(8,0 ,"#RN-KEY-IVKNR");
|
||||
@L2 A xRA_SC_MELDUNGS_ART = A(1 ,"#RA-SC-MELDUNGS-ART");
|
||||
@L2 A xRA_OS_GFALL = A(2 ,"#RA-OS-GFALL");
|
||||
@L2 A xRA_OS_DOKART = A(4 ,"#RA-OS-DOKART");
|
||||
@L2 A xRA_OS_BR_ART = A(3 ,"#RA-OS-BR-ART");
|
||||
@L2 N xRN_KEY_YU_PERSNR = N(13,0 ,"#RN-KEY-YU-PERSNR");
|
||||
@L2 N xSCN_VERS_BEGINN = N(8,0 ,"#SCN-VERS-BEGINN");
|
||||
@L2 A xDI_ABTEILUNG = A(8 ,"#DI-ABTEILUNG");
|
||||
@L2 P xDI_STAT_DZT = P(3,0 ,"#DI-STAT-DZT");
|
||||
@L2 P xDI_STAT_DZT_BIS = P(3,0 ,"#DI-STAT-DZT-BIS");
|
||||
@L2 A xDI_SPARTE = A(2 ,"#DI-SPARTE");
|
||||
@L2 A xDI_STATION = A(8 ,"#DI-STATION");
|
||||
@L2 A xDI_WM_OPID = A(8 ,"#DI-WM-OPID");
|
||||
@L2 A xDI_AW_POL_FIL = A(1 ,"#DI-AW-POL-FIL");
|
||||
@L2 A xDI_VAE_ART = A(3 ,"#DI-VAE-ART");
|
||||
@L2 A xDI_WK_VAE_ART = A(3 ,"#DI-WK-VAE-ART");
|
||||
@L2 A xDI_AW_SUB_VR = A(1 ,"#DI-AW-SUB-VR");
|
||||
@L2 P xDI_SUB_VR = P(3,0 ,"#DI-SUB-VR");
|
||||
@L2 P xDI_POL_KREIS = P(2,0 ,"#DI-POL-KREIS");
|
||||
@L2 P xDI_POL_BRAN = P(2,0 ,"#DI-POL-BRAN");
|
||||
@L2 A xDI_VAE_HINW = A(3 ,"#DI-VAE-HINW");
|
||||
@L1 A gNEUGLO_02 = A(250 ,"+NEUGLO-02");
|
||||
@L1 Redefine gNEUGLO_02;
|
||||
@L2 A xDI_BEARB_GRP = A(3 ,"#DI-BEARB-GRP");
|
||||
@L2 A xDI_AW_DRF = A(1 ,"#DI-AW-DRF");
|
||||
@L2 A xDI_AW_E_ANT = A(1 ,"#DI-AW-E-ANT");
|
||||
@L2 P xDI_PC_ANT_VKTO = P(7,0 ,"#DI-PC-ANT-VKTO");
|
||||
@L2 P xDI_PLZ_VON = P(7,0 ,"#DI-PLZ-VON");
|
||||
@L2 P xDI_PLZ_BIS = P(7,0 ,"#DI-PLZ-BIS");
|
||||
@L2 A xDI_POSTNATION = A(3 ,"#DI-POSTNATION");
|
||||
@L2 P xDI_WM_DATUM = P(8,0 ,"#DI-WM-DATUM");
|
||||
@L2 P xDI_POL_FILIALE = P(2,0 ,"#DI-POL-FILIALE");
|
||||
@L2 A xDI_AW_VAE_HINW = A(1 ,"#DI-AW-VAE-HINW");
|
||||
@L2 A xDI_AW_VKTO = A(1 ,"#DI-AW-VKTO");
|
||||
@L2 A xDI_AW_BEARB_GRP = A(1 ,"#DI-AW-BEARB-GRP");
|
||||
@L2 A xDI_AW_KR_BR = A(1 ,"#DI-AW-KR-BR");
|
||||
@L2 A xDI_AW_VAE_ART = A(1 ,"#DI-AW-VAE-ART");
|
||||
@L2 P xDI_VR_KEY = P(3,0 ,"#DI-VR-KEY");
|
||||
@L2 A xDI_OART = A(2 ,"#DI-OART");
|
||||
@L2 P xDI_WM_DATUM_VON = P(8,0 ,"#DI-WM-DATUM-VON");
|
||||
@L2 A xDI_WM_AUSWAHL = A(1 ,"#DI-WM-AUSWAHL");
|
||||
@L2 A xRA_KEY_OIB = A(20 ,"#RA-KEY-OIB");
|
||||
|
||||
|
||||
}
|
||||
@@ -201,8 +201,10 @@ public final class CypherQueries {
|
||||
*/
|
||||
public static final String DELETE_STALE_NATURAL_TABLE_ACCESS_EDGES = """
|
||||
UNWIND $files AS p
|
||||
MATCH (src:AstNode {project: $project, sourceFile: p.f, language: 'natural'})-[r:READS|WRITES]->(t:AstNode {project: $project, sourceFile: ""})
|
||||
MATCH (src:AstNode {project: $project, sourceFile: p.f})-[r:READS|WRITES]->(t:AstNode {project: $project, sourceFile: ""})
|
||||
USING INDEX src:AstNode(project, sourceFile)
|
||||
WHERE src.ownerModule IN p.os
|
||||
AND src.language IN ['natural', 'natjav']
|
||||
AND t.type IN ['DB_TABLE', 'WORKFILE']
|
||||
DELETE r
|
||||
""";
|
||||
@@ -225,8 +227,9 @@ public final class CypherQueries {
|
||||
*/
|
||||
public static final String DELETE_STALE_NATURAL_USING_EDGES = """
|
||||
UNWIND $files AS p
|
||||
MATCH (src:AstNode {project: $project, sourceFile: p.f, language: 'natural'})-[r:INCLUDES]->(d:AstNode {project: $project, type: 'DATA_STRUCTURE'})
|
||||
MATCH (src:AstNode {project: $project, sourceFile: p.f})-[r:INCLUDES]->(d:AstNode {project: $project, type: 'DATA_STRUCTURE'})
|
||||
WHERE src.ownerModule IN p.os
|
||||
AND src.language IN ['natural', 'natjav']
|
||||
DELETE r
|
||||
""";
|
||||
|
||||
@@ -1546,7 +1549,9 @@ public final class CypherQueries {
|
||||
// one that used to answer 404 "no such module" for a name that exists twice. It is a
|
||||
// deliberate record, not leftover scaffolding.
|
||||
AND ph.duplicatePaths IS NULL
|
||||
DELETE ph
|
||||
// Batched: this is the node sweep that follows the two CONTAINS sweeps above, so its size
|
||||
// follows theirs — on `korin` 313,088 placeholder VARIABLEs alone become edgeless here.
|
||||
CALL { WITH ph DELETE ph } IN TRANSACTIONS OF $batchSize ROWS
|
||||
""";
|
||||
/**
|
||||
* Scoped variant of {@link #LINK_ARGS_TO_PARAMS_JAVA_CROSS} for by-name deep ingest ({@code $names}).
|
||||
@@ -2070,16 +2075,24 @@ public final class CypherQueries {
|
||||
* final graph: a reference whose target file has since been ingested reads {@code unresolved =
|
||||
* false} (a real twin exists), a genuinely dangling one (missing module, unresolvable dynamic
|
||||
* {@code CALLNAT} variable) reads {@code true}. This is the resolution pass item 40 asks for â
|
||||
* project-wide, cheap (bounded by placeholder count), idempotent, and safe in every ingest mode.
|
||||
* project-wide, idempotent, and safe in every ingest mode.
|
||||
*
|
||||
* <p><b>{@code EXISTS}, not {@code count}.</b> The question is whether a real twin exists, and the
|
||||
* earlier {@code OPTIONAL MATCH … count(real) = 0} answered it by enumerating <em>every</em> node
|
||||
* sharing the placeholder's {@code (type, name)} -- for a field name that recurs across hundreds of
|
||||
* files, thousands of rows per placeholder, all to compare against zero. Once NatJav dataflow
|
||||
* (item E1) raised `korin`'s placeholder count to 340,667 that cost 274 s, the single most
|
||||
* expensive step of the refresh. The existential form short-circuits at the first match and
|
||||
* computes the identical flag: 274 s -> 15.7 s, same 6,973 unresolved.
|
||||
* Reverse queries ({@code callers}, {@code callees}, {@code search_identifier}) surface the flag so
|
||||
* dynamic/dangling call sites are visible yet distinguishable from resolved references.
|
||||
*/
|
||||
public static final String STAMP_UNRESOLVED_PLACEHOLDERS = """
|
||||
MATCH (ph:AstNode {project: $project, sourceFile: ""})
|
||||
OPTIONAL MATCH (real:AstNode {project: $project, type: ph.type, name: ph.name})
|
||||
WHERE real.sourceFile <> ""
|
||||
WITH ph, count(real) AS reals
|
||||
SET ph.unresolved = (reals = 0)
|
||||
SET ph.unresolved = NOT EXISTS {
|
||||
MATCH (real:AstNode {project: $project, type: ph.type, name: ph.name})
|
||||
WHERE real.sourceFile <> ""
|
||||
}
|
||||
""";
|
||||
/**
|
||||
* Scoped variant of {@link #DELETE_DYNAMIC_CALLNAT_PLACEHOLDER_EDGES}: only markers on callers in
|
||||
@@ -2218,16 +2231,23 @@ public final class CypherQueries {
|
||||
public static final String DELETE_RESOLVED_BARE_PLACEHOLDER_CONTAINS = """
|
||||
MATCH (m:AstNode {project: $project, type: 'MODULE'})-[c:CONTAINS]->(phv:AstNode {project: $project, sourceFile: "", type: 'VARIABLE'})
|
||||
WHERE m.sourceFile <> "" AND NOT EXISTS { (m)-[:CONTAINS*0..1]->()-[:READS|WRITES]->(phv) }
|
||||
DELETE c
|
||||
// Batched for the same reason as DELETE_RESOLVED_PLACEHOLDER_FIELD_CONTAINS: same shape,
|
||||
// same growth with the project's placeholder count.
|
||||
CALL { WITH c DELETE c } IN TRANSACTIONS OF $batchSize ROWS
|
||||
""";
|
||||
/**
|
||||
* The {@code DATA_STRUCTURE}s a module references: external copybook areas pulled in via
|
||||
* {@code DEFINE DATA ... USING} ({@code INCLUDES} edges) and inline {@code DEFINE DATA} groups
|
||||
* ({@code CONTAINS} edges). Grouped to one row per (name, relationship); {@code fieldCount} is the
|
||||
* size of the referenced structure's own subtree, {@code area} is derived from the defining file's
|
||||
* extension. The precise {@code PARAMETER}/{@code LOCAL}/{@code GLOBAL} USING scope is not stored on
|
||||
* the edge, so {@code area} (PDA/LDA/GDA) is the available proxy; an unresolved copybook (its file
|
||||
* not ingested) reports {@code area = UNKNOWN}, {@code fieldCount = 0}, {@code sourceFile = null}.
|
||||
* extension — or, where the {@code INCLUDES} edge carries a {@code scope} (item 186: every NatJav
|
||||
* {@code @Using} does, 78,533 of 78,533 on korin; Natural edges do not), from that scope:
|
||||
* {@code PARAMETER}/{@code LOCAL}/{@code GLOBAL} → PDA/LDA/GDA. The edge, not the node, is the
|
||||
* right source: the same area is a PDA for one module and an LDA for another (3,112 of korin's
|
||||
* 5,818), and this endpoint is per module, where the value is single — one edge, one scope per
|
||||
* (module, area). The scope is applied only to a <em>resolved</em> definition, so an unresolved
|
||||
* copybook (its file not ingested) still reports {@code area = UNKNOWN}, {@code fieldCount = 0},
|
||||
* {@code sourceFile = null} — {@code UNKNOWN} keeps meaning exactly that and nothing else.
|
||||
*/
|
||||
public static final String MODULE_DATA_STRUCTURES = """
|
||||
MATCH (m:MODULE {name: $name, project: $project})
|
||||
@@ -2235,8 +2255,8 @@ public final class CypherQueries {
|
||||
MATCH (m)-[r:INCLUDES|CONTAINS]->(d:AstNode {type: 'DATA_STRUCTURE'})
|
||||
OPTIONAL MATCH (d)-[:CONTAINS*1..]->(f:AstNode)
|
||||
WHERE f.type IN ['VARIABLE', 'CONSTANT', 'DATA_STRUCTURE']
|
||||
WITH d, type(r) AS rel, count(DISTINCT f) AS fc
|
||||
WITH d.name AS name, rel,
|
||||
WITH d, type(r) AS rel, r.scope AS scope, count(DISTINCT f) AS fc
|
||||
WITH d.name AS name, rel, scope,
|
||||
CASE WHEN d.sourceFile = '' THEN null ELSE d.sourceFile END AS sourceFile,
|
||||
max(fc) AS fieldCount
|
||||
// Item 102: group by (name, sourceFile), never by name alone. The previous
|
||||
@@ -2245,13 +2265,18 @@ public final class CypherQueries {
|
||||
// `W-WIF-A2, 10 fields, old/W-WIF-A7.pda` — a combination true of neither definition), and
|
||||
// `head(collect(...))` was order-dependent across ingests. A still-unresolved placeholder is
|
||||
// only reported when no resolved definition exists for that (name, relationship).
|
||||
WITH name, rel, collect({sourceFile: sourceFile, fieldCount: fieldCount}) AS defs
|
||||
WITH name, rel, collect({sourceFile: sourceFile, fieldCount: fieldCount, scope: scope}) AS defs
|
||||
WITH name, rel, defs, [x IN defs WHERE x.sourceFile IS NOT NULL] AS resolved
|
||||
UNWIND (CASE WHEN size(resolved) > 0 THEN resolved ELSE defs END) AS def
|
||||
WITH name, rel, def.sourceFile AS sourceFile, def.fieldCount AS fieldCount
|
||||
WITH name, rel, def.sourceFile AS sourceFile, def.fieldCount AS fieldCount, def.scope AS scope
|
||||
RETURN name,
|
||||
CASE rel WHEN 'INCLUDES' THEN 'USING' ELSE 'INLINE' END AS relationship,
|
||||
CASE
|
||||
// Item 186: the edge's scope first, and only on a resolved definition — an
|
||||
// unresolved area keeps UNKNOWN, which the API guide defines as "not resolved".
|
||||
WHEN sourceFile IS NOT NULL AND scope = 'PARAMETER' THEN 'PDA'
|
||||
WHEN sourceFile IS NOT NULL AND scope = 'LOCAL' THEN 'LDA'
|
||||
WHEN sourceFile IS NOT NULL AND scope = 'GLOBAL' THEN 'GDA'
|
||||
WHEN sourceFile ENDS WITH '.pda' THEN 'PDA'
|
||||
WHEN sourceFile ENDS WITH '.lda' THEN 'LDA'
|
||||
WHEN sourceFile ENDS WITH '.gda' THEN 'GDA'
|
||||
@@ -2651,6 +2676,13 @@ public final class CypherQueries {
|
||||
"CREATE INDEX ast_node_copysite IF NOT EXISTS FOR (n:AstNode) ON (n.project, n.sourceFile, n.type, n.name, n.copySite)",
|
||||
"CREATE INDEX ast_node_project_sourcefile_paramposition IF NOT EXISTS FOR (n:AstNode) ON (n.project, n.sourceFile, n.paramPosition)",
|
||||
"CREATE INDEX ast_node_project_sourcefile IF NOT EXISTS FOR (n:AstNode) ON (n.project, n.sourceFile)",
|
||||
// NatJav placeholder re-keying joins a USING/CALLNAT placeholder to the real module on
|
||||
// className (RESOLVE_NATJAV_PLACEHOLDER_NAMES) -- the one join in the pipeline that is not
|
||||
// on name or sourceFile. Without this index it is a nested loop: measured on `korin`,
|
||||
// 22,769 placeholders against 45,577 className-bearing nodes took 24 minutes, longer than
|
||||
// the entire rest of the refresh. Narrow like item 164's: className is written only by the
|
||||
// NatJav parser, so the index stays empty for Natural and Java projects.
|
||||
"CREATE INDEX ast_node_project_classname IF NOT EXISTS FOR (n:AstNode) ON (n.project, n.className)",
|
||||
// Item 82: manual dynamic-CALLNAT overrides are keyed by (project, originFile, lineNo);
|
||||
// the apply/list/reset queries seek by project. Not on the :AstNode label on purpose, so
|
||||
// DELETE_PROJECT_NODES (which matches :AstNode only) leaves overrides intact across a refresh.
|
||||
@@ -2802,7 +2834,11 @@ public final class CypherQueries {
|
||||
MATCH (ph)-[c:CONTAINS]->(phv:AstNode {project: $project, sourceFile: ""})
|
||||
WHERE phv.type IN ['VARIABLE', 'CONSTANT']
|
||||
AND NOT (phv)-[:READS|WRITES]-()
|
||||
DELETE c
|
||||
// Batched (item: NatJav/K3). By far the largest single write in the pipeline, because it
|
||||
// scales with placeholder fields TIMES the areas that declare them: measured 4,438,644
|
||||
// edges on `korin`, against the 674,708 the heaviest redirect step deletes. One
|
||||
// transaction for that exceeds the limit whatever the heap is set to.
|
||||
CALL { WITH c DELETE c } IN TRANSACTIONS OF $batchSize ROWS
|
||||
""";
|
||||
/**
|
||||
* Item 130: the project's REST surface — one row per handler method, with the endpoint path
|
||||
@@ -3717,10 +3753,17 @@ public final class CypherQueries {
|
||||
WHERE size(matches) = 1
|
||||
WITH m, ph, matches[0] AS realv
|
||||
MATCH (m)-[:CONTAINS*0..1]->(src:AstNode)-[r:%s]->(ph)
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
DELETE r
|
||||
// Batched (item: NatJav/K3), like the two placeholder-field passes before it - same
|
||||
// shape, same growth with the project's reference count. No aggregation needed here:
|
||||
// `size(matches) = 1` above already pins exactly one target per (m, ph), and a
|
||||
// placeholder has one owning module, so every `r` appears in exactly one row.
|
||||
CALL {
|
||||
WITH src, r, realv
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
DELETE r
|
||||
} IN TRANSACTIONS OF $batchSize ROWS
|
||||
""".formatted(INCLUDE_FIELD_DEPTH, INCLUDE_FIELD_DEPTH, INCLUDE_FIELD_DEPTH, type.name(), type.name(),
|
||||
edgeKey(type, "coalesce(r.lineNo, -1)", "r.originFile", "src")));
|
||||
}
|
||||
@@ -3785,6 +3828,77 @@ public final class CypherQueries {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* NatJav placeholder keying (roadmap "Language support — NatJav", item B): rewrites a NatJav
|
||||
* {@code USING} placeholder's {@code name} from the <b>Java class</b> it was parsed from to the
|
||||
* <b>Natural</b> name the real data area carries, so that the ordinary
|
||||
* {@link #resolvePlaceholderTargets} machinery — which joins on {@code name} — can redirect it
|
||||
* like any other placeholder.
|
||||
*
|
||||
* <p>It exists because the two names cannot be bridged by a string rule. A NatJav
|
||||
* {@code @Using KORIN...WSAIVP_DA} names the class; the node is {@code WSAIVP}. Stripping the
|
||||
* {@code _DA} suffix yields the right Natural name for only 90.46 % of the 5,818 data areas in
|
||||
* the reference corpus — {@code V_MIT03P_DA} is {@code V-MIT03P}, and which of its remaining
|
||||
* underscores become hyphens is not derivable (Natural names may contain both). Joining on the
|
||||
* {@code className} both sides recorded at parse time is exact.
|
||||
*
|
||||
* <p>Rewrites only when <b>exactly one</b> real node is identified. Two files in the corpus share
|
||||
* a class name ({@code ACOMMON}, {@code COMMON_DA}, both duplicated across libraries); binding
|
||||
* those arbitrarily would invent a dependency. Item 184: where the class name alone is ambiguous,
|
||||
* the library the {@code @Using} named ({@code usingPackage}, see {@code NatJavUsings}) picks the
|
||||
* candidate whose {@code package} matches; what is still ambiguous after that stays unresolved
|
||||
* and surfaces as such.
|
||||
*
|
||||
* <p><b>Not idempotent across ingests by itself — item 183.</b> {@code name} is part of the node
|
||||
* merge key. After the rewrite the placeholder is stored under its Natural name, so the next
|
||||
* ingest's {@code MERGE} on the class name finds nothing and mints a second node, which this
|
||||
* step then renames too: measured on {@code korin} as exactly +34 placeholder
|
||||
* {@code DATA_STRUCTURE}s per deep refresh, 16 copies of each after 16 ingests, every copy wired
|
||||
* to the same current child fields. The earlier javadoc here claimed idempotency; it held for
|
||||
* the same node and not for the re-ingest that creates a new one.
|
||||
*
|
||||
* <p>So the rewrite now folds: if a placeholder of the same type already carries the target name
|
||||
* <em>and the same class name</em> — the previous ingest's copy of this very area — it is deleted
|
||||
* and the fresh one takes the name. Deleting the old rather than the new is safe because a
|
||||
* renamed placeholder has no incoming {@code INCLUDES} left (they were redirected to the real
|
||||
* area in the same run that renamed it; measured 0 on all 34) and its outgoing {@code CONTAINS}
|
||||
* to placeholder fields are re-created by every parse (which is how one field came to have 16
|
||||
* parents). The class-name guard keeps a homonym out of reach: two areas sharing a Natural name
|
||||
* have different class names, so neither can ever delete the other. The {@code collect}/{@code
|
||||
* UNWIND} is a deliberate barrier — every {@code stale} is read before any is deleted, so no row
|
||||
* can touch a node a previous row removed.
|
||||
*/
|
||||
public static final String RESOLVE_NATJAV_PLACEHOLDER_NAMES = """
|
||||
MATCH (ph:AstNode {project: $project, sourceFile: "", language: "natjav"})
|
||||
WHERE ph.className IS NOT NULL
|
||||
MATCH (real:AstNode {project: $project, type: ph.type, className: ph.className,
|
||||
language: "natjav"})
|
||||
WHERE real.sourceFile <> ""
|
||||
WITH ph, collect(DISTINCT real) AS candidates
|
||||
// Item 184: a class name two libraries share (COMMON_DA in KORIN and ABFRAGE) is told
|
||||
// apart by the library the @Using named - or, unqualified, the including file's own
|
||||
// package - which the parser recorded as usingPackage and the real area carries as
|
||||
// package. Applied only where the class name alone is ambiguous, so the unique case is
|
||||
// exactly what it was. A placeholder without a recorded package (one module including
|
||||
// the same class name from two libraries) stays unresolved rather than being bound to
|
||||
// either.
|
||||
WITH ph, CASE WHEN size(candidates) = 1 THEN candidates
|
||||
ELSE [c IN candidates WHERE ph.usingPackage IS NOT NULL
|
||||
AND c.package = ph.usingPackage]
|
||||
END AS candidates
|
||||
WHERE size(candidates) = 1
|
||||
WITH ph, head(candidates).name AS naturalName
|
||||
WHERE ph.name <> naturalName
|
||||
OPTIONAL MATCH (stale:AstNode {project: $project, sourceFile: "", type: ph.type,
|
||||
name: naturalName, className: ph.className})
|
||||
WHERE stale <> ph
|
||||
WITH collect({ph: ph, naturalName: naturalName, stale: stale}) AS work
|
||||
UNWIND work AS w
|
||||
WITH w.ph AS ph, w.naturalName AS naturalName, w.stale AS stale
|
||||
DETACH DELETE stale
|
||||
SET ph.name = naturalName
|
||||
""";
|
||||
|
||||
private static Map<EdgeType, String> buildResolvePlaceholderTargetQueries() {
|
||||
Map<EdgeType, String> queries = new EnumMap<>(EdgeType.class);
|
||||
for (EdgeType type : RESOLVABLE_EDGE_TYPES) {
|
||||
@@ -3814,17 +3928,39 @@ public final class CypherQueries {
|
||||
OR toLower(c.sourceFile) ENDS WITH '.gda')
|
||||
AND toUpper(head(split(last(split(c.sourceFile, '/')), '.'))) = toUpper(c.name)
|
||||
] AS memberRoots
|
||||
UNWIND (CASE WHEN size(memberRoots) > 0 THEN memberRoots ELSE cands END) AS real
|
||||
WITH ph, (CASE WHEN size(memberRoots) > 0 THEN memberRoots ELSE cands END) AS reals
|
||||
MATCH (src:AstNode)-[r:%s]->(ph)
|
||||
// coalesce only guards the MERGE key: a stale edge persisted before edge lineNo
|
||||
// existed (commit fe6d24f) has no lineNo, and MERGE rejects a null key property.
|
||||
// The trailing SET restores the true value (null stays null â Neo4j removes the
|
||||
// property â so downstream `WHERE lineNo IS NOT NULL` collectors keep filtering it,
|
||||
// and no -1 sentinel ever leaks into caller/callee output).
|
||||
MERGE (src)-[r2:%s {%s}]->(real)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
DELETE r
|
||||
// Batched (item: NatJav/K3): this redirects one edge per row, and the row count is
|
||||
// the project's, not the query's. `korin` drives 6,424 rows here; `k3` — 27,687
|
||||
// Java files, each call to an unresolved class minting a placeholder — drives
|
||||
// 821,312 and exceeded the transaction memory limit at 1.4 GiB and again at
|
||||
// 2.8 GiB after the heap was doubled. Batching bounds it by rows instead of by
|
||||
// heap, the same way DELETE_PROJECT_NODES does. Requires an auto-commit
|
||||
// transaction, which is what EnrichmentStep.batched arranges.
|
||||
// One row per edge, never per (edge, candidate): the fan-out over `reals` happens
|
||||
// INSIDE the batch. Unwinding it outside puts the same `r` in several rows, and once
|
||||
// the batches are separate transactions the row that arrives after `r`'s DELETE has
|
||||
// committed reads a relationship that no longer exists — `Relationship with id N has
|
||||
// been deleted in this transaction`, which is how this failed on `korin` at step 3 of
|
||||
// 52 (INCLUDES, where a `USING` with several member roots is the common case). The
|
||||
// single transaction it ran in before hid the duplicate: Cypher's eager evaluation
|
||||
// did every match before any delete.
|
||||
CALL {
|
||||
WITH src, r, reals
|
||||
UNWIND reals AS real
|
||||
// coalesce only guards the MERGE key: a stale edge persisted before edge lineNo
|
||||
// existed (commit fe6d24f) has no lineNo, and MERGE rejects a null key property.
|
||||
// The trailing SET restores the true value (null stays null - Neo4j removes the
|
||||
// property - so downstream `WHERE lineNo IS NOT NULL` collectors keep filtering
|
||||
// it, and no -1 sentinel ever leaks into caller/callee output).
|
||||
MERGE (src)-[r2:%s {%s}]->(real)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
// Collapses the fan-out back to the one edge, so it is deleted once and only after
|
||||
// every candidate has been merged.
|
||||
WITH DISTINCT r
|
||||
DELETE r
|
||||
} IN TRANSACTIONS OF $batchSize ROWS
|
||||
""".formatted(type.name(), type.name(),
|
||||
edgeKey(type, "coalesce(r.lineNo, -1)", "r.originFile", "src")));
|
||||
}
|
||||
@@ -3924,10 +4060,30 @@ public final class CypherQueries {
|
||||
// and builds an m x src cartesian; as a predicate it stays a bounded check on the
|
||||
// module that already owns `src`.
|
||||
WHERE m.sourceFile <> "" AND EXISTS { (m)-[:INCLUDES]->(real) }
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
DELETE r
|
||||
// Batched (item: NatJav/K3). This is the heaviest write in the pipeline: on `korin`
|
||||
// the READS pass alone set 2,335,704 properties across 434,372 created edges, and
|
||||
// the WRITES pass — roughly twice the edges — exceeded the transaction limit at
|
||||
// 2.8 GiB. The work scales with the project's field references, not with the query,
|
||||
// so a bigger heap only moves the ceiling; batching bounds it by rows.
|
||||
//
|
||||
// The aggregation is what makes batching safe, and it is not optional: a placeholder
|
||||
// name can match several real data areas (mean 1.45, up to 4 on upms), which put the
|
||||
// SAME `r` in several rows. In one transaction Cypher's eager evaluation hid that —
|
||||
// every match ran before any delete. Across batch boundaries the second row reads an
|
||||
// `r` whose DELETE has already committed, which fails the whole step with
|
||||
// `Relationship with id N has been deleted in this transaction`.
|
||||
WITH src, r, collect(DISTINCT realv) AS realvs
|
||||
CALL {
|
||||
WITH src, r, realvs
|
||||
UNWIND realvs AS realv
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
// Collapses the fan-out back to the one edge: deleted once, and only after every
|
||||
// target has been merged.
|
||||
WITH DISTINCT r
|
||||
DELETE r
|
||||
} IN TRANSACTIONS OF $batchSize ROWS
|
||||
""".formatted(type.name(), type.name(),
|
||||
edgeKey(type, "coalesce(r.lineNo, -1)", "r.originFile", "src")));
|
||||
}
|
||||
@@ -3948,10 +4104,20 @@ public final class CypherQueries {
|
||||
MATCH (real)-[:CONTAINS*1..10]->(realv:AstNode {project: $project, name: phv.name})
|
||||
WHERE realv.sourceFile <> "" AND realv.type IN ['VARIABLE', 'CONSTANT']
|
||||
MATCH (src:AstNode)-[r:%s]->(phv)
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
DELETE r
|
||||
// Batched, and aggregated for the same reason as the INCLUDES-scoped pass above:
|
||||
// several real data areas can share a placeholder name, which duplicates `r` across
|
||||
// rows and, once the rows are in separate transactions, makes the later row read a
|
||||
// relationship whose DELETE has already committed.
|
||||
WITH src, r, collect(DISTINCT realv) AS realvs
|
||||
CALL {
|
||||
WITH src, r, realvs
|
||||
UNWIND realvs AS realv
|
||||
MERGE (src)-[r2:%s {%s}]->(realv)
|
||||
SET r2 += properties(r)
|
||||
SET r2.lineNo = r.lineNo
|
||||
WITH DISTINCT r
|
||||
DELETE r
|
||||
} IN TRANSACTIONS OF $batchSize ROWS
|
||||
""".formatted(type.name(), type.name(),
|
||||
edgeKey(type, "coalesce(r.lineNo, -1)", "r.originFile", "src")));
|
||||
}
|
||||
|
||||
@@ -123,8 +123,21 @@ public class GraphRepository {
|
||||
// EXTENDS/IMPLEMENTS. Project-wide in both modes (no scoped variant). Note: when field
|
||||
// resolution is skipped we deliberately do NOT delete placeholders, so a later (scoped) deep
|
||||
// ingest can still resolve the field placeholders left behind.
|
||||
// NatJav only: a `USING` placeholder was parsed from a Java class name, the real data area
|
||||
// carries its Natural name. Re-key the placeholder on the className both sides recorded before
|
||||
// the name-joining resolution below runs — no string rule can bridge the two (see the query).
|
||||
// A no-op for Natural and Java projects: it matches only language "natjav" nodes.
|
||||
statements.add(new EnrichmentStep("resolve-natjav-placeholder-names",
|
||||
CypherQueries.RESOLVE_NATJAV_PLACEHOLDER_NAMES));
|
||||
for (EdgeType type : CypherQueries.RESOLVABLE_EDGE_TYPES) {
|
||||
statements.add(new EnrichmentStep("resolve-placeholder " + type, CypherQueries.resolvePlaceholderTargets(type)));
|
||||
// Batched: this redirects every edge pointing at a placeholder, and that count follows the
|
||||
// project, not the query. `korin` has 6,424 CALLS edges into placeholders and fit in one
|
||||
// transaction; `k3` — a Java estate of 27,687 files, where every call to an unresolved
|
||||
// class mints one — has 821,312 and did not, failing the refresh at 1.4 GiB and again at
|
||||
// 2.8 GiB after the heap was doubled. Raising the heap only moves the ceiling; batching
|
||||
// removes it.
|
||||
statements.add(new EnrichmentStep("resolve-placeholder " + type,
|
||||
CypherQueries.resolvePlaceholderTargets(type), true));
|
||||
}
|
||||
// J7: resolve Panache-ness inherited through a project base class (needs EXTENDS pointing at
|
||||
// real nodes, i.e. after the placeholder loop above) by setting repositoryEntity on the
|
||||
@@ -185,27 +198,36 @@ public class GraphRepository {
|
||||
// project, dominated by the WRITES pass). Done globally only for a deep whole-root ingest;
|
||||
// otherwise deferred to a scoped per-program deep ingest (scoped=true).
|
||||
for (EdgeType type : CypherQueries.RESOLVABLE_FIELD_EDGE_TYPES) {
|
||||
// Batched in the whole-root form only. This is where a deep refresh does its heaviest
|
||||
// writing - on `korin` the READS pass set 2.3 M properties over 434 k created edges and
|
||||
// the WRITES pass, about twice that, blew the 2.8 GiB transaction limit. The scoped form
|
||||
// processes a handful of modules, where the row count is bounded by the caller's
|
||||
// $names and batching would only add commit overhead. It goes through the same
|
||||
// runEnrichment, so it COULD be batched if a scoped ingest ever grew large.
|
||||
statements.add(new EnrichmentStep("resolve-field-placeholder" + sfx + " " + type,
|
||||
scoped ? CypherQueries.resolvePlaceholderFieldTargetsScoped(type) : CypherQueries.resolvePlaceholderFieldTargets(type)));
|
||||
scoped ? CypherQueries.resolvePlaceholderFieldTargetsScoped(type) : CypherQueries.resolvePlaceholderFieldTargets(type),
|
||||
!scoped));
|
||||
}
|
||||
// Fallback by global name for the remainder whose module has no matching INCLUDES edge
|
||||
// (GLOBAL USING / copycode). Runs after the INCLUDES-scoped pass deleted what it
|
||||
// resolved, so it only sees the small leftover set.
|
||||
for (EdgeType type : CypherQueries.RESOLVABLE_FIELD_EDGE_TYPES) {
|
||||
statements.add(new EnrichmentStep("resolve-field-placeholder-by-name" + sfx + " " + type,
|
||||
scoped ? CypherQueries.resolvePlaceholderFieldTargetsByNameScoped(type) : CypherQueries.resolvePlaceholderFieldTargetsByName(type)));
|
||||
scoped ? CypherQueries.resolvePlaceholderFieldTargetsByNameScoped(type) : CypherQueries.resolvePlaceholderFieldTargetsByName(type),
|
||||
!scoped));
|
||||
}
|
||||
statements.add(new EnrichmentStep("delete-resolved-field-contains", CypherQueries.DELETE_RESOLVED_PLACEHOLDER_FIELD_CONTAINS));
|
||||
statements.add(new EnrichmentStep("delete-resolved-field-contains", CypherQueries.DELETE_RESOLVED_PLACEHOLDER_FIELD_CONTAINS, true));
|
||||
// Item 18: redirect bare (unqualified) references to an included field onto the matching
|
||||
// real field, when uniquely resolvable through the module's includes.
|
||||
for (EdgeType type : CypherQueries.RESOLVABLE_FIELD_EDGE_TYPES) {
|
||||
statements.add(new EnrichmentStep("resolve-bare-included" + sfx + " " + type,
|
||||
scoped ? CypherQueries.resolveBareIncludedFieldTargetsScoped(type) : CypherQueries.resolveBareIncludedFieldTargets(type)));
|
||||
scoped ? CypherQueries.resolveBareIncludedFieldTargetsScoped(type) : CypherQueries.resolveBareIncludedFieldTargets(type),
|
||||
!scoped));
|
||||
}
|
||||
statements.add(new EnrichmentStep("delete-bare-placeholder-contains", CypherQueries.DELETE_RESOLVED_BARE_PLACEHOLDER_CONTAINS));
|
||||
statements.add(new EnrichmentStep("delete-bare-placeholder-contains", CypherQueries.DELETE_RESOLVED_BARE_PLACEHOLDER_CONTAINS, true));
|
||||
// Only safe once field placeholders are resolved — otherwise it would reap placeholder
|
||||
// structures a later scoped deep ingest still needs to resolve their fields.
|
||||
statements.add(new EnrichmentStep("delete-resolved-placeholders", CypherQueries.DELETE_RESOLVED_PLACEHOLDERS));
|
||||
statements.add(new EnrichmentStep("delete-resolved-placeholders", CypherQueries.DELETE_RESOLVED_PLACEHOLDERS, true));
|
||||
}
|
||||
if (dataflow) {
|
||||
// Dataflow: map CALLNAT arguments to callee parameters by position. Expensive on a whole
|
||||
@@ -1845,7 +1867,8 @@ public class GraphRepository {
|
||||
* idempotent).
|
||||
*/
|
||||
public Uni<Void> finalizeProjectScoped(String project, List<String> moduleNames) {
|
||||
Map<String, Object> params = Map.of("project", project, "names", moduleNames);
|
||||
Map<String, Object> params = Map.of("project", project, "names", moduleNames,
|
||||
"batchSize", deleteBatchSize);
|
||||
return runEnrichment(project, enrichmentSteps(true, true, true), params, "scoped-deep");
|
||||
}
|
||||
|
||||
@@ -1864,7 +1887,8 @@ public class GraphRepository {
|
||||
*/
|
||||
public Uni<Void> finalizeProject(String project, EnrichmentLevel level, boolean profile) {
|
||||
return runEnrichment(project, enrichmentSteps(level.dataflow(), level.resolveFields(), false),
|
||||
Map.of("project", project), level.name().toLowerCase(java.util.Locale.ROOT), profile);
|
||||
Map.of("project", project, "batchSize", deleteBatchSize),
|
||||
level.name().toLowerCase(java.util.Locale.ROOT), profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1894,7 +1918,11 @@ public class GraphRepository {
|
||||
EnrichmentStep step = steps.get(i);
|
||||
long start = System.nanoTime();
|
||||
String cypher = profile ? "PROFILE " + step.cypher() : step.cypher();
|
||||
ResultSummary summary = session.executeWrite(tx -> tx.run(cypher, params).consume());
|
||||
// A batched step manages its own transactions; wrapping it in an explicit one
|
||||
// would make Neo4j refuse the CALL { … } IN TRANSACTIONS inside it.
|
||||
ResultSummary summary = step.batched()
|
||||
? session.run(cypher, params).consume()
|
||||
: session.executeWrite(tx -> tx.run(cypher, params).consume());
|
||||
SummaryCounters c = summary.counters();
|
||||
long ms = (System.nanoTime() - start) / 1_000_000;
|
||||
if (profile && ms >= PROFILE_LOG_THRESHOLD_MS && summary.hasProfile()) {
|
||||
@@ -2163,7 +2191,18 @@ public class GraphRepository {
|
||||
return params;
|
||||
}
|
||||
|
||||
private record EnrichmentStep(String label, String cypher) {
|
||||
/**
|
||||
* One enrichment statement.
|
||||
*
|
||||
* @param batched whether the query drives its own {@code CALL { … } IN TRANSACTIONS} and must
|
||||
* therefore run in an <b>auto-commit</b> transaction. Neo4j rejects that construct
|
||||
* inside an explicit one, which is what {@code session.executeWrite} opens.
|
||||
*/
|
||||
private record EnrichmentStep(String label, String cypher, boolean batched) {
|
||||
|
||||
EnrichmentStep(String label, String cypher) {
|
||||
this(label, cypher, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Uni<ProjectOpResult> createProject(String name, @Nullable String description,
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.agenticcode.parserjava;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.CommentBlocks;
|
||||
import com.agenticcode.parsercore.ast.model.CommentProperties;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.comments.BlockComment;
|
||||
import com.github.javaparser.ast.comments.Comment;
|
||||
import com.github.javaparser.ast.comments.JavadocComment;
|
||||
import com.github.javaparser.ast.comments.LineComment;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Comment blocks of a parsed Java compilation unit (roadmap item 141), shared by the Java and the
|
||||
* NatJav parsers — the grouping and the DOCUMENTS attachment are identical for both, because NatJav
|
||||
* <em>is</em> Java syntax.
|
||||
*
|
||||
* <p>What differs is the text: a transpiled file also carries the transpiler's own bookkeeping, which
|
||||
* is why {@code retainedText} lets a caller drop parts of a block before it becomes a node. The Java
|
||||
* parser passes the identity — a hand-written file has nothing to strip.
|
||||
*/
|
||||
public final class JavaComments {
|
||||
|
||||
private JavaComments() {
|
||||
}
|
||||
|
||||
/**
|
||||
* One {@link NodeType#COMMENT} node per contiguous comment block, each with a
|
||||
* {@link EdgeType#DOCUMENTS} edge to the declaration it documents.
|
||||
*
|
||||
* <p>Comments are free here: JavaParser already attaches them to the {@link CompilationUnit}
|
||||
* (the default {@code ParserConfiguration.setAttributeComments(true)}), so this collects what the
|
||||
* parse produced rather than re-lexing. Adjacent {@code //} lines are folded into one block —
|
||||
* a five-line {@code //} preamble is one comment, not five.
|
||||
*/
|
||||
public static void collect(CompilationUnit unit, String language, String sourceFile,
|
||||
List<AstNode> nodes, List<AstEdge> edges, UnaryOperator<String> retainedText) {
|
||||
List<Comment> comments = unit.getAllComments().stream()
|
||||
.filter(c -> c.getBegin().isPresent() && c.getEnd().isPresent())
|
||||
.sorted(Comparator.comparingInt(c -> c.getBegin().orElseThrow().line))
|
||||
.toList();
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Declaration nodes of THIS file only: placeholder modules for referenced classes carry
|
||||
// sourceFile "" and are not a target a comment in this file could ever document.
|
||||
List<AstNode> targets = nodes.stream()
|
||||
.filter(n -> n.sourceFile().equals(sourceFile))
|
||||
.filter(CommentBlocks::isDocumentable)
|
||||
.toList();
|
||||
if (targets.isEmpty()) {
|
||||
return; // nothing parsed from this file — a comment with no target is not emitted
|
||||
}
|
||||
|
||||
List<Comment> block = new ArrayList<>();
|
||||
for (Comment comment : comments) {
|
||||
boolean extendsBlock = !block.isEmpty()
|
||||
&& comment instanceof LineComment
|
||||
&& block.get(block.size() - 1) instanceof LineComment previous
|
||||
&& previous.getEnd().orElseThrow().line + 1 == comment.getBegin().orElseThrow().line;
|
||||
if (!extendsBlock && !block.isEmpty()) {
|
||||
emitCommentBlock(block, language, sourceFile, targets, nodes, edges, retainedText);
|
||||
block = new ArrayList<>();
|
||||
}
|
||||
block.add(comment);
|
||||
}
|
||||
emitCommentBlock(block, language, sourceFile, targets, nodes, edges, retainedText);
|
||||
}
|
||||
|
||||
private static void emitCommentBlock(List<Comment> block, String language, String sourceFile,
|
||||
List<AstNode> targets, List<AstNode> nodes, List<AstEdge> edges,
|
||||
UnaryOperator<String> retainedText) {
|
||||
if (block.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int startLine = block.get(0).getBegin().orElseThrow().line;
|
||||
int endLine = block.get(block.size() - 1).getEnd().orElseThrow().line;
|
||||
String text = retainedText.apply(
|
||||
block.stream().map(JavaComments::commentText).collect(Collectors.joining("\n")));
|
||||
@Nullable AstNode commentNode = CommentBlocks.commentNode(language, sourceFile, startLine, endLine,
|
||||
text, commentKind(block.get(0)));
|
||||
if (commentNode == null) {
|
||||
return; // an empty /**/ or a bare // documents nothing
|
||||
}
|
||||
nodes.add(commentNode);
|
||||
edges.add(CommentBlocks.documentsEdge(commentNode,
|
||||
CommentBlocks.documentedNode(targets, startLine, endLine)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment's text with its decoration removed. {@code getContent()} hands back the inner text
|
||||
* verbatim, which for a block/Javadoc comment still carries every line's indentation and leading
|
||||
* {@code *} — so the stored text read {@code "* ServiceEndpoint: …\n\t * UpmsObject: …"}. Natural
|
||||
* comments already lose their markers, and a reader (human or agent) wants the same thing here:
|
||||
* the sentence, not the comment syntax.
|
||||
*/
|
||||
private static String commentText(Comment comment) {
|
||||
if (comment instanceof LineComment) {
|
||||
return comment.getContent().strip();
|
||||
}
|
||||
return comment.getContent().lines()
|
||||
.map(line -> {
|
||||
String stripped = line.strip();
|
||||
return stripped.startsWith("*") ? stripped.substring(1).stripLeading() : stripped;
|
||||
})
|
||||
.collect(Collectors.joining("\n"))
|
||||
.strip();
|
||||
}
|
||||
|
||||
private static String commentKind(Comment comment) {
|
||||
if (comment instanceof JavadocComment) {
|
||||
return CommentProperties.KIND_JAVADOC;
|
||||
}
|
||||
return comment instanceof BlockComment ? CommentProperties.KIND_BLOCK : CommentProperties.KIND_LINE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -1539,100 +1540,11 @@ public final class JavaParser implements LanguageParser {
|
||||
|
||||
// Item 141: comments are file-level, so this runs once after the type loop — inside it, a file
|
||||
// with nested types would emit every comment once per type.
|
||||
addCommentNodes(unit, sourceFile, nodes, edges);
|
||||
JavaComments.collect(unit, "java", sourceFile, nodes, edges, UnaryOperator.identity());
|
||||
|
||||
return new ParseResult(nodes, edges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 141: one {@link NodeType#COMMENT} node per contiguous comment block, each with a
|
||||
* {@link EdgeType#DOCUMENTS} edge to the declaration it documents.
|
||||
*
|
||||
* <p>Comments are free here: JavaParser already attaches them to the {@link CompilationUnit}
|
||||
* (the default {@code ParserConfiguration.setAttributeComments(true)}), so this collects what the
|
||||
* parse produced rather than re-lexing. Adjacent {@code //} lines are folded into one block —
|
||||
* a five-line {@code //} preamble is one comment, not five.
|
||||
*/
|
||||
private static void addCommentNodes(CompilationUnit unit, String sourceFile,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
List<Comment> comments = unit.getAllComments().stream()
|
||||
.filter(c -> c.getBegin().isPresent() && c.getEnd().isPresent())
|
||||
.sorted(Comparator.comparingInt(c -> c.getBegin().orElseThrow().line))
|
||||
.toList();
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Declaration nodes of THIS file only: placeholder modules for referenced classes carry
|
||||
// sourceFile "" and are not a target a comment in this file could ever document.
|
||||
List<AstNode> targets = nodes.stream()
|
||||
.filter(n -> n.sourceFile().equals(sourceFile))
|
||||
.filter(CommentBlocks::isDocumentable)
|
||||
.toList();
|
||||
if (targets.isEmpty()) {
|
||||
return; // nothing parsed from this file — a comment with no target is not emitted
|
||||
}
|
||||
|
||||
List<Comment> block = new ArrayList<>();
|
||||
for (Comment comment : comments) {
|
||||
boolean extendsBlock = !block.isEmpty()
|
||||
&& comment instanceof LineComment
|
||||
&& block.get(block.size() - 1) instanceof LineComment previous
|
||||
&& previous.getEnd().orElseThrow().line + 1 == comment.getBegin().orElseThrow().line;
|
||||
if (!extendsBlock && !block.isEmpty()) {
|
||||
emitCommentBlock(block, sourceFile, targets, nodes, edges);
|
||||
block = new ArrayList<>();
|
||||
}
|
||||
block.add(comment);
|
||||
}
|
||||
emitCommentBlock(block, sourceFile, targets, nodes, edges);
|
||||
}
|
||||
|
||||
private static void emitCommentBlock(List<Comment> block, String sourceFile, List<AstNode> targets,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
if (block.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int startLine = block.get(0).getBegin().orElseThrow().line;
|
||||
int endLine = block.get(block.size() - 1).getEnd().orElseThrow().line;
|
||||
String text = block.stream().map(JavaParser::commentText).collect(Collectors.joining("\n"));
|
||||
@Nullable AstNode commentNode = CommentBlocks.commentNode("java", sourceFile, startLine, endLine,
|
||||
text, commentKind(block.get(0)));
|
||||
if (commentNode == null) {
|
||||
return; // an empty /**/ or a bare // documents nothing
|
||||
}
|
||||
nodes.add(commentNode);
|
||||
edges.add(CommentBlocks.documentsEdge(commentNode,
|
||||
CommentBlocks.documentedNode(targets, startLine, endLine)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment's text with its decoration removed. {@code getContent()} hands back the inner text
|
||||
* verbatim, which for a block/Javadoc comment still carries every line's indentation and leading
|
||||
* {@code *} — so the stored text read {@code "* ServiceEndpoint: …\n\t * UpmsObject: …"}. Natural
|
||||
* comments already lose their markers, and a reader (human or agent) wants the same thing here:
|
||||
* the sentence, not the comment syntax.
|
||||
*/
|
||||
private static String commentText(Comment comment) {
|
||||
if (comment instanceof LineComment) {
|
||||
return comment.getContent().strip();
|
||||
}
|
||||
return comment.getContent().lines()
|
||||
.map(line -> {
|
||||
String stripped = line.strip();
|
||||
return stripped.startsWith("*") ? stripped.substring(1).stripLeading() : stripped;
|
||||
})
|
||||
.collect(Collectors.joining("\n"))
|
||||
.strip();
|
||||
}
|
||||
|
||||
private static String commentKind(Comment comment) {
|
||||
if (comment instanceof JavadocComment) {
|
||||
return CommentProperties.KIND_JAVADOC;
|
||||
}
|
||||
return comment instanceof BlockComment ? CommentProperties.KIND_BLOCK : CommentProperties.KIND_LINE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The handful of facts that differ between the four kinds of Java type declaration, resolved once
|
||||
* so the ingest loop below can treat them uniformly.
|
||||
|
||||
63
ac-parser-natjav/pom.xml
Normal file
63
ac-parser-natjav/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>agenticcode</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ac-parser-natjav</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ac-parser-natjav</name>
|
||||
<description>Parser for NatJav — Software AG Natural transpiled to Java (innoWake maxenso)</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-parser-core</artifactId>
|
||||
</dependency>
|
||||
<!-- NatJav is syntactically Java, so the syntax layer is JavaParser; only the semantic
|
||||
mapping (Natural module kinds, names, data areas) lives in this module. -->
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-parser-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.javaparser</groupId>
|
||||
<artifactId>javaparser-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-mvn-plugins</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.CallKind;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* The call graph of a NatJav module: its internal subroutines and every {@code perform} /
|
||||
* {@code callnat} / {@code fetch} / {@code fetchReturn} they make.
|
||||
*
|
||||
* <p>Resolution needs no name derivation, because both sides of a call use the same Java identifier:
|
||||
* {@code perform("FX_LEEREN")} names the method {@code void FX_LEEREN()}, and
|
||||
* {@code callnat("V_MIT04")} names the class {@code V_MIT04}. Measured over the corpus, 93.8 % of the
|
||||
* 129,590 {@code perform}s hit a method in their own file — exact and file-local. The remaining
|
||||
* 6.2 % are Natural <em>external</em> subroutines, which the transpiler made classes of their own
|
||||
* ({@code NaturalSubroutine}); 98.4 % of them exist as such a class, so NatJav resolves a call the
|
||||
* Natural parser explicitly cannot (see the roadmap's item-71 retraction).
|
||||
*
|
||||
* <p>A subroutine is a parameterless method that does <b>not</b> override a runtime hook.
|
||||
* {@code run}, {@code atTopOfPage}, {@code atWriteTitle}, {@code onError}, {@code atEndOfPage} and
|
||||
* {@code doBreak} are the transpiled forms of Natural's inline event blocks ({@code AT TOP OF PAGE},
|
||||
* {@code ON ERROR}, …), which the Natural parser attributes to the module — only
|
||||
* {@code DEFINE SUBROUTINE} makes a {@code FUNCTION} there. Treating them as subroutines would invent
|
||||
* ~5,300 of them and add a hop to every call inside one.
|
||||
*/
|
||||
final class NatJavCalls {
|
||||
|
||||
/**
|
||||
* The Natural statements the transpiler emits as bare API calls, mapped to the call kind the
|
||||
* unified AST records.
|
||||
*/
|
||||
private static final Map<String, CallKind> CALL_STATEMENTS = Map.of(
|
||||
"perform", CallKind.PERFORM,
|
||||
"callnat", CallKind.CALLNAT,
|
||||
"fetch", CallKind.CALLNAT,
|
||||
"fetchReturn", CallKind.CALLNAT);
|
||||
|
||||
private NatJavCalls() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the module's {@code FUNCTION} nodes, its {@code CALLS} edges and the placeholders those
|
||||
* edges point at.
|
||||
*
|
||||
* @return the subroutines by name, so a later pass can attribute its statements to the same
|
||||
* owner this one used
|
||||
*
|
||||
* @param type the module's primary type
|
||||
* @param module the file's shell node
|
||||
* @param sourceFile the file's project-relative path
|
||||
* @param copycode the file's expansion markers, used to attribute a call to the copycode it
|
||||
* came from
|
||||
*/
|
||||
static Map<String, AstNode> collect(TypeDeclaration<?> type, AstNode module, String sourceFile,
|
||||
NatJavCopycode copycode, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
List<MethodDeclaration> methods = type.getMethods();
|
||||
|
||||
// Subroutines first: a `perform` may call one declared further down the file.
|
||||
Map<String, AstNode> subroutines = new LinkedHashMap<>();
|
||||
for (MethodDeclaration method : methods) {
|
||||
if (!isSubroutine(method)) {
|
||||
continue;
|
||||
}
|
||||
AstNode function = new AstNode(UUID.randomUUID(), NodeType.FUNCTION, method.getNameAsString(),
|
||||
sourceFile, NatJavParser.LANGUAGE, lineOf(method), endLineOf(method), null, null,
|
||||
copycode.attribute(Map.of(), lineOf(method)));
|
||||
nodes.add(function);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, module.id(), function.id(),
|
||||
function.startLine(), null));
|
||||
subroutines.put(function.name(), function);
|
||||
}
|
||||
|
||||
Map<String, AstNode> placeholders = new HashMap<>();
|
||||
for (MethodDeclaration method : methods) {
|
||||
// Calls in an event block or in run() belong to the module, exactly as in Natural.
|
||||
AstNode caller = subroutines.getOrDefault(method.getNameAsString(), module);
|
||||
for (MethodCallExpr call : method.findAll(MethodCallExpr.class)) {
|
||||
CallKind kind = CALL_STATEMENTS.get(call.getNameAsString());
|
||||
// A bare call is the transpiled Natural statement; `x.perform(...)` would be something
|
||||
// else entirely (a runtime object's own method).
|
||||
if (kind == null || call.getScope().isPresent() || call.getArguments().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
emitCall(call, kind, caller, subroutines, placeholders, copycode, nodes, edges);
|
||||
}
|
||||
}
|
||||
return subroutines;
|
||||
}
|
||||
|
||||
private static void emitCall(MethodCallExpr call, CallKind kind, AstNode caller,
|
||||
Map<String, AstNode> subroutines, Map<String, AstNode> placeholders,
|
||||
NatJavCopycode copycode, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
int lineNo = lineOf(call);
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
Expression target = call.getArgument(0);
|
||||
|
||||
AstNode targetNode;
|
||||
if (target instanceof StringLiteralExpr literal) {
|
||||
String name = literal.asString().trim();
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AstNode local = kind == CallKind.PERFORM ? subroutines.get(name) : null;
|
||||
// A local subroutine wins over a same-named module: Natural resolves PERFORM against the
|
||||
// module's own subroutines first, and 93.8 % of the corpus's targets are exactly that.
|
||||
targetNode = local != null ? local : placeholder(placeholders, nodes, name, lineNo, true);
|
||||
properties.put("callKind", kind.name());
|
||||
} else {
|
||||
// Dynamic dispatch: the target name lives in a variable. Recorded as a placeholder named
|
||||
// after it plus the dynamicVar marker, the same shape the Natural parser uses so the
|
||||
// constant-propagation enrichment can recover literal targets later.
|
||||
String variable = target.toString().trim();
|
||||
targetNode = placeholder(placeholders, nodes, variable, lineNo, false);
|
||||
properties.put("callKind", CallKind.CALLNAT_DYNAMIC.name());
|
||||
properties.put("dynamicVar", variable);
|
||||
}
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CALLS, caller.id(), targetNode.id(), lineNo,
|
||||
null, copycode.attribute(properties, lineNo)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the deduped unresolved-module placeholder for {@code name}, carrying the class name the
|
||||
* call site used so the NatJav placeholder resolution can key on it (the call names the Java
|
||||
* class, the module node its Natural name)
|
||||
*/
|
||||
private static AstNode placeholder(Map<String, AstNode> placeholders, List<AstNode> nodes,
|
||||
String name, int lineNo, boolean literalTarget) {
|
||||
return placeholders.computeIfAbsent(name, n -> {
|
||||
// className only where the name IS a class name. For a dynamic call it is the dispatch
|
||||
// variable's text, and the NatJav placeholder re-keying matches any placeholder carrying
|
||||
// one — so a variable that happens to share a module's identifier would have been wired
|
||||
// to that module as a real call it never makes.
|
||||
Map<String, String> properties = literalTarget
|
||||
? Map.of(NatJavUsings.CLASS_NAME, n) : Map.of();
|
||||
AstNode node = new AstNode(UUID.randomUUID(), NodeType.MODULE, n, "", NatJavParser.LANGUAGE,
|
||||
lineNo, lineNo, null, null, properties);
|
||||
nodes.add(node);
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether {@code method} is a Natural internal subroutine rather than an event block or
|
||||
* the module body
|
||||
*/
|
||||
private static boolean isSubroutine(MethodDeclaration method) {
|
||||
return method.getParameters().isEmpty()
|
||||
&& !method.isAnnotationPresent("Override")
|
||||
&& !RUNTIME_HOOKS.contains(method.getNameAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The runtime hooks, checked by name as well as by {@code @Override}: a generated file may omit
|
||||
* the annotation, and mistaking {@code run} for a subroutine would attach the whole module body
|
||||
* to a function Natural does not have.
|
||||
*/
|
||||
private static final Set<String> RUNTIME_HOOKS = Set.of(
|
||||
"run", "onError", "atWriteTitle", "atTopOfPage", "atEndOfPage", "doBreak",
|
||||
"atStartOfData", "atEndOfData", "atBreak", "initialize");
|
||||
|
||||
private static int lineOf(com.github.javaparser.ast.Node node) {
|
||||
return node.getBegin().map(p -> p.line).orElse(1);
|
||||
}
|
||||
|
||||
private static int endLineOf(com.github.javaparser.ast.Node node) {
|
||||
return node.getEnd().map(p -> p.line).orElse(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.model.SourceHash;
|
||||
import com.agenticcode.parsercore.ast.spi.CoarseScanner;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Tier-1 coarse scan of a NatJav file (roadmap item 36): the file's outline only — its
|
||||
* {@code MODULE}/{@code DATA_STRUCTURE} shell, stamped with the content hash (item 41) and
|
||||
* LoC/SLoC (item 46) — with the deep detail left to {@link NatJavParser}.
|
||||
*
|
||||
* <p>While the parser emits nothing beyond the shell, this scan is the same result plus the
|
||||
* metrics; it filters rather than assumes, so it keeps producing the correct outline as the parser
|
||||
* grows a call graph and data structures in later phases.
|
||||
*/
|
||||
public final class NatJavCoarseScanner implements CoarseScanner {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
private final NatJavLineCounter lineCounter = new NatJavLineCounter();
|
||||
|
||||
/**
|
||||
* @return {@code shell} with {@code sourceHash} and {@code loc}/{@code sloc} added, preserving
|
||||
* every property the parser already set (naturalName identity, moduleKind, description, ...)
|
||||
*/
|
||||
private static AstNode withShellMetrics(AstNode shell, String hash, LocMetrics metrics) {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
if (shell.properties() != null) {
|
||||
properties.putAll(shell.properties());
|
||||
}
|
||||
properties.put(SourceHash.PROPERTY, hash);
|
||||
properties.put(LocMetrics.LOC, Integer.toString(metrics.loc()));
|
||||
properties.put(LocMetrics.SLOC, Integer.toString(metrics.sloc()));
|
||||
return new AstNode(shell.id(), shell.type(), shell.name(), shell.sourceFile(), shell.language(),
|
||||
shell.startLine(), shell.endLine(), shell.dataType(), shell.value(), properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String language() {
|
||||
return NatJavParser.LANGUAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseResult scan(String sourceFile, String content) {
|
||||
return scan(sourceFile, content, NatJavDdmResolver.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tier-1 scan with the project's DDM index; see {@link NatJavParser#parse(String, String,
|
||||
* NatJavDdmResolver)}.
|
||||
*/
|
||||
public ParseResult scan(String sourceFile, String content, NatJavDdmResolver ddms) {
|
||||
NatJavParser.Parsed parsed = parser.parseWithShell(sourceFile, content, ddms);
|
||||
String hash = SourceHash.of(content);
|
||||
LocMetrics metrics = lineCounter.count(content);
|
||||
|
||||
// Kept: the file's shell, its FUNCTION shells (subroutines — the call graph is Tier-1) and
|
||||
// the unresolved cross-file references (USING/CALLNAT placeholders, which carry an empty
|
||||
// sourceFile). The field tree is deep detail a Tier-2 parse fills in — and it cannot be
|
||||
// filtered by node type, because a data area's groups are DATA_STRUCTUREs as well, which is
|
||||
// why the shell is recognised by identity rather than by type.
|
||||
List<AstNode> coarseNodes = new ArrayList<>();
|
||||
Set<UUID> kept = new HashSet<>();
|
||||
for (AstNode node : parsed.result().nodes()) {
|
||||
boolean shell = node.id().equals(parsed.shell().id());
|
||||
// A placeholder is only outline material when it stands for another FILE — a USING or a
|
||||
// CALLNAT target. Dataflow now also mints field placeholders and DB_TABLEs with an empty
|
||||
// sourceFile, and keeping those made a Tier-1 scan persist the whole cross-file field
|
||||
// population (340,667 on `korin`) plus tables with no access to hang off.
|
||||
boolean crossFileReference = node.sourceFile().isEmpty()
|
||||
&& (node.type() == NodeType.MODULE || node.type() == NodeType.DATA_STRUCTURE);
|
||||
boolean outline = shell || node.type() == NodeType.FUNCTION || crossFileReference;
|
||||
if (!outline) {
|
||||
continue;
|
||||
}
|
||||
kept.add(node.id());
|
||||
coarseNodes.add(shell ? withShellMetrics(node, hash, metrics) : node);
|
||||
}
|
||||
return new ParseResult(coarseNodes, parsed.result().edges().stream()
|
||||
.filter(e -> kept.contains(e.sourceId()) && kept.contains(e.targetId()))
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parserjava.JavaComments;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Comment blocks of a NatJav file. The grouping and the {@code DOCUMENTS} attachment are the Java
|
||||
* parser's ({@link JavaComments}) — NatJav <em>is</em> Java syntax — and what this class adds is the
|
||||
* one thing that differs: a transpiled file carries the transpiler's own bookkeeping alongside the
|
||||
* comments a Natural developer wrote.
|
||||
*
|
||||
* <p>Measured over the corpus's 2,473,041 comment lines, 26 % are that bookkeeping, and every part of
|
||||
* it is <b>already in the graph as structured data</b>: the {@code @@INCLUDE} markers as a call's
|
||||
* {@code viaCopycode}, the header's {@code @FUNCTION}/{@code @OWNER} as the module's
|
||||
* {@code description}/{@code owner}. Storing it again as comment text would bury the real comments —
|
||||
* {@code search_by_value} with {@code includeComments} would answer a quarter transpiler markers.
|
||||
*
|
||||
* <p><b>The filtering is per line, not per block</b>, and that distinction is the whole point. The
|
||||
* migration header looks like pure framing but is not: of the 14,749 files carrying one, <b>10,466
|
||||
* (71 %)</b> have real prose inside it — often a fuller description of the program than the single
|
||||
* {@code @FUNCTION} line. Dropping the block would delete the documentation of 10,466 modules.
|
||||
*
|
||||
* <pre>
|
||||
* * *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01*** ← framing, dropped
|
||||
* * * @FUNCTION LOESCHEN UMKONTIERUNGEN ← already the module's description
|
||||
* * * @OWNER P36 ← already the module's owner
|
||||
* * *
|
||||
* * * Löschen Umkontierungen ← kept
|
||||
* </pre>
|
||||
*/
|
||||
final class NatJavComments {
|
||||
|
||||
/**
|
||||
* A block that is nothing but transpiler bookkeeping: the {@code @@USING}/{@code @@INCLUDE}
|
||||
* expansion markers (245,024 lines) and the generation banner (21,603). Unlike the migration
|
||||
* header these never carry prose, so they are dropped whole.
|
||||
*/
|
||||
private static final Pattern GENERATED_BLOCK = Pattern.compile(
|
||||
"^\\s*(@@(USING|INCLUDE):(BEGIN|END)\\(|Generated by innoWake"
|
||||
+ "|additional loop and database objects)");
|
||||
|
||||
/**
|
||||
* One line of the migration header's frame, or a field of it already extracted into a module
|
||||
* property. Everything else in the header is the developer's own text.
|
||||
*/
|
||||
private static final Pattern HEADER_FRAME = Pattern.compile(
|
||||
"^[\\s*]*$"
|
||||
+ "|LCM-HEADER\\s+(BEGIN|END)"
|
||||
+ "|\\$\\$LCMHEADER"
|
||||
// The corpus spells this both ways: 57,484 "migration" and 1,347 "migraion".
|
||||
// Matching the correct spelling only let 442 blocks of pure framing through.
|
||||
+ "|header created during migra[a-z]*ion process"
|
||||
+ "|@(FUNCTION|OWNER|MERGE-SOURCE|NO-MERGE)\\b"
|
||||
// The generator stamps per-line metadata wherever it likes, and the label is not
|
||||
// one word: besides `versionRep2Str:` and `originalObjectName:` there is
|
||||
// `Scanpunkte für manuelle Eingriffe:` (2,140 lines, also HTML-escaped as
|
||||
// `für`). Any line that opens with the generator's name and carries a label
|
||||
// is its stamp — a hand-written comment does not start that way.
|
||||
+ "|^[\\s*]*innoWake\\b[^:]*:"
|
||||
+ "|^\\s*</?pre>\\s*$");
|
||||
|
||||
private NatJavComments() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the file's comment nodes and their {@code DOCUMENTS} edges.
|
||||
*/
|
||||
static void collect(CompilationUnit unit, String sourceFile, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
JavaComments.collect(unit, NatJavParser.LANGUAGE, sourceFile, nodes, edges, NatJavComments::retained);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the part of a comment block worth keeping: {@code ""} for pure bookkeeping, otherwise
|
||||
* the block without its framing lines
|
||||
*/
|
||||
static String retained(String text) {
|
||||
if (GENERATED_BLOCK.matcher(text).find()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder kept = new StringBuilder();
|
||||
for (String line : text.split("\\R")) {
|
||||
if (HEADER_FRAME.matcher(line).find() || isDecorationOnly(line)) {
|
||||
continue;
|
||||
}
|
||||
if (kept.length() > 0) {
|
||||
kept.append('\n');
|
||||
}
|
||||
kept.append(withoutNaturalMarker(line));
|
||||
}
|
||||
return kept.toString().strip();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the line without Natural's own comment marker. The Java layer already stripped the
|
||||
* Javadoc {@code *}; underneath it sits the {@code *} that marked a full-line comment in the
|
||||
* Natural source, and a Natural ingest does not store that either — the sentence is wanted, not
|
||||
* the comment syntax.
|
||||
*/
|
||||
private static String withoutNaturalMarker(String line) {
|
||||
String core = line.strip();
|
||||
return core.startsWith("*") ? core.substring(1).strip() : core;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the line is only the comment box's own drawing — a row of asterisks, dashes or
|
||||
* equals signs, which the Natural sources use to frame a header and which carries no statement
|
||||
*/
|
||||
private static boolean isDecorationOnly(String line) {
|
||||
String core = line.strip();
|
||||
if (core.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < core.length(); i++) {
|
||||
char c = core.charAt(i);
|
||||
if (c != '*' && c != '-' && c != '=' && c != ' ' && c != '/') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.Node;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import com.github.javaparser.ast.stmt.DoStmt;
|
||||
import com.github.javaparser.ast.stmt.ForEachStmt;
|
||||
import com.github.javaparser.ast.stmt.ForStmt;
|
||||
import com.github.javaparser.ast.stmt.IfStmt;
|
||||
import com.github.javaparser.ast.stmt.Statement;
|
||||
import com.github.javaparser.ast.stmt.SwitchStmt;
|
||||
import com.github.javaparser.ast.stmt.WhileStmt;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Control-flow blocks as {@code CONTROL_FLOW} nodes, nested by containment — the same shape the
|
||||
* Natural parser builds for {@code IF}/{@code FOR}/{@code REPEAT}/{@code DECIDE}.
|
||||
*
|
||||
* <p>Two things this must <em>not</em> do, both measured over the reference corpus:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>A database loop is not control flow.</b> 20,749 of the corpus's 23,696 {@code while}
|
||||
* statements are {@code while (readUMK.hasNext())} — a transpiled {@code READ}/{@code FIND}
|
||||
* loop. The Natural parser models {@code IF}, {@code FOR}, {@code REPEAT} and {@code DECIDE},
|
||||
* never a {@code READ} loop, so taking these would invent 20,749 blocks the original has not.
|
||||
* Only 2,947 {@code while}s are Natural {@code REPEAT}s.</li>
|
||||
* <li><b>A {@code DECIDE} is one construct, not a chain of ifs.</b> The transpiler lowers Natural's
|
||||
* {@code DECIDE} into {@code if}/{@code else if} around a counter variable, and emitting each
|
||||
* arm separately would turn 9,340 dispatches into some 30,000 unrelated {@code IF}s and leave
|
||||
* the dispatch views empty. The counter is what makes the chain recognisable.</li>
|
||||
* </ul>
|
||||
*/
|
||||
final class NatJavControlFlow {
|
||||
|
||||
/**
|
||||
* The method a transpiled read loop tests. Its receiver is the loop object a {@code read}/
|
||||
* {@code find}/{@code get}/{@code histogram} assigned.
|
||||
*/
|
||||
private static final String LOOP_TEST = "hasNext";
|
||||
|
||||
/**
|
||||
* Local the transpiler declares before a lowered {@code DECIDE} and increments in every branch;
|
||||
* the trailing {@code if (decideCounterN == 0)} is the {@code NONE} arm.
|
||||
*/
|
||||
private static final String DECIDE_COUNTER = "decideCounter";
|
||||
|
||||
private NatJavControlFlow() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the module's control-flow blocks.
|
||||
*
|
||||
* @param subroutine method name → the {@code FUNCTION} owning its statements
|
||||
*/
|
||||
static void collect(TypeDeclaration<?> type, AstNode module, String sourceFile,
|
||||
Map<String, AstNode> subroutine, NatJavCopycode copycode,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
for (MethodDeclaration method : type.getMethods()) {
|
||||
AstNode owner = subroutine.getOrDefault(method.getNameAsString(), module);
|
||||
method.getBody().ifPresent(body -> walk(body, owner.id(), sourceFile, copycode, nodes, edges));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks {@code statement}'s children, emitting a node per block and recursing under it so the
|
||||
* nesting in the graph is the nesting in the source.
|
||||
*/
|
||||
private static void walk(Node statement, UUID parent, String sourceFile, NatJavCopycode copycode,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
for (Node child : statement.getChildNodes()) {
|
||||
visit(child, parent, sourceFile, copycode, nodes, edges);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes one node: emits it if it opens a block, then descends. Splitting this from
|
||||
* {@link #walk} is what makes an {@code else if} work — the else-branch of an {@code if} <b>is</b>
|
||||
* an {@code IfStmt}, and handing it to a children-walk skipped it silently while its own
|
||||
* else-branch surfaced one level too high. An {@code if}/{@code else if}/{@code else if} chain
|
||||
* lost every second arm.
|
||||
*/
|
||||
private static void visit(Node child, UUID parent, String sourceFile, NatJavCopycode copycode,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
Block block = blockOf(child);
|
||||
if (block == null) {
|
||||
walk(child, parent, sourceFile, copycode, nodes, edges);
|
||||
return;
|
||||
}
|
||||
AstNode node = emit(block, sourceFile, copycode, nodes, edges, parent);
|
||||
// The block's BODIES, never the statement itself: for a lowered DECIDE the bodies are the
|
||||
// arms' then-branches, so the chain's own `else if` is not met again and folded into a
|
||||
// second DECIDE. A body that is itself a block is emitted under this one.
|
||||
for (Node body : block.bodies()) {
|
||||
visit(body, node.id(), sourceFile, copycode, nodes, edges);
|
||||
}
|
||||
}
|
||||
|
||||
private static AstNode emit(Block block, String sourceFile, NatJavCopycode copycode,
|
||||
List<AstNode> nodes, List<AstEdge> edges, UUID parent) {
|
||||
int lineNo = block.node().getBegin().map(p -> p.line).orElse(1);
|
||||
int endLine = block.node().getEnd().map(p -> p.line).orElse(lineNo);
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
if (!block.branchValues().isEmpty()) {
|
||||
properties.put("decideValues", String.join(",", block.branchValues()));
|
||||
}
|
||||
AstNode node = new AstNode(UUID.randomUUID(), NodeType.CONTROL_FLOW, block.kind(), sourceFile,
|
||||
NatJavParser.LANGUAGE, lineNo, endLine, block.kind(), block.condition(),
|
||||
copycode.attribute(properties, lineNo));
|
||||
nodes.add(node);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, parent, node.id(), lineNo, null));
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the block {@code child} opens, or {@code null} when it is not a control-flow statement
|
||||
* (or is a database loop, which is not one)
|
||||
*/
|
||||
private static @Nullable Block blockOf(Node child) {
|
||||
if (child instanceof IfStmt ifStatement) {
|
||||
// Being a dispatch and having literal arms are two different questions. Conflating them
|
||||
// classified every DECIDE whose arms compare numbers — not strings — as a plain IF.
|
||||
if (!isLoweredDecide(ifStatement)) {
|
||||
return new Block("IF", ifStatement.getCondition().toString(), ifStatement,
|
||||
armBodies(ifStatement, false), List.of());
|
||||
}
|
||||
return new Block("DECIDE", ifStatement.getCondition().toString(), ifStatement,
|
||||
armBodies(ifStatement, true), decideBranchValues(ifStatement));
|
||||
}
|
||||
if (child instanceof ForStmt statement) {
|
||||
return new Block("FOR", conditionText(child), child, List.of(statement.getBody()), List.of());
|
||||
}
|
||||
if (child instanceof ForEachStmt statement) {
|
||||
return new Block("FOR", conditionText(child), child, List.of(statement.getBody()), List.of());
|
||||
}
|
||||
if (child instanceof WhileStmt whileStatement) {
|
||||
return isDatabaseLoop(whileStatement.getCondition())
|
||||
? null
|
||||
: new Block("REPEAT", whileStatement.getCondition().toString(), whileStatement,
|
||||
List.of(whileStatement.getBody()), List.of());
|
||||
}
|
||||
if (child instanceof DoStmt doStatement) {
|
||||
return new Block("REPEAT", doStatement.getCondition().toString(), doStatement,
|
||||
List.of(doStatement.getBody()), List.of());
|
||||
}
|
||||
if (child instanceof SwitchStmt switchStatement) {
|
||||
// The entries, not the statement: passing the statement itself would make `visit`
|
||||
// recognise it again and recurse without end.
|
||||
return new Block("DECIDE", switchStatement.getSelector().toString(), switchStatement,
|
||||
List.copyOf(switchStatement.getEntries()), List.of());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the subtrees to recurse into. For a chain folded into one {@code DECIDE} these are the
|
||||
* arms' bodies, so the chain's own {@code else if} statements are never met again; for a plain
|
||||
* {@code if} the then- and else-branches, which nest as Java nests them.
|
||||
*/
|
||||
private static List<Node> armBodies(IfStmt ifStatement, boolean chain) {
|
||||
List<Node> bodies = new ArrayList<>();
|
||||
Statement branch = ifStatement;
|
||||
while (branch instanceof IfStmt arm) {
|
||||
bodies.add(arm.getThenStmt());
|
||||
Statement next = arm.getElseStmt().orElse(null);
|
||||
if (!chain) {
|
||||
if (next != null) {
|
||||
bodies.add(next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
branch = next;
|
||||
}
|
||||
if (chain && branch != null) {
|
||||
bodies.add(branch);
|
||||
}
|
||||
return bodies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this {@code while} is a transpiled {@code READ}/{@code FIND} loop — its
|
||||
* condition asks a loop object for the next record
|
||||
*/
|
||||
private static boolean isDatabaseLoop(Expression condition) {
|
||||
return condition instanceof MethodCallExpr call
|
||||
&& call.getNameAsString().equals(LOOP_TEST)
|
||||
&& call.getScope().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this {@code if} is a lowered {@code DECIDE} — every arm of the chain increments
|
||||
* a {@code decideCounter} local. The counter's name is not always numbered
|
||||
* ({@code decideCounterGWFMCHKC} occurs too), so it is matched by prefix.
|
||||
*/
|
||||
private static boolean isLoweredDecide(IfStmt ifStatement) {
|
||||
Statement branch = ifStatement;
|
||||
while (branch instanceof IfStmt arm) {
|
||||
if (!incrementsDecideCounter(arm.getThenStmt())) {
|
||||
return false;
|
||||
}
|
||||
branch = arm.getElseStmt().orElse(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the literal values the chain's arms compare against — what the Natural parser records
|
||||
* as the dispatch's {@code VALUE} arms. Empty for a dispatch on numbers, which is a dispatch all
|
||||
* the same.
|
||||
*/
|
||||
private static List<String> decideBranchValues(IfStmt ifStatement) {
|
||||
List<String> values = new ArrayList<>();
|
||||
Statement branch = ifStatement;
|
||||
while (branch instanceof IfStmt arm) {
|
||||
values.addAll(literalsOf(arm.getCondition()));
|
||||
branch = arm.getElseStmt().orElse(null);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether {@code body} contains the {@code decideCounterN++} the transpiler puts in every
|
||||
* arm of a lowered {@code DECIDE}
|
||||
*/
|
||||
private static boolean incrementsDecideCounter(Statement body) {
|
||||
// A DIRECT statement of the arm, not anywhere in its subtree. The transpiler puts the
|
||||
// increment first in every arm; searching the subtree also found it through a nested `if`,
|
||||
// so one dispatch was recognised again at each nesting level — 13,193 detections against the
|
||||
// corpus's 9,340 counters, 1,917 of them a DECIDE inside its own DECIDE.
|
||||
List<Statement> statements = body instanceof com.github.javaparser.ast.stmt.BlockStmt block
|
||||
? block.getStatements()
|
||||
: List.of(body);
|
||||
return statements.stream()
|
||||
.filter(statement -> statement instanceof com.github.javaparser.ast.stmt.ExpressionStmt)
|
||||
.map(statement -> ((com.github.javaparser.ast.stmt.ExpressionStmt) statement).getExpression())
|
||||
.anyMatch(expression -> expression instanceof com.github.javaparser.ast.expr.UnaryExpr unary
|
||||
&& unary.getExpression().toString().startsWith(DECIDE_COUNTER));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the string literals a branch condition compares against — the {@code VALUE} arms of the
|
||||
* original {@code DECIDE}
|
||||
*/
|
||||
private static List<String> literalsOf(Expression condition) {
|
||||
return condition.findAll(StringLiteralExpr.class).stream()
|
||||
.map(StringLiteralExpr::asString)
|
||||
.map(String::trim)
|
||||
.filter(value -> !value.isEmpty())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static String conditionText(Node loop) {
|
||||
if (loop instanceof ForStmt statement) {
|
||||
return statement.getCompare().map(Object::toString).orElse("");
|
||||
}
|
||||
if (loop instanceof ForEachStmt statement) {
|
||||
return statement.getIterable().toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* One control-flow block.
|
||||
*
|
||||
* @param kind the Natural construct: {@code IF}, {@code FOR}, {@code REPEAT} or
|
||||
* {@code DECIDE}
|
||||
* @param condition the condition text, stored as the node's value like the Natural parser does
|
||||
* @param node the statement, for its line range
|
||||
* @param bodies the subtrees to recurse into
|
||||
* @param branchValues the arms of a dispatch, empty for anything else
|
||||
*/
|
||||
private record Block(String kind, String condition, Node node, List<Node> bodies,
|
||||
List<String> branchValues) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.CopycodeProperties;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The copycode expansions the transpiler left behind as markers:
|
||||
* {@code /* @@INCLUDE:BEGIN(YCISN) *}{@code /} … {@code /* @@INCLUDE:END(YCISN) *}{@code /}. Measured
|
||||
* over the reference corpus: 47,313 blocks in 6,124 files, nested at most 3 deep, with no unbalanced
|
||||
* block and no {@code BEGIN}/{@code END} name mismatch — so a plain stack is enough.
|
||||
*
|
||||
* <p><b>This is attribution, not the Natural copycode model.</b> On the Natural side a copycode is a
|
||||
* separate {@code .cpy} file whose nodes live in it and are <em>shared</em> by every including module
|
||||
* ({@code L4N-ENTER} is contained by 137 of them). The transpiler baked each expansion into its host
|
||||
* file, so there is no second file and no shared node: the same copycode subroutine exists 137 times,
|
||||
* once per host. What survives is where a statement came from, which is what
|
||||
* {@link CopycodeProperties#VIA_COPYCODE} and {@link CopycodeProperties#INCLUDED_AT} record — 23.3 %
|
||||
* of all calls in the corpus sit inside such a block, so it is the difference between attributing
|
||||
* 50,336 calls to the program that happens to hold them and to the copycode they belong to.
|
||||
*
|
||||
* <p>{@link CopycodeProperties#INCLUDE_PATH} is deliberately <b>not</b> written. It identifies one
|
||||
* expansion site because a Natural copycode node is shared between sites; here every node already
|
||||
* lives in the host file being parsed, takes the ordinary merge path, and has no sibling to be told
|
||||
* apart from.
|
||||
*/
|
||||
final class NatJavCopycode {
|
||||
|
||||
private static final Pattern BEGIN = Pattern.compile("@@INCLUDE:BEGIN\\(([^)]*)\\)");
|
||||
private static final Pattern END = Pattern.compile("@@INCLUDE:END\\(([^)]*)\\)");
|
||||
|
||||
private final List<Block> blocks;
|
||||
|
||||
private NatJavCopycode(List<Block> blocks) {
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans {@code content} for the expansion markers.
|
||||
*
|
||||
* <p>Read off the raw text rather than the parsed comments on purpose: these markers are the
|
||||
* transpiler's own bookkeeping, and a block opens and closes at a line, not at a syntax node —
|
||||
* an expansion routinely starts mid-statement-list and is not a well-formed Java subtree.
|
||||
*/
|
||||
static NatJavCopycode of(String content) {
|
||||
List<Block> blocks = new ArrayList<>();
|
||||
Deque<Block> open = new ArrayDeque<>();
|
||||
String[] lines = content.split("\\R", -1);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int lineNo = i + 1;
|
||||
Matcher begin = BEGIN.matcher(lines[i]);
|
||||
if (begin.find()) {
|
||||
open.push(new Block(begin.group(1), lineNo));
|
||||
}
|
||||
Matcher end = END.matcher(lines[i]);
|
||||
if (end.find() && !open.isEmpty()) {
|
||||
Block block = open.pop();
|
||||
block.endLine = lineNo;
|
||||
blocks.add(block);
|
||||
}
|
||||
}
|
||||
// An unterminated block (none in the corpus, but a truncated file could) still ends the file.
|
||||
while (!open.isEmpty()) {
|
||||
Block block = open.pop();
|
||||
block.endLine = lines.length;
|
||||
blocks.add(block);
|
||||
}
|
||||
return new NatJavCopycode(blocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code properties} plus the copycode origin of {@code lineNo}, or {@code properties}
|
||||
* unchanged when the line is the host module's own code
|
||||
*/
|
||||
Map<String, String> attribute(Map<String, String> properties, int lineNo) {
|
||||
Block innermost = innermost(lineNo);
|
||||
if (innermost == null) {
|
||||
return properties;
|
||||
}
|
||||
Map<String, String> merged = new LinkedHashMap<>(properties);
|
||||
merged.put(CopycodeProperties.VIA_COPYCODE, innermost.member);
|
||||
merged.put(CopycodeProperties.INCLUDED_AT, Integer.toString(innermost.beginLine));
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innermost block containing {@code lineNo} — innermost, because a nested expansion
|
||||
* is the one the statement actually came from
|
||||
*/
|
||||
private @Nullable Block innermost(int lineNo) {
|
||||
Block innermost = null;
|
||||
for (Block block : blocks) {
|
||||
if (lineNo >= block.beginLine && lineNo <= block.endLine
|
||||
&& (innermost == null || block.beginLine > innermost.beginLine)) {
|
||||
innermost = block;
|
||||
}
|
||||
}
|
||||
return innermost;
|
||||
}
|
||||
|
||||
/**
|
||||
* One expansion of one copycode member: the marker lines that bracket it.
|
||||
*/
|
||||
private static final class Block {
|
||||
private final String member;
|
||||
private final int beginLine;
|
||||
private int endLine;
|
||||
|
||||
private Block(String member, int beginLine) {
|
||||
this.member = member;
|
||||
this.beginLine = beginLine;
|
||||
this.endLine = beginLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Statement-level dataflow: which variable a statement reads and which it writes, as
|
||||
* {@code READS}/{@code WRITES} edges from the enclosing function (or the module) to the field —
|
||||
* what makes {@code flow-forward}, {@code flow-backward} and {@code field-flow} answerable.
|
||||
*
|
||||
* <p>The corpus holds 1,414,872 such statements, and 77.6 % of their targets are qualified
|
||||
* ({@code WSAIVP.INHALT}, {@code xWA_UMKONT_MENGE.xRN_UMKONT_MENGE}). Every operand names <b>Java</b>
|
||||
* identifiers while the field nodes carry <b>Natural</b> names, so resolution is the crux:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>File-local</b> — the symbol table the field walk built maps the Java name to the node
|
||||
* exactly, with no guessing.</li>
|
||||
* <li><b>A field of a {@code USING}'d data area</b> — the operand's prefix is an exposure field
|
||||
* whose declared type names the area, and the pre-parse index supplies the field's Natural
|
||||
* name from that area's own source. The placeholder is then emitted <em>under that Natural
|
||||
* name</em>, so the existing placeholder-field resolution, which joins on name, redirects it
|
||||
* like any Natural one. No new query, and in particular no join on a Java name — the shape
|
||||
* that cost 24 minutes in item D3.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>An operand that resolves to neither is skipped rather than guessed at.
|
||||
*/
|
||||
final class NatJavDataflow {
|
||||
|
||||
/**
|
||||
* Statements whose first operand is written and whose remaining operands are read.
|
||||
*/
|
||||
private static final Set<String> WRITE_FIRST = Set.of("assign", "compute");
|
||||
|
||||
/**
|
||||
* Accumulating statements: the first operand is Natural's {@code ADD … TO x}, which reads the
|
||||
* target as well as writing it — the old value is an input. The Natural parser makes the same
|
||||
* distinction ({@code accumulate} for {@code TO}, plain write for {@code GIVING}); modelling
|
||||
* these as write-only would break every backward flow that runs through an accumulator.
|
||||
*/
|
||||
private static final Set<String> ACCUMULATE_FIRST = Set.of(
|
||||
"addTo", "subtractFrom", "multiplyBy", "divideInto");
|
||||
|
||||
/**
|
||||
* Statements whose operands are all written (Natural {@code RESET}).
|
||||
*/
|
||||
private static final Set<String> WRITE_ALL = Set.of("reset", "resetInitial");
|
||||
|
||||
/**
|
||||
* Statements whose operands are all read (Natural {@code IF}, {@code WRITE}, {@code DISPLAY} …
|
||||
* are handled elsewhere; these are the value-producing forms whose target is named by
|
||||
* {@code into(...)}).
|
||||
*/
|
||||
private static final Set<String> READ_ALL = Set.of(
|
||||
"move", "compress", "add", "subtract", "multiply", "divide", "separate", "examine");
|
||||
|
||||
/**
|
||||
* Wrapper naming the target of a {@code READ_ALL} statement: {@code compress(a, b, into(t))}.
|
||||
*/
|
||||
private static final Set<String> TARGET_WRAPPERS = Set.of("into", "giving", "to");
|
||||
|
||||
private NatJavDataflow() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the module's variable accesses.
|
||||
*
|
||||
* @param byJavaName the file's own fields, by Java identifier
|
||||
* @param exposures Java field name → the data area it exposes, for cross-file operands
|
||||
* @param subroutine method name → the {@code FUNCTION} owning its statements
|
||||
*/
|
||||
static void collect(TypeDeclaration<?> type, AstNode module, Map<String, AstNode> byJavaName,
|
||||
Map<String, String> exposures, Map<String, AstNode> areaPlaceholders,
|
||||
Map<String, AstNode> subroutine, NatJavDdmResolver ddms, NatJavCopycode copycode,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
Map<String, AstNode> placeholderFields = new HashMap<>();
|
||||
for (MethodDeclaration method : type.getMethods()) {
|
||||
AstNode caller = subroutine.getOrDefault(method.getNameAsString(), module);
|
||||
for (MethodCallExpr call : method.findAll(MethodCallExpr.class)) {
|
||||
if (call.getScope().isPresent() || call.getArguments().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String statement = call.getNameAsString();
|
||||
boolean accumulate = ACCUMULATE_FIRST.contains(statement);
|
||||
boolean writeFirst = accumulate || WRITE_FIRST.contains(statement);
|
||||
boolean writeAll = WRITE_ALL.contains(statement);
|
||||
if (!writeFirst && !writeAll && !READ_ALL.contains(statement)) {
|
||||
continue;
|
||||
}
|
||||
int lineNo = call.getBegin().map(p -> p.line).orElse(1);
|
||||
List<Expression> arguments = call.getArguments();
|
||||
for (int i = 0; i < arguments.size(); i++) {
|
||||
Expression argument = arguments.get(i);
|
||||
boolean write = writeAll || (writeFirst && i == 0) || isTargetWrapper(argument);
|
||||
boolean read = !write || (accumulate && i == 0);
|
||||
for (String operand : operands(argument)) {
|
||||
AstNode field = resolve(operand, byJavaName, exposures, areaPlaceholders,
|
||||
placeholderFields, ddms, lineNo, nodes, edges);
|
||||
if (field == null) {
|
||||
continue;
|
||||
}
|
||||
if (write) {
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.WRITES, caller.id(),
|
||||
field.id(), lineNo, null, copycode.attribute(Map.of(), lineNo)));
|
||||
}
|
||||
if (read) {
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.READS, caller.id(),
|
||||
field.id(), lineNo, null, copycode.attribute(Map.of(), lineNo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether {@code argument} is {@code into(x)}/{@code giving(x)}/{@code to(x)}, which name
|
||||
* the statement's target rather than one of its inputs
|
||||
*/
|
||||
private static boolean isTargetWrapper(Expression argument) {
|
||||
return argument instanceof MethodCallExpr call
|
||||
&& call.getScope().isEmpty()
|
||||
&& TARGET_WRAPPERS.contains(call.getNameAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the variable operands of one argument: the argument itself when it names a field, or
|
||||
* the operands of a target wrapper. Literals, constants and runtime calls yield nothing.
|
||||
*/
|
||||
private static List<String> operands(Expression argument) {
|
||||
if (argument instanceof MethodCallExpr call && call.getScope().isEmpty()
|
||||
&& TARGET_WRAPPERS.contains(call.getNameAsString())) {
|
||||
return call.getArguments().stream().map(NatJavDataflow::operandOf)
|
||||
.filter(o -> !o.isEmpty()).toList();
|
||||
}
|
||||
String operand = operandOf(argument);
|
||||
return operand.isEmpty() ? List.of() : List.of(operand);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the field path an expression names, with array subscripts removed
|
||||
* ({@code MSG_INFO.xxMSG_DATA.get(1)} → {@code MSG_INFO.xxMSG_DATA}), or {@code ""} when it names
|
||||
* no field (a literal, a system variable such as {@code $PROGRAM()}, a nested call)
|
||||
*/
|
||||
private static String operandOf(Expression expression) {
|
||||
String text = withoutSubscripts(expression.toString().trim());
|
||||
if (text.isEmpty() || !Character.isJavaIdentifierStart(text.charAt(0))
|
||||
|| text.indexOf('(') >= 0 || text.indexOf('"') >= 0) {
|
||||
return "";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every {@code .get(...)} subscript from a field path, keeping what follows it.
|
||||
*
|
||||
* <p>Cutting the string at the first {@code .get(} instead — which is what this did — turned
|
||||
* {@code ARR.get(1).FLD} into {@code ARR}: the access was attributed to the group and the field
|
||||
* it actually names never saw it, so {@code field-flow} on an indexed group member missed every
|
||||
* statement. The argument list nests ({@code .get(range(1, 4))}), so the closing parenthesis is
|
||||
* found by counting, not by searching.
|
||||
*/
|
||||
private static String withoutSubscripts(String text) {
|
||||
StringBuilder path = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
int subscript = text.indexOf(".get(", i);
|
||||
if (subscript < 0) {
|
||||
path.append(text, i, text.length());
|
||||
break;
|
||||
}
|
||||
path.append(text, i, subscript);
|
||||
int depth = 0;
|
||||
int j = subscript + ".get(".length() - 1;
|
||||
for (; j < text.length(); j++) {
|
||||
char c = text.charAt(j);
|
||||
if (c == '(') {
|
||||
depth++;
|
||||
} else if (c == ')' && --depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j >= text.length()) {
|
||||
break; // unbalanced: keep what was collected rather than guess
|
||||
}
|
||||
i = j + 1;
|
||||
}
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an operand to the field node it names, creating a cross-file placeholder when it
|
||||
* belongs to a {@code USING}'d data area.
|
||||
*/
|
||||
private static @Nullable AstNode resolve(String operand, Map<String, AstNode> byJavaName,
|
||||
Map<String, String> exposures,
|
||||
Map<String, AstNode> areaPlaceholders,
|
||||
Map<String, AstNode> placeholderFields,
|
||||
NatJavDdmResolver ddms, int lineNo,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
AstNode local = byJavaName.get(operand);
|
||||
if (local != null) {
|
||||
return local;
|
||||
}
|
||||
int dot = operand.indexOf('.');
|
||||
if (dot < 0) {
|
||||
// A bare name the file does not declare: nothing identifies which area it came from.
|
||||
return byJavaName.get(operand);
|
||||
}
|
||||
String prefix = operand.substring(0, dot);
|
||||
String field = operand.substring(operand.lastIndexOf('.') + 1);
|
||||
|
||||
AstNode nested = byJavaName.get(field);
|
||||
if (nested != null && byJavaName.containsKey(prefix)) {
|
||||
return nested; // both halves are this file's own — a group member
|
||||
}
|
||||
String area = exposures.get(prefix);
|
||||
AstNode areaPlaceholder = area == null ? null : areaPlaceholders.get(area);
|
||||
if (area == null || areaPlaceholder == null) {
|
||||
return null;
|
||||
}
|
||||
String naturalName = ddms.resolveAreaField(area, field);
|
||||
if (naturalName == null) {
|
||||
return null;
|
||||
}
|
||||
// Emitted under the Natural name so the existing placeholder-field resolution, which joins on
|
||||
// name, can redirect it to the real field in the data area's own file.
|
||||
return placeholderFields.computeIfAbsent(area + "." + naturalName, key -> {
|
||||
AstNode node = new AstNode(UUID.randomUUID(), NodeType.VARIABLE, naturalName, "",
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, null, null, null);
|
||||
nodes.add(node);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, areaPlaceholder.id(),
|
||||
node.id(), lineNo, null));
|
||||
return node;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.body.VariableDeclarator;
|
||||
import com.github.javaparser.ast.expr.AssignExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.NameExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* The ADABAS and work-file statements of a NatJav module, mapped onto the same {@code DB_ACCESS} /
|
||||
* {@code READS} / {@code WRITES} shape the Natural parser produces.
|
||||
*
|
||||
* <p>Only a call with no receiver is a Natural statement. That distinction is the whole game here:
|
||||
* {@code UMK.SPARTEN.get(range(1, 4))} is an array subscript and occurs 435,129 times, against 9,904
|
||||
* real {@code GET}s — reading {@code get(} without it would report a fortyfold phantom.
|
||||
*
|
||||
* <p><b>Writes name the loop, not the view.</b> {@code UPDATE} and {@code DELETE} operate on the
|
||||
* record a preceding read is positioned on, and the transpiler expressed that with an object:
|
||||
* {@code getDEL = get(MDUMKONT_DEL, withHold(), isn); … delete(getDEL);}. The table is only known at
|
||||
* the read. These objects are <em>class fields</em>, so a file-wide map resolves them even when the
|
||||
* read sits in {@code run()} and the write in a subroutine — measured over the corpus's 5,576
|
||||
* {@code update}/{@code delete} statements, 98.7 % resolve, 1.3 % have no visible assignment, and 18
|
||||
* (0.3 %) name an object assigned from two different views. Those 18 get an edge to each candidate
|
||||
* and an {@code ambiguousLoopTarget} marker: for a write, a reported possibility beats a silent
|
||||
* omission.
|
||||
*/
|
||||
final class NatJavDbAccess {
|
||||
|
||||
/**
|
||||
* Statements whose first argument is the view being read. The {@code *Events} variants are the
|
||||
* same Natural statements written with event blocks ({@code AT START OF DATA}, {@code AT BREAK});
|
||||
* the corpus holds 876 of them, and omitting them lost both those reads and every
|
||||
* {@code UPDATE}/{@code DELETE} positioned by one.
|
||||
*/
|
||||
private static final Set<String> READS = Set.of(
|
||||
"read", "get", "find", "histogram",
|
||||
"readEvents", "findEvents", "histogramEvents");
|
||||
|
||||
/**
|
||||
* Statements whose first argument is the view being written.
|
||||
*/
|
||||
private static final Set<String> STORES = Set.of("store");
|
||||
|
||||
/**
|
||||
* Statements whose first argument is a loop object, not a view.
|
||||
*/
|
||||
private static final Map<String, String> BY_LOOP = Map.of("update", "WRITE", "delete", "DELETE");
|
||||
|
||||
private static final Map<String, String> WORKFILE_STATEMENTS = Map.of(
|
||||
"readWorkFile", "READ", "writeWorkFile", "WRITE");
|
||||
|
||||
private NatJavDbAccess() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the module's DB and work-file accesses.
|
||||
*
|
||||
* @param views the module's views, by Java field name
|
||||
* @param subroutine resolves a method to the {@code FUNCTION} that owns its statements, falling
|
||||
* back to the module for {@code run()} and the event blocks
|
||||
*/
|
||||
static void collect(TypeDeclaration<?> type, AstNode module, String sourceFile,
|
||||
Map<String, NatJavFields.View> declaredViews, NatJavTables tables,
|
||||
Map<String, AstNode> subroutine, NatJavCopycode copycode,
|
||||
NatJavDdmResolver ddms, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
Map<String, NatJavFields.View> views = withOperandAliases(type, declaredViews, ddms, tables);
|
||||
Map<String, Set<AstNode>> loopTables = loopTables(type, views);
|
||||
Map<String, AstNode> workfiles = new HashMap<>();
|
||||
|
||||
for (MethodDeclaration method : type.getMethods()) {
|
||||
AstNode caller = subroutine.getOrDefault(method.getNameAsString(), module);
|
||||
for (MethodCallExpr call : method.findAll(MethodCallExpr.class)) {
|
||||
if (call.getScope().isPresent() || call.getArguments().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String statement = call.getNameAsString();
|
||||
if (WORKFILE_STATEMENTS.containsKey(statement)) {
|
||||
workfileAccess(call, WORKFILE_STATEMENTS.get(statement), caller, sourceFile,
|
||||
workfiles, copycode, nodes, edges);
|
||||
} else if (READS.contains(statement)) {
|
||||
tableAccess(call, "READ", EdgeType.READS, tableOf(call.getArgument(0), views),
|
||||
caller, sourceFile, copycode, nodes, edges, false);
|
||||
} else if (STORES.contains(statement)) {
|
||||
tableAccess(call, "WRITE", EdgeType.WRITES, tableOf(call.getArgument(0), views),
|
||||
caller, sourceFile, copycode, nodes, edges, false);
|
||||
} else if (BY_LOOP.containsKey(statement)) {
|
||||
Set<AstNode> candidates = loopTables.getOrDefault(name(call.getArgument(0)), Set.of());
|
||||
boolean ambiguous = candidates.size() > 1;
|
||||
if (candidates.isEmpty()) {
|
||||
tableAccess(call, BY_LOOP.get(statement), EdgeType.WRITES, null, caller,
|
||||
sourceFile, copycode, nodes, edges, false);
|
||||
}
|
||||
for (AstNode table : candidates) {
|
||||
tableAccess(call, BY_LOOP.get(statement), EdgeType.WRITES, table, caller,
|
||||
sourceFile, copycode, nodes, edges, ambiguous);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the declared views with the operands that stand in for one. A statement rarely names
|
||||
* the {@code @ViewOf} field directly:
|
||||
*
|
||||
* <pre>{@code
|
||||
* VVSTICH.VT VTO = VT; // alias of a local view
|
||||
* public final KORIN…YLPOSLV_DA.SWPOS SWPOS = YLPOSLV_DA.SWPOS; // view of a USING'd data area
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Both name their origin in the field's <b>declared type</b> — {@code <owner>.<view>} — which
|
||||
* is a statement of fact, not a guess: the owner is either this class (an alias) or the data area
|
||||
* that declares the view. Resolution follows the chain, since an alias may point at another
|
||||
* alias, and stops at a small depth because a cycle cannot be ruled out from one file.
|
||||
*/
|
||||
/**
|
||||
* @return Java field name → the data area its declared type names, for every field typed
|
||||
* {@code <area>.<member>} where the area is a generated data-area class. This is what lets a
|
||||
* dataflow operand such as {@code WSAIVP.INHALT} be traced to the file that declares the field.
|
||||
*/
|
||||
static Map<String, String> exposures(TypeDeclaration<?> type) {
|
||||
Map<String, String> exposures = new LinkedHashMap<>();
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
for (VariableDeclarator variable : declaration.getVariables()) {
|
||||
String[] parts = ownerAndView(variable.getTypeAsString());
|
||||
if (parts != null && parts[0].endsWith("_DA")) {
|
||||
exposures.put(variable.getNameAsString(), parts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return exposures;
|
||||
}
|
||||
|
||||
private static Map<String, NatJavFields.View> withOperandAliases(
|
||||
TypeDeclaration<?> type, Map<String, NatJavFields.View> declared,
|
||||
NatJavDdmResolver ddms, NatJavTables tables) {
|
||||
// Java field name -> (owner class, view name) from the declared type.
|
||||
Map<String, String[]> typed = new LinkedHashMap<>();
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
for (VariableDeclarator variable : declaration.getVariables()) {
|
||||
String[] parts = ownerAndView(variable.getTypeAsString());
|
||||
if (parts != null) {
|
||||
typed.put(variable.getNameAsString(), parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, NatJavFields.View> views = new LinkedHashMap<>(declared);
|
||||
for (String operand : typed.keySet()) {
|
||||
if (!views.containsKey(operand)) {
|
||||
NatJavFields.View resolved = resolveOperand(operand, typed, views, ddms, tables, 0);
|
||||
if (resolved != null) {
|
||||
views.put(operand, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
private static NatJavFields.@Nullable View resolveOperand(
|
||||
String operand, Map<String, String[]> typed, Map<String, NatJavFields.View> views,
|
||||
NatJavDdmResolver ddms, NatJavTables tables, int depth) {
|
||||
if (depth > 4) {
|
||||
return null;
|
||||
}
|
||||
NatJavFields.View known = views.get(operand);
|
||||
if (known != null) {
|
||||
return known;
|
||||
}
|
||||
String[] parts = typed.get(operand);
|
||||
if (parts == null) {
|
||||
return null;
|
||||
}
|
||||
String owner = parts[0];
|
||||
String view = parts[1];
|
||||
NatJavDdmResolver.Ddm ddm = ddms.resolveAreaView(owner, view);
|
||||
if (ddm != null) {
|
||||
return new NatJavFields.View(null, tables.get(ddm.tableName(), 1));
|
||||
}
|
||||
// Not a data-area view: an alias of another operand in this file.
|
||||
return resolveOperand(view, typed, views, ddms, tables, depth + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last two segments of a declared type — {@code {owner, view}} — or, for an
|
||||
* unqualified type naming a view in this same file ({@code VERTRAG_VD VD = VERTRAG_VD;}),
|
||||
* {@code {"", view}}. {@code null} for a type that can name no view (a generic).
|
||||
*/
|
||||
private static String @Nullable [] ownerAndView(String declaredType) {
|
||||
String type = declaredType.trim();
|
||||
if (type.indexOf('<') >= 0 || type.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (type.indexOf('.') < 0) {
|
||||
// The transpiler also writes the alias with the view's own generated type, unqualified.
|
||||
// There is no owner to look up then; the chain continues at the view itself.
|
||||
return new String[]{"", type};
|
||||
}
|
||||
String[] segments = type.split("\\.");
|
||||
if (segments.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return new String[]{segments[segments.length - 2], segments[segments.length - 1]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return loop-object Java name → the tables it may be positioned on. A {@link Set} because an
|
||||
* object reassigned from two views has two, and picking one would be a guess.
|
||||
*/
|
||||
private static Map<String, Set<AstNode>> loopTables(TypeDeclaration<?> type,
|
||||
Map<String, NatJavFields.View> views) {
|
||||
Map<String, Set<AstNode>> loops = new LinkedHashMap<>();
|
||||
for (AssignExpr assignment : type.findAll(AssignExpr.class)) {
|
||||
if (!(assignment.getTarget() instanceof NameExpr target)
|
||||
|| !(assignment.getValue() instanceof MethodCallExpr call)
|
||||
|| call.getScope().isPresent()
|
||||
// A STORE positions a record too, and an UPDATE may follow on it.
|
||||
|| !(READS.contains(call.getNameAsString()) || STORES.contains(call.getNameAsString()))
|
||||
|| call.getArguments().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
AstNode table = tableOf(call.getArgument(0), views);
|
||||
if (table != null) {
|
||||
loops.computeIfAbsent(target.getNameAsString(), n -> new LinkedHashSet<>()).add(table);
|
||||
}
|
||||
}
|
||||
return loops;
|
||||
}
|
||||
|
||||
private static void tableAccess(MethodCallExpr call, String kind, EdgeType direction,
|
||||
@Nullable AstNode table, AstNode caller, String sourceFile,
|
||||
NatJavCopycode copycode, List<AstNode> nodes, List<AstEdge> edges,
|
||||
boolean ambiguous) {
|
||||
int lineNo = line(call);
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
if (ambiguous) {
|
||||
properties.put("ambiguousLoopTarget", "true");
|
||||
}
|
||||
AstNode access = new AstNode(UUID.randomUUID(), NodeType.DB_ACCESS,
|
||||
table != null ? table.name() : "", sourceFile, NatJavParser.LANGUAGE, lineNo, lineNo,
|
||||
kind, call.toString(), copycode.attribute(properties, lineNo));
|
||||
nodes.add(access);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, caller.id(), access.id(), lineNo, null));
|
||||
if (table != null) {
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.USES_TYPE, access.id(), table.id(), lineNo, null));
|
||||
edges.add(new AstEdge(UUID.randomUUID(), direction, caller.id(), table.id(), lineNo, null,
|
||||
copycode.attribute(Map.of(), lineNo)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a {@code WORKFILE} and its access. The work file is keyed by its number, exactly as in
|
||||
* Natural ({@code READ/WRITE WORK FILE n}), and the remaining arguments are the record buffer.
|
||||
*/
|
||||
private static void workfileAccess(MethodCallExpr call, String kind, AstNode caller,
|
||||
String sourceFile, Map<String, AstNode> workfiles,
|
||||
NatJavCopycode copycode, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
int lineNo = line(call);
|
||||
String number = call.getArgument(0).toString().trim();
|
||||
AstNode workfile = workfiles.computeIfAbsent(number, n -> {
|
||||
AstNode node = new AstNode(UUID.randomUUID(), NodeType.WORKFILE, n, sourceFile,
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, null, null, null);
|
||||
nodes.add(node);
|
||||
return node;
|
||||
});
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
String buffer = call.getArguments().stream().skip(1).map(Object::toString)
|
||||
.reduce((a, b) -> a + ", " + b).orElse("");
|
||||
if (!buffer.isEmpty()) {
|
||||
properties.put("recordBuffer", buffer);
|
||||
}
|
||||
AstNode access = new AstNode(UUID.randomUUID(), NodeType.WORKFILE_ACCESS, number, sourceFile,
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, kind, call.toString(),
|
||||
copycode.attribute(properties, lineNo));
|
||||
nodes.add(access);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, caller.id(), access.id(), lineNo, null));
|
||||
// The record buffer goes on the edge as well as the node: workfile-accesses aggregates it
|
||||
// from the READS/WRITES edge (`collect(r.recordBuffer)`), so putting it only on the node
|
||||
// leaves every buffer list empty. The Natural parser writes it to both for the same reason.
|
||||
edges.add(new AstEdge(UUID.randomUUID(),
|
||||
"WRITE".equals(kind) ? EdgeType.WRITES : EdgeType.READS,
|
||||
caller.id(), workfile.id(), lineNo, null, copycode.attribute(properties, lineNo)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the table a statement's first argument reads, or {@code null} when the operand is not a
|
||||
* known view (a view declared in a {@code USING}'d data area, for instance)
|
||||
*/
|
||||
private static @Nullable AstNode tableOf(Expression operand, Map<String, NatJavFields.View> views) {
|
||||
NatJavFields.View view = views.get(name(operand));
|
||||
return view == null ? null : view.table();
|
||||
}
|
||||
|
||||
private static String name(Expression expression) {
|
||||
return expression instanceof NameExpr named ? named.getNameAsString() : expression.toString().trim();
|
||||
}
|
||||
|
||||
private static int line(MethodCallExpr call) {
|
||||
return call.getBegin().map(p -> p.line).orElse(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ParserConfiguration;
|
||||
import com.github.javaparser.ParserConfiguration.LanguageLevel;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.body.VariableDeclarator;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A data definition module — the description of one ADABAS file. The transpiler wrote each as a
|
||||
* {@code NaturalDdm} class of constants:
|
||||
*
|
||||
* <pre>{@code
|
||||
* public static final DbN VR_KEY = reference(FDT0050.AG, "VR-KEY", NUMERIC, 3, 0);
|
||||
* public static final DbA OART = reference(FDT0050.AH, "OART");
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Two things come out of that, and both are needed elsewhere: the column's <b>Natural</b> name,
|
||||
* which a view referencing it does not state (it declares columns by Java name only), and its format
|
||||
* where the DDM gives one — 1,834 of the corpus's 17,982 references do; the other 16,148 keep their
|
||||
* length in the FDT class, and it is left absent rather than guessed.
|
||||
*
|
||||
* <p>The DDM file itself stays a {@code MODULE}: a {@code DB_TABLE} carries no source file anywhere
|
||||
* in this system — that is what makes every reference to a table merge onto one node — so the file
|
||||
* and the table are two nodes joined by {@link EdgeType#MAPS_TO}, exactly as the Java parser joins a
|
||||
* JPA entity to its table.
|
||||
*/
|
||||
public final class NatJavDdm {
|
||||
|
||||
private static final ParserConfiguration PARSER_CONFIGURATION =
|
||||
new ParserConfiguration().setLanguageLevel(LanguageLevel.JAVA_21);
|
||||
|
||||
/**
|
||||
* Declared field types of a DDM constant, mapped to the Natural format letter. {@code DbGroup} is
|
||||
* a periodic/multiple-value group and carries no format of its own.
|
||||
*/
|
||||
private static final Map<String, String> FORMATS = Map.of(
|
||||
"DbA", "A", "DbN", "N", "DbP", "P", "DbB", "B", "DbL", "L", "DbT", "T", "DbD", "D");
|
||||
|
||||
private NatJavDdm() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a DDM source file into the table description the view resolution needs.
|
||||
*
|
||||
* @param content the DDM class's source
|
||||
* @param stem the file's name without extension, used as the identity fallback
|
||||
* @return the DDM, or {@code null} if {@code content} does not parse
|
||||
*/
|
||||
public static NatJavDdmResolver.@Nullable Ddm parse(String content, String stem) {
|
||||
CompilationUnit unit;
|
||||
try {
|
||||
unit = new com.github.javaparser.JavaParser(PARSER_CONFIGURATION).parse(content)
|
||||
.getResult().orElse(null);
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
if (unit == null) {
|
||||
return null;
|
||||
}
|
||||
TypeDeclaration<?> type = NatJavFacts.primaryType(unit, stem);
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
NatJavFacts facts = NatJavFacts.of(unit, stem);
|
||||
Map<String, String> columns = new LinkedHashMap<>();
|
||||
Map<String, String> formats = new LinkedHashMap<>();
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
for (VariableDeclarator variable : declaration.getVariables()) {
|
||||
Reference reference = referenceOf(variable);
|
||||
if (reference == null) {
|
||||
continue;
|
||||
}
|
||||
columns.put(variable.getNameAsString(), reference.naturalName);
|
||||
if (reference.format != null) {
|
||||
formats.put(variable.getNameAsString(), reference.format);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new NatJavDdmResolver.Ddm(facts.naturalName(), columns, formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the column nodes of a DDM file and the {@link EdgeType#MAPS_TO} edge to the table it
|
||||
* describes. Called when the file being parsed <em>is</em> a DDM.
|
||||
*/
|
||||
static void collect(TypeDeclaration<?> type, AstNode module, String tableName, String sourceFile,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
AstNode table = new AstNode(UUID.randomUUID(), NodeType.DB_TABLE, tableName, "",
|
||||
NatJavParser.LANGUAGE, module.startLine(), module.startLine(), null, null, null);
|
||||
nodes.add(table);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.MAPS_TO, module.id(), table.id(),
|
||||
module.startLine(), null));
|
||||
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
for (VariableDeclarator variable : declaration.getVariables()) {
|
||||
Reference reference = referenceOf(variable);
|
||||
if (reference == null) {
|
||||
continue;
|
||||
}
|
||||
int lineNo = declaration.getBegin().map(p -> p.line).orElse(module.startLine());
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
properties.put(NatJavFields.JAVA_NAME, variable.getNameAsString());
|
||||
if (reference.shortName != null) {
|
||||
// The ADABAS two-character short name — the physical field key, which the Natural
|
||||
// source never shows and only the DDM knows.
|
||||
properties.put("adabasShortName", reference.shortName);
|
||||
}
|
||||
AstNode column = new AstNode(UUID.randomUUID(), NodeType.FIELD, reference.naturalName,
|
||||
sourceFile, NatJavParser.LANGUAGE, lineNo, lineNo, reference.format, null,
|
||||
properties);
|
||||
nodes.add(column);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, module.id(), column.id(),
|
||||
lineNo, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code reference(...)} initializer's parts, or {@code null} if this field is not a
|
||||
* column declaration
|
||||
*/
|
||||
private static @Nullable Reference referenceOf(VariableDeclarator variable) {
|
||||
Expression initializer = variable.getInitializer().orElse(null);
|
||||
if (!(initializer instanceof MethodCallExpr call) || !call.getNameAsString().equals("reference")
|
||||
|| call.getArguments().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String naturalName = null;
|
||||
List<String> trailing = new ArrayList<>();
|
||||
String shortName = null;
|
||||
for (Expression argument : call.getArguments()) {
|
||||
if (argument instanceof StringLiteralExpr literal) {
|
||||
if (naturalName == null) {
|
||||
naturalName = literal.asString().trim();
|
||||
}
|
||||
} else if (naturalName == null) {
|
||||
// The FDT constant, e.g. FDT0050.AG — its last segment is the ADABAS short name.
|
||||
String text = argument.toString();
|
||||
int dot = text.lastIndexOf('.');
|
||||
shortName = dot >= 0 ? text.substring(dot + 1) : null;
|
||||
} else {
|
||||
trailing.add(argument.toString().trim());
|
||||
}
|
||||
}
|
||||
if (naturalName == null || naturalName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new Reference(naturalName, shortName, format(variable.getTypeAsString(), trailing));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Natural type spec — the declared {@code Db*} type's format letter plus the
|
||||
* length/precision the DDM states, spelled as elsewhere ({@code N3}, not {@code N3,0}) — or
|
||||
* {@code null} when the DDM names no format
|
||||
*/
|
||||
private static @Nullable String format(String declaredType, List<String> trailing) {
|
||||
String letter = FORMATS.get(declaredType);
|
||||
if (letter == null) {
|
||||
return null;
|
||||
}
|
||||
List<String> numbers = trailing.stream().filter(t -> t.matches("\\d+")).toList();
|
||||
if (numbers.isEmpty()) {
|
||||
return letter;
|
||||
}
|
||||
if (numbers.size() >= 2 && "0".equals(numbers.get(1))) {
|
||||
return letter + numbers.get(0);
|
||||
}
|
||||
return letter + String.join(",", numbers);
|
||||
}
|
||||
|
||||
/**
|
||||
* One column declaration's parts.
|
||||
*/
|
||||
private record Reference(String naturalName, @Nullable String shortName, @Nullable String format) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resolves a DDM class name to the ADABAS file it describes. A NatJav view declares
|
||||
* {@code @L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT = view("MDUMKONT")}: the <em>class</em> is named
|
||||
* here, the table name and every column name live in that other file. This is what carries them
|
||||
* across.
|
||||
*
|
||||
* <p>Supplied by the ingest layer, which has filesystem access; the parser SPI itself stays
|
||||
* file-agnostic — the same split as {@code CopycodeResolver} on the Natural side. {@link #NONE}
|
||||
* knows no DDMs, in which case a view is emitted without its columns and without a table link.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NatJavDdmResolver {
|
||||
|
||||
/**
|
||||
* A resolver that knows no DDM.
|
||||
*/
|
||||
NatJavDdmResolver NONE = name -> null;
|
||||
|
||||
/**
|
||||
* @param ddmClassName the simple class name a view's {@code @ViewOf} refers to, e.g.
|
||||
* {@code MDUMKONT_DDM}
|
||||
* @return the DDM, or {@code null} if it is not among the project's files
|
||||
*/
|
||||
@Nullable Ddm resolve(String ddmClassName);
|
||||
|
||||
/**
|
||||
* Resolves a view <em>declared in a data area</em>. A program that {@code USING}s an area re-declares
|
||||
* its views as fields typed by the area:
|
||||
* {@code public final KORIN…YLPOSLV_DA.SWPOS SWPOS = YLPOSLV_DA.SWPOS;} — and then reads
|
||||
* {@code find(SWPOS, …)}. The view itself, and with it the DDM, lives in the area's file, so the
|
||||
* statement's table is only knowable across files.
|
||||
*
|
||||
* @param areaClassName the data area's simple class name, e.g. {@code YLPOSLV_DA}
|
||||
* @param viewName the view's Java field name inside it, e.g. {@code SWPOS}
|
||||
* @return the DDM that view reads, or {@code null} if the area or the view is unknown
|
||||
*/
|
||||
default @Nullable Ddm resolveAreaView(String areaClassName, String viewName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a <em>field</em> of a data area to its Natural name. A dataflow operand names Java
|
||||
* identifiers throughout ({@code WSAIVP.INHALT}), while the field's node carries the Natural name
|
||||
* the transpiler preserved in the area's own file ({@code INHALT} may well be {@code #INHALT} or
|
||||
* {@code IN-HALT}). Measured over the corpus, 77.6 % of assignment targets are qualified this
|
||||
* way, so this is the bridge the whole of item E1 rests on.
|
||||
*
|
||||
* <p>Flat over the area rather than per group: the operand's prefix identifies the <em>area</em>
|
||||
* (through the program's exposure field's declared type), and the field name within it is what
|
||||
* has to be translated.
|
||||
*
|
||||
* @param areaClassName the data area's simple class name, e.g. {@code WSAIVP_DA}
|
||||
* @param javaFieldName the field's Java identifier, e.g. {@code INHALT}
|
||||
* @return the field's Natural name, or {@code null} if the area or the field is unknown
|
||||
*/
|
||||
default @Nullable String resolveAreaField(String areaClassName, String javaFieldName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* One data definition module.
|
||||
*
|
||||
* @param tableName the ADABAS file's Natural name ({@code MDUMKONT}) — the name every
|
||||
* {@code DB_TABLE} node in the graph is keyed by
|
||||
* @param columns Java field name → the column's Natural name, e.g. {@code VR_KEY} →
|
||||
* {@code VR-KEY}. A view declares its columns by Java name only, so this is the
|
||||
* only way to give them the name the Natural original used.
|
||||
* @param formats Java field name → the column's Natural type spec where the DDM states one
|
||||
* ({@code N3}, {@code P8}); absent for the 16,148 of 17,982 references that
|
||||
* name only the field, whose length lives in the FDT class
|
||||
*/
|
||||
record Ddm(String tableName, Map<String, String> columns, Map<String, String> formats) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.MemberValuePair;
|
||||
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import com.github.javaparser.ast.type.ClassOrInterfaceType;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The module-level facts of one NatJav compilation unit, shared by {@link NatJavParser} and
|
||||
* {@link NatJavCoarseScanner} so a coarse scan and a deep parse can never disagree about a
|
||||
* module's identity.
|
||||
*
|
||||
* @param naturalName the <b>Natural</b> object name — the module's identity in the graph, so that a
|
||||
* NatJav project answers the same queries as its Natural original
|
||||
* ({@code V-MIT04}, not {@code V_MIT04}; the GDA {@code COMMON}, not
|
||||
* {@code COMMON_DA})
|
||||
* @param className the Java class name the transpiler generated
|
||||
* @param packageName the Java package (the transpiled Natural library path), or {@code null}
|
||||
* @param kind the Natural object type, or {@code null} for a class that is not NatJav at all
|
||||
* @param description the migrated header's {@code @FUNCTION} text, or {@code null}
|
||||
* @param owner the migrated header's {@code @OWNER} code, or {@code null}
|
||||
*/
|
||||
public record NatJavFacts(String naturalName, String className, @Nullable String packageName,
|
||||
@Nullable NatJavModuleKind kind, @Nullable String description,
|
||||
@Nullable String owner) {
|
||||
|
||||
/**
|
||||
* {@code @FUNCTION <text>} of the {@code *LCM-HEADER} block the migration wrote into each class's
|
||||
* Javadoc — the module's one-line purpose, and the closest equivalent to the {@code **SAG TITLE}
|
||||
* line the Natural parser reads.
|
||||
*/
|
||||
private static final Pattern HEADER_FUNCTION = Pattern.compile("@FUNCTION\\s+(.+?)\\s*\\**\\s*$");
|
||||
|
||||
/**
|
||||
* {@code @OWNER <code>} of the same header block: the owning developer/department key.
|
||||
*/
|
||||
private static final Pattern HEADER_OWNER = Pattern.compile("@OWNER\\s+(\\S+)");
|
||||
|
||||
/**
|
||||
* Leading Javadoc decoration of one header line: {@code " * * "} — the comment's own asterisks
|
||||
* plus the ones the original Natural comment box drew.
|
||||
*/
|
||||
private static final Pattern JAVADOC_DECORATION = Pattern.compile("^[\\s*]+");
|
||||
|
||||
/**
|
||||
* Reads the facts of {@code unit}'s primary type.
|
||||
*
|
||||
* @param unit the parsed compilation unit
|
||||
* @param stem the file's name without extension, used to pick the primary type among several
|
||||
* top-level types and as the identity fallback for a unit with no type at all
|
||||
* @return the facts, never {@code null}
|
||||
*/
|
||||
public static NatJavFacts of(CompilationUnit unit, String stem) {
|
||||
TypeDeclaration<?> type = primaryType(unit, stem);
|
||||
if (type == null) {
|
||||
return new NatJavFacts(stem, stem, packageName(unit), null, null, null);
|
||||
}
|
||||
String className = type.getNameAsString();
|
||||
NatJavModuleKind kind = NatJavModuleKind.ofBaseClass(baseClassName(type));
|
||||
// The transpiler emits `@Natural(moduleName="...")` only where the Natural name cannot be
|
||||
// spelled as a Java identifier — hyphens (V-MIT04), a leading '#' (#ABBRUCH), or the _DA/_DDM
|
||||
// suffix it appends. Where the two agree it writes a bare `@Natural`, so the class name is
|
||||
// the Natural name. Never derive the name from the class name by string surgery: measured
|
||||
// over the reference corpus that rule is wrong for 72 of 7,093 annotated modules, because
|
||||
// Natural names may legitimately contain '_' (V_MIT11, T-PRFZ11 vs T_PRFZ11).
|
||||
String naturalName = moduleNameAnnotation(type).orElse(className);
|
||||
return new NatJavFacts(naturalName, className, packageName(unit), kind,
|
||||
headerValue(type, HEADER_FUNCTION), headerValue(type, HEADER_OWNER));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type declaration that <em>is</em> the module: the top-level type named after the
|
||||
* file, else the first top-level type. Nested types (a data area's inner group classes) are not
|
||||
* modules of their own.
|
||||
*/
|
||||
static @Nullable TypeDeclaration<?> primaryType(CompilationUnit unit, String stem) {
|
||||
TypeDeclaration<?> first = null;
|
||||
for (TypeDeclaration<?> type : unit.getTypes()) {
|
||||
if (type.getNameAsString().equals(stem)) {
|
||||
return type;
|
||||
}
|
||||
if (first == null) {
|
||||
first = type;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
private static @Nullable String packageName(CompilationUnit unit) {
|
||||
return unit.getPackageDeclaration().map(p -> p.getNameAsString()).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the simple name of {@code type}'s superclass, or {@code null} if it has none
|
||||
*/
|
||||
private static @Nullable String baseClassName(TypeDeclaration<?> type) {
|
||||
if (!(type instanceof ClassOrInterfaceDeclaration declaration)) {
|
||||
return null;
|
||||
}
|
||||
return declaration.getExtendedTypes().stream()
|
||||
.findFirst()
|
||||
.map(ClassOrInterfaceType::getNameAsString)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code moduleName} member of the type's {@code @Natural} annotation, if it has one
|
||||
*/
|
||||
private static Optional<String> moduleNameAnnotation(TypeDeclaration<?> type) {
|
||||
for (AnnotationExpr annotation : type.getAnnotations()) {
|
||||
if (!annotation.getNameAsString().equals("Natural")
|
||||
|| !(annotation instanceof NormalAnnotationExpr normal)) {
|
||||
continue;
|
||||
}
|
||||
for (MemberValuePair pair : normal.getPairs()) {
|
||||
if (pair.getNameAsString().equals("moduleName")
|
||||
&& pair.getValue() instanceof StringLiteralExpr literal) {
|
||||
String value = literal.asString().trim();
|
||||
if (!value.isEmpty()) {
|
||||
return Optional.of(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first match of {@code pattern} in the type's Javadoc (the migrated
|
||||
* {@code *LCM-HEADER} block), stripped of the comment's own decoration, or {@code null}
|
||||
*/
|
||||
private static @Nullable String headerValue(TypeDeclaration<?> type, Pattern pattern) {
|
||||
Optional<String> javadoc = type.getComment().map(c -> c.getContent());
|
||||
if (javadoc.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (String raw : javadoc.get().split("\\R")) {
|
||||
String line = JAVADOC_DECORATION.matcher(raw).replaceFirst("");
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
String value = matcher.group(1).trim();
|
||||
if (!value.isEmpty()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.body.VariableDeclarator;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Builds the field tree of a NatJav class — the transpiled form of a Natural {@code DEFINE DATA}
|
||||
* block, whether it is a standalone data area ({@code *_DA.java}) or the local data a program
|
||||
* declares inline (15,775 non-data-area files in the reference corpus do).
|
||||
*
|
||||
* <p>The nesting comes from the {@code @L1}..{@code @L7} <b>annotations</b>, not from Java scoping:
|
||||
* every field is a flat member of the class and only its level number says what contains it. That
|
||||
* is the same shape the Natural parser walks, so this keeps a level stack exactly as it does.
|
||||
*
|
||||
* <p>Names are taken from the declaration's Natural-name literal ({@code A(8, "#WA-SPARTEN")}),
|
||||
* never derived from the Java identifier. Measured over the corpus's 709,376 named fields, the
|
||||
* obvious rule (leading {@code x} → {@code #}, {@code g} → {@code +}, {@code _} → {@code -}) is
|
||||
* wrong for <b>4.81 %</b> of them, because Natural names may mix {@code _} and {@code -}
|
||||
* ({@code #IP_EL_STV-CODE}). Where no literal exists, the Java name stands and
|
||||
* {@link #NATURAL_NAME_KNOWN} records that it is not the Natural one — a wrong-but-plausible name
|
||||
* is worse than an honest Java one.
|
||||
*/
|
||||
final class NatJavFields {
|
||||
|
||||
/**
|
||||
* Property set to {@code "false"} on a field whose Natural name the source does not state, so a
|
||||
* caller can tell an authoritative name from a Java identifier standing in for one.
|
||||
*/
|
||||
static final String NATURAL_NAME_KNOWN = "naturalNameKnown";
|
||||
|
||||
/**
|
||||
* Property carrying the Java field identifier when it differs from the node's name.
|
||||
*/
|
||||
static final String JAVA_NAME = "javaName";
|
||||
|
||||
/**
|
||||
* Marks the structural declaration {@code @Ln Redefine <field>} — Natural's
|
||||
* {@code n REDEFINE <field>} — so a caller can tell a redefinition from a real group.
|
||||
*/
|
||||
static final String REDEFINES = "redefines";
|
||||
|
||||
/**
|
||||
* The scope annotations, mapping to the {@code DEFINE DATA} section keyword the Natural parser
|
||||
* stores as {@code scope}. Exactly one appears per data area in the corpus (4,654 PARAMETER,
|
||||
* 1,157 LOCAL, 7 GLOBAL — summing to all 5,818), which is what the {@code .pda}/{@code .lda}/
|
||||
* {@code .gda} extension says for Natural.
|
||||
*/
|
||||
private static final Map<String, String> SCOPES = Map.of(
|
||||
"Parameter", "PARAMETER",
|
||||
"Local", "LOCAL",
|
||||
"Global", "GLOBAL",
|
||||
"Independent", "INDEPENDENT");
|
||||
|
||||
private NatJavFields() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks {@code type}'s field declarations and appends the resulting nodes and edges.
|
||||
*
|
||||
* @param type the module's primary type
|
||||
* @param shell the file's {@code MODULE}/{@code DATA_STRUCTURE} node, which owns every
|
||||
* top-level field
|
||||
* @param moduleName the shell's Natural name, needed for the collapse rule below
|
||||
* @param sourceFile the file's project-relative path
|
||||
* @param nodes collects the created nodes
|
||||
* @param edges collects the created {@code CONTAINS} edges
|
||||
*/
|
||||
static Declared collect(TypeDeclaration<?> type, AstNode shell, String moduleName,
|
||||
String sourceFile, NatJavDdmResolver ddms, NatJavTables tables,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
Map<String, View> views = new LinkedHashMap<>();
|
||||
Deque<Level> stack = new ArrayDeque<>();
|
||||
stack.push(new Level(0, shell));
|
||||
// Java name -> the node it declared, so `Redefine xSYS_SCHALTER` and an array bound naming a
|
||||
// variable resolve to the Natural name without guessing.
|
||||
Map<String, AstNode> byJavaName = new HashMap<>();
|
||||
String scope = "";
|
||||
// A view's columns are declared without names or lengths — both live in the DDM class, which
|
||||
// is another file. They are resolved with the DB access that reads them; until then the view
|
||||
// itself is emitted and everything nested under it skipped, rather than inventing names.
|
||||
int skipBelow = Integer.MAX_VALUE;
|
||||
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
String annotationScope = scopeOf(declaration);
|
||||
if (annotationScope != null) {
|
||||
scope = annotationScope;
|
||||
}
|
||||
Integer level = levelOf(declaration);
|
||||
if (level == null || declaration.getVariables().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (level > skipBelow) {
|
||||
continue;
|
||||
}
|
||||
skipBelow = Integer.MAX_VALUE;
|
||||
|
||||
VariableDeclarator variable = declaration.getVariable(0);
|
||||
String javaName = variable.getNameAsString();
|
||||
String typeName = variable.getTypeAsString();
|
||||
@Nullable MethodCallExpr baseCall = baseCall(variable);
|
||||
@Nullable MethodCallExpr arrayCall = chainedCall(variable, "defineArray");
|
||||
@Nullable MethodCallExpr constantCall = chainedCall(variable, "constant");
|
||||
boolean isView = hasAnnotation(declaration, "ViewOf");
|
||||
boolean isRedefine = typeName.equals("Redefine");
|
||||
|
||||
while (stack.size() > 1 && stack.peek().level >= level) {
|
||||
stack.pop();
|
||||
}
|
||||
AstNode parent = stack.peek().node;
|
||||
|
||||
// Item-100 equivalent: the shell already carries the area's name, so a top-level group of
|
||||
// that same name must not become a second node — 3,773 of the corpus's 5,818 data areas
|
||||
// declare exactly that, and each would otherwise produce two identically named
|
||||
// DATA_STRUCTUREs in one file.
|
||||
String naturalName = naturalName(javaName, baseCall, isRedefine, byJavaName);
|
||||
boolean isContainer = isRedefine || isView || !NatJavTypeSpec.isFormat(typeName);
|
||||
if (level == 1 && !isView && isContainer && naturalName.equals(moduleName)) {
|
||||
stack.push(new Level(level, shell));
|
||||
byJavaName.put(javaName, shell);
|
||||
continue;
|
||||
}
|
||||
|
||||
AstNode node = fieldNode(naturalName, javaName, typeName, sourceFile, lineOf(declaration),
|
||||
baseCall, arrayCall, constantCall, isView, isRedefine, scope, byJavaName);
|
||||
nodes.add(node);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, parent.id(), node.id(),
|
||||
node.startLine(), null));
|
||||
// A REDEFINE re-declares the same Java identifier as the field it redefines. The symbol
|
||||
// table must keep pointing at the field: an operand names the variable being written,
|
||||
// not the redefinition group over it. The Natural parser keeps the same separation
|
||||
// (`variables` vs `dataStructures`).
|
||||
if (isRedefine) {
|
||||
byJavaName.putIfAbsent(javaName, node);
|
||||
} else {
|
||||
byJavaName.put(javaName, node);
|
||||
}
|
||||
|
||||
if (node.type() == NodeType.DATA_STRUCTURE) {
|
||||
stack.push(new Level(level, node));
|
||||
if (isView) {
|
||||
// A view's columns are declared by Java name only; the DDM the @ViewOf names holds
|
||||
// their Natural names and the table's. Everything nested under the view is taken
|
||||
// from there instead of from the (nameless) declarations below it.
|
||||
views.put(javaName, view(declaration, node, sourceFile, ddms, tables, nodes, edges));
|
||||
skipBelow = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Declared(views, byJavaName);
|
||||
}
|
||||
|
||||
/**
|
||||
* What a file declares: its views (for DB statements) and its field symbol table (for dataflow
|
||||
* operands, which name Java identifiers while the nodes carry Natural names).
|
||||
*/
|
||||
record Declared(Map<String, View> views, Map<String, AstNode> byJavaName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a view to the table it reads and gives its columns their Natural names, both out of the
|
||||
* DDM class the {@code @ViewOf} annotation names.
|
||||
*/
|
||||
private static View view(FieldDeclaration declaration, AstNode viewNode, String sourceFile,
|
||||
NatJavDdmResolver ddms, NatJavTables tables,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
String ddmClass = ddmClassOf(declaration);
|
||||
NatJavDdmResolver.Ddm ddm = ddmClass == null ? null : ddms.resolve(ddmClass);
|
||||
if (ddm == null) {
|
||||
// No DDM in this project (or none resolvable): the view stands, without columns and
|
||||
// without a table — better than inventing either.
|
||||
return new View(viewNode, null);
|
||||
}
|
||||
AstNode table = tables.get(ddm.tableName(), viewNode.startLine());
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.USES_TYPE, viewNode.id(), table.id(),
|
||||
viewNode.startLine(), null));
|
||||
for (VariableDeclarator column : columnsOf(declaration)) {
|
||||
String javaName = column.getNameAsString();
|
||||
String naturalName = ddm.columns().get(javaName);
|
||||
if (naturalName == null) {
|
||||
continue;
|
||||
}
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
properties.put(JAVA_NAME, javaName);
|
||||
AstNode field = new AstNode(UUID.randomUUID(), NodeType.FIELD, naturalName, sourceFile,
|
||||
NatJavParser.LANGUAGE, viewNode.startLine(), viewNode.startLine(),
|
||||
ddm.formats().get(javaName), null, properties);
|
||||
nodes.add(field);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, viewNode.id(), field.id(),
|
||||
field.startLine(), null));
|
||||
}
|
||||
return new View(viewNode, table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the simple class name of a view's {@code @ViewOf} target, or {@code null}
|
||||
*/
|
||||
private static @Nullable String ddmClassOf(FieldDeclaration declaration) {
|
||||
for (AnnotationExpr annotation : declaration.getAnnotations()) {
|
||||
if (!annotation.getNameAsString().equals("ViewOf")) {
|
||||
continue;
|
||||
}
|
||||
String type = declaration.getVariable(0).getTypeAsString();
|
||||
int dot = type.lastIndexOf('.');
|
||||
return dot >= 0 ? type.substring(dot + 1) : type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the column declarations nested under {@code view} — the field declarations that follow
|
||||
* it at a deeper level, which the level walk skipped
|
||||
*/
|
||||
private static List<VariableDeclarator> columnsOf(FieldDeclaration view) {
|
||||
List<VariableDeclarator> columns = new ArrayList<>();
|
||||
Integer viewLevel = levelOf(view);
|
||||
if (viewLevel == null || view.getParentNode().isEmpty()) {
|
||||
return columns;
|
||||
}
|
||||
boolean seen = false;
|
||||
for (com.github.javaparser.ast.Node sibling : view.getParentNode().get().getChildNodes()) {
|
||||
if (sibling == view) {
|
||||
seen = true;
|
||||
continue;
|
||||
}
|
||||
if (!seen || !(sibling instanceof FieldDeclaration field)) {
|
||||
continue;
|
||||
}
|
||||
Integer level = levelOf(field);
|
||||
if (level == null || level <= viewLevel) {
|
||||
break;
|
||||
}
|
||||
columns.addAll(field.getVariables());
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* A view operand and the table it reads.
|
||||
*
|
||||
* @param node the view's own {@code DATA_STRUCTURE}, or {@code null} when the operand is an
|
||||
* alias or a view declared in a {@code USING}'d data area — those have their node in
|
||||
* the file that declares them, not here
|
||||
* @param table the {@code DB_TABLE}, or {@code null} when the DDM could not be resolved
|
||||
*/
|
||||
record View(@Nullable AstNode node, @Nullable AstNode table) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the field's Natural name: the declaration's name literal, the redefined field's name
|
||||
* for a {@code Redefine}, else the Java identifier (flagged by the caller)
|
||||
*/
|
||||
private static String naturalName(String javaName, @Nullable MethodCallExpr baseCall,
|
||||
boolean isRedefine, Map<String, AstNode> byJavaName) {
|
||||
if (isRedefine) {
|
||||
AstNode target = byJavaName.get(javaName);
|
||||
return target != null ? target.name() : javaName;
|
||||
}
|
||||
String literal = nameLiteral(baseCall);
|
||||
return literal != null ? literal : javaName;
|
||||
}
|
||||
|
||||
private static AstNode fieldNode(String naturalName, String javaName, String typeName,
|
||||
String sourceFile, int lineNo, @Nullable MethodCallExpr baseCall,
|
||||
@Nullable MethodCallExpr arrayCall, @Nullable MethodCallExpr constantCall,
|
||||
boolean isView, boolean isRedefine, String scope,
|
||||
Map<String, AstNode> byJavaName) {
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
if (!scope.isEmpty()) {
|
||||
properties.put("scope", scope);
|
||||
}
|
||||
if (!naturalName.equals(javaName)) {
|
||||
properties.put(JAVA_NAME, javaName);
|
||||
} else if (!isRedefine && nameLiteral(baseCall) == null) {
|
||||
properties.put(NATURAL_NAME_KNOWN, "false");
|
||||
}
|
||||
if (isRedefine) {
|
||||
properties.put(REDEFINES, naturalName);
|
||||
}
|
||||
|
||||
NodeType nodeType;
|
||||
@Nullable String dataType = null;
|
||||
@Nullable String value = null;
|
||||
if (isRedefine || isView || !NatJavTypeSpec.isFormat(typeName)) {
|
||||
// A Group, a Redefine and a view are all containers: no format, children nest under them.
|
||||
nodeType = NodeType.DATA_STRUCTURE;
|
||||
} else {
|
||||
dataType = NatJavTypeSpec.render(typeName, baseCall, arrayCall,
|
||||
name -> resolveBound(name, byJavaName));
|
||||
if (constantCall != null) {
|
||||
nodeType = NodeType.CONSTANT;
|
||||
value = constantValue(constantCall, typeName);
|
||||
} else {
|
||||
nodeType = NodeType.VARIABLE;
|
||||
}
|
||||
}
|
||||
return new AstNode(UUID.randomUUID(), nodeType, naturalName, sourceFile, NatJavParser.LANGUAGE,
|
||||
lineNo, lineNo, dataType, value, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the constant's value spelled as Natural's {@code CONST<...>} content — alphanumeric
|
||||
* values quoted ({@code 'GET'}), numeric ones bare ({@code 0}), an array's values comma-joined
|
||||
*/
|
||||
private static String constantValue(MethodCallExpr constantCall, String format) {
|
||||
List<String> values = new ArrayList<>();
|
||||
for (Expression argument : constantCall.getArguments()) {
|
||||
if (argument instanceof StringLiteralExpr literal) {
|
||||
values.add(format.equals("A") ? "'" + literal.asString() + "'" : literal.asString());
|
||||
} else if (argument instanceof MethodCallExpr call && call.getNameAsString().equals("val")
|
||||
&& call.getArguments().isNonEmpty()
|
||||
&& call.getArgument(0) instanceof StringLiteralExpr scaled) {
|
||||
// val("0.83") carries a scaled decimal the transpiler could not write as a Java literal.
|
||||
values.add(scaled.asString());
|
||||
} else {
|
||||
values.add(argument.toString().trim());
|
||||
}
|
||||
}
|
||||
return String.join(",", values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Natural name of the variable used as an array bound, or the Java name if unknown
|
||||
*/
|
||||
private static String resolveBound(String javaName, Map<String, AstNode> byJavaName) {
|
||||
AstNode bound = byJavaName.get(javaName);
|
||||
return bound != null ? bound.name() : javaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innermost call of the initializer chain — the format constructor {@code A(8, "X")},
|
||||
* {@code group("X")} or {@code view("X")} — or {@code null} for a declaration with no initializer
|
||||
*/
|
||||
private static @Nullable MethodCallExpr baseCall(VariableDeclarator variable) {
|
||||
Expression expression = variable.getInitializer().orElse(null);
|
||||
while (expression instanceof MethodCallExpr call) {
|
||||
Expression scope = call.getScope().orElse(null);
|
||||
if (scope == null) {
|
||||
return call;
|
||||
}
|
||||
expression = scope;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chained call named {@code method} ({@code defineArray}, {@code constant}) anywhere
|
||||
* in the initializer chain, or {@code null}
|
||||
*/
|
||||
private static @Nullable MethodCallExpr chainedCall(VariableDeclarator variable, String method) {
|
||||
Expression expression = variable.getInitializer().orElse(null);
|
||||
while (expression instanceof MethodCallExpr call) {
|
||||
if (call.getNameAsString().equals(method)) {
|
||||
return call;
|
||||
}
|
||||
expression = call.getScope().orElse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first string literal argument of {@code call} — the Natural name the transpiler
|
||||
* preserved — or {@code null} if the declaration states none
|
||||
*/
|
||||
private static @Nullable String nameLiteral(@Nullable MethodCallExpr call) {
|
||||
if (call == null) {
|
||||
return null;
|
||||
}
|
||||
for (Expression argument : call.getArguments()) {
|
||||
if (argument instanceof StringLiteralExpr literal) {
|
||||
String value = literal.asString().trim();
|
||||
return value.isEmpty() ? null : value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the level from an {@code @L1}..{@code @L7} annotation, or {@code null} if the field
|
||||
* carries none (a loop/database object, an event handler's helper — not data)
|
||||
*/
|
||||
private static @Nullable Integer levelOf(FieldDeclaration declaration) {
|
||||
for (AnnotationExpr annotation : declaration.getAnnotations()) {
|
||||
String name = annotation.getNameAsString();
|
||||
if (name.length() == 2 && name.charAt(0) == 'L' && Character.isDigit(name.charAt(1))) {
|
||||
return name.charAt(1) - '0';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String scopeOf(FieldDeclaration declaration) {
|
||||
for (AnnotationExpr annotation : declaration.getAnnotations()) {
|
||||
String scope = SCOPES.get(annotation.getNameAsString());
|
||||
if (scope != null) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasAnnotation(FieldDeclaration declaration, String name) {
|
||||
return declaration.getAnnotations().stream().anyMatch(a -> a.getNameAsString().equals(name));
|
||||
}
|
||||
|
||||
private static int lineOf(FieldDeclaration declaration) {
|
||||
return declaration.getBegin().map(p -> p.line).orElse(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* One entry of the level stack: the declared level and the node that contains what follows it.
|
||||
*/
|
||||
private record Level(int level, AstNode node) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parsercore.ast.spi.LineCounter;
|
||||
import com.agenticcode.parserjava.JavaLineCounter;
|
||||
|
||||
/**
|
||||
* LoC/SLoC counter for NatJav. The comment and literal syntax is Java's, so the counting itself is
|
||||
* delegated to {@link JavaLineCounter} rather than duplicated.
|
||||
*
|
||||
* <p>Delegation, deliberately not inheritance: {@link #language()} must answer {@code "natjav"},
|
||||
* and an inherited {@code language()} would report these files as {@code "java"} and silently merge
|
||||
* transpiled Natural into the hand-written-Java bucket of every per-language LoC rollup.
|
||||
*/
|
||||
public final class NatJavLineCounter implements LineCounter {
|
||||
|
||||
private final JavaLineCounter delegate = new JavaLineCounter();
|
||||
|
||||
@Override
|
||||
public String language() {
|
||||
return NatJavParser.LANGUAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocMetrics count(String content) {
|
||||
return delegate.count(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The Natural object type of a NatJav class, read off its {@code innowake.mee.natural.api} base
|
||||
* class. Measured over the 22,808-file reference corpus, every generated class extends exactly one
|
||||
* of these, so this is an <b>exact</b> classification — unlike the Natural parser, which can only
|
||||
* guess {@code PROGRAM} vs {@code SUBPROGRAM} from the presence of a {@code PARAMETER} section
|
||||
* (a flat {@code .nat} file carries no catalog metadata).
|
||||
*
|
||||
* <p>The {@link #nodeType()} mapping mirrors how the same object appears in a Natural ingest: a
|
||||
* data area is the {@code DATA_STRUCTURE} a {@code .lda}/{@code .pda}/{@code .gda} produces,
|
||||
* everything else is a {@code MODULE}.
|
||||
*/
|
||||
public enum NatJavModuleKind {
|
||||
|
||||
/**
|
||||
* {@code NaturalProgram} — a Natural main program (8,393 of the reference corpus).
|
||||
*/
|
||||
PROGRAM("NaturalProgram", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalSubprogram} — a {@code CALLNAT} target (5,859).
|
||||
*/
|
||||
SUBPROGRAM("NaturalSubprogram", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalData} — an LDA/PDA/GDA data area (5,818; always named {@code *_DA.java}).
|
||||
*/
|
||||
DATA_AREA("NaturalData", NodeType.DATA_STRUCTURE),
|
||||
/**
|
||||
* {@code NaturalMap} — a Natural map (1,543).
|
||||
*/
|
||||
MAP("NaturalMap", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalForm} — a report/print form (354).
|
||||
*/
|
||||
FORM("NaturalForm", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalSubroutine} — an external subroutine (325).
|
||||
*/
|
||||
SUBROUTINE("NaturalSubroutine", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalDdm} — a data definition module, i.e. the schema of an ADABAS file (243; always
|
||||
* named {@code *_DDM.java}). Kept as a {@code MODULE} in this phase: turning a DDM into the
|
||||
* {@code DB_TABLE} the {@code db-accesses} endpoint reports is a data-access concern and is
|
||||
* handled where views are resolved, not here.
|
||||
*/
|
||||
DDM("NaturalDdm", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalHelp} — a helproutine (142).
|
||||
*/
|
||||
HELP("NaturalHelp", NodeType.MODULE),
|
||||
/**
|
||||
* {@code NaturalFdt} — a field definition table (132).
|
||||
*/
|
||||
FDT("NaturalFdt", NodeType.MODULE);
|
||||
|
||||
private final String baseClass;
|
||||
private final NodeType nodeType;
|
||||
|
||||
NatJavModuleKind(String baseClass, NodeType nodeType) {
|
||||
this.baseClass = baseClass;
|
||||
this.nodeType = nodeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseClass the simple name of the class's {@code extends} target, e.g.
|
||||
* {@code NaturalProgram}
|
||||
* @return the matching kind, or {@code null} if {@code baseClass} is not a NatJav base type
|
||||
* (a hand-written Java class in the same tree, which is not a NatJav module at all)
|
||||
*/
|
||||
public static @Nullable NatJavModuleKind ofBaseClass(@Nullable String baseClass) {
|
||||
if (baseClass == null) {
|
||||
return null;
|
||||
}
|
||||
for (NatJavModuleKind kind : values()) {
|
||||
if (kind.baseClass.equals(baseClass)) {
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the simple name of the {@code innowake.mee.natural.api} base class this kind maps from
|
||||
*/
|
||||
public String baseClass() {
|
||||
return baseClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unified-AST node type this kind's file produces
|
||||
*/
|
||||
public NodeType nodeType() {
|
||||
return nodeType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser;
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.ParserConfiguration;
|
||||
import com.github.javaparser.ParserConfiguration.LanguageLevel;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Parses <b>NatJav</b> — Software AG Natural transpiled to Java by the innoWake maxenso
|
||||
* enterprise-engine — into the <em>same</em> unified AST the Natural parser produces, so a NatJav
|
||||
* project answers the identical API as its Natural original.
|
||||
*
|
||||
* <p>The syntax layer is JavaParser (NatJav is valid Java); this class supplies the semantic
|
||||
* mapping. Its advantage over the Natural parser is that the transpiler preserved the catalog
|
||||
* metadata a flat {@code .nat} file loses: the base class states the Natural object type exactly
|
||||
* ({@link NatJavModuleKind}) where {@code NaturalParser} can only guess PROGRAM vs SUBPROGRAM from
|
||||
* a {@code PARAMETER} section.
|
||||
*
|
||||
* <p><b>Emitted so far:</b> the file's {@code MODULE} (or {@code DATA_STRUCTURE} for a data area)
|
||||
* shell, its {@code DEFINE DATA} field tree ({@link NatJavFields}), its {@code USING} references
|
||||
* ({@link NatJavUsings}) and its call graph ({@link NatJavCalls}, with copycode attribution from
|
||||
* {@link NatJavCopycode}). DB access and dataflow follow in later phases; a view's columns come with
|
||||
* the DB access that reads them.
|
||||
*/
|
||||
public final class NatJavParser implements LanguageParser {
|
||||
|
||||
/**
|
||||
* The language identifier, distinct from {@code "java"} so a per-language rollup separates
|
||||
* transpiled Natural from hand-written Java, and from {@code "natural"} so a mixed estate can
|
||||
* tell an original from its migration.
|
||||
*/
|
||||
public static final String LANGUAGE = "natjav";
|
||||
|
||||
private static final ParserConfiguration PARSER_CONFIGURATION =
|
||||
new ParserConfiguration().setLanguageLevel(LanguageLevel.JAVA_21);
|
||||
|
||||
/**
|
||||
* @return the file's name without its extension — the transpiler names the file after the Java
|
||||
* class, so this is the class name, not necessarily the Natural name
|
||||
*/
|
||||
static String stem(String sourceFile) {
|
||||
String name = sourceFile;
|
||||
int slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
|
||||
if (slash >= 0) {
|
||||
name = name.substring(slash + 1);
|
||||
}
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot >= 0 ? name.substring(0, dot) : name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String language() {
|
||||
return LANGUAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseResult parse(String sourceFile, String content) {
|
||||
return parse(sourceFile, content, NatJavDdmResolver.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses with the project's DDM index, which a view needs to name its table and its columns: a
|
||||
* {@code @ViewOf} states only the DDM's class, and everything else about the ADABAS file lives in
|
||||
* that other source. Without one ({@link NatJavDdmResolver#NONE}) views are still emitted, just
|
||||
* without columns and without a table link.
|
||||
*/
|
||||
public ParseResult parse(String sourceFile, String content, NatJavDdmResolver ddms) {
|
||||
return parseWithShell(sourceFile, content, ddms).result();
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link #parse}, but also names the file's shell node. The Tier-1 scan keeps the shell and
|
||||
* drops the field tree, and must not have to <em>infer</em> which node the shell is: a data
|
||||
* area's groups are {@code DATA_STRUCTURE}s too, so a type-based filter would keep all of them.
|
||||
*/
|
||||
Parsed parseWithShell(String sourceFile, String content, NatJavDdmResolver ddms) {
|
||||
List<AstNode> nodes = new ArrayList<>();
|
||||
List<AstEdge> edges = new ArrayList<>();
|
||||
|
||||
com.github.javaparser.ParseResult<CompilationUnit> parsed =
|
||||
new com.github.javaparser.JavaParser(PARSER_CONFIGURATION).parse(content);
|
||||
CompilationUnit unit = parsed.getResult()
|
||||
.orElseThrow(() -> new ParseProblemException(parsed.getProblems()));
|
||||
|
||||
NatJavFacts facts = NatJavFacts.of(unit, stem(sourceFile));
|
||||
AstNode module = moduleNode(facts, sourceFile, LocMetrics.physicalLineCount(content));
|
||||
nodes.add(module);
|
||||
|
||||
TypeDeclaration<?> type = NatJavFacts.primaryType(unit, stem(sourceFile));
|
||||
if (type != null) {
|
||||
NatJavCopycode copycode = NatJavCopycode.of(content);
|
||||
NatJavTables tables = new NatJavTables(nodes);
|
||||
if (facts.kind() == NatJavModuleKind.DDM) {
|
||||
// A DDM describes an ADABAS file rather than declaring data: its constants are the
|
||||
// table's columns, and the table itself is a separate, file-less node.
|
||||
NatJavDdm.collect(type, module, facts.naturalName(), sourceFile, nodes, edges);
|
||||
} else {
|
||||
NatJavFields.Declared declared = NatJavFields.collect(type, module,
|
||||
facts.naturalName(), sourceFile, ddms, tables, nodes, edges);
|
||||
Map<String, AstNode> areas = NatJavUsings.collect(type, module, nodes, edges);
|
||||
Map<String, AstNode> subroutines =
|
||||
NatJavCalls.collect(type, module, sourceFile, copycode, nodes, edges);
|
||||
NatJavDbAccess.collect(type, module, sourceFile, declared.views(), tables, subroutines,
|
||||
copycode, ddms, nodes, edges);
|
||||
NatJavDataflow.collect(type, module, declared.byJavaName(),
|
||||
NatJavDbAccess.exposures(type), areas, subroutines, ddms, copycode, nodes, edges);
|
||||
NatJavControlFlow.collect(type, module, sourceFile, subroutines, copycode, nodes, edges);
|
||||
if (NatJavPayload.collect(type, module, sourceFile, nodes, edges)) {
|
||||
// Same marker the Natural parser sets, so the payload endpoint can find the
|
||||
// wrappers without walking every module's nodes. Replaced rather than mutated:
|
||||
// an AstNode is a record, and the copy keeps the id every edge already points at.
|
||||
nodes.set(nodes.indexOf(module), withProperty(module, "xmlWrapper", "true"));
|
||||
}
|
||||
}
|
||||
NatJavComments.collect(unit, sourceFile, nodes, edges);
|
||||
}
|
||||
return new Parsed(new ParseResult(nodes, edges), module);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a copy of {@code node} carrying one more property, with the same identity
|
||||
*/
|
||||
private static AstNode withProperty(AstNode node, String key, String value) {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
if (node.properties() != null) {
|
||||
properties.putAll(node.properties());
|
||||
}
|
||||
properties.put(key, value);
|
||||
return new AstNode(node.id(), node.type(), node.name(), node.sourceFile(), node.language(),
|
||||
node.startLine(), node.endLine(), node.dataType(), node.value(), properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* A parse result together with the {@code MODULE}/{@code DATA_STRUCTURE} node standing for the
|
||||
* file itself.
|
||||
*/
|
||||
record Parsed(ParseResult result, AstNode shell) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the file's shell node. The line range is the whole file, not the type declaration's
|
||||
* own range: in Natural a module <em>is</em> its source file, and the source/comment endpoints
|
||||
* rely on that equivalence.
|
||||
*/
|
||||
private static AstNode moduleNode(NatJavFacts facts, String sourceFile, int lineCount) {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(NatJavUsings.CLASS_NAME, facts.className());
|
||||
if (facts.packageName() != null) {
|
||||
properties.put("package", facts.packageName());
|
||||
}
|
||||
if (facts.kind() != null) {
|
||||
// Same property name and PROGRAM/SUBPROGRAM vocabulary as the Natural parser, extended
|
||||
// with the object types a .nat file cannot reveal (MAP, FORM, DDM, ...).
|
||||
properties.put("moduleKind", facts.kind().name());
|
||||
}
|
||||
if (facts.description() != null) {
|
||||
properties.put("description", facts.description());
|
||||
}
|
||||
if (facts.owner() != null) {
|
||||
properties.put("owner", facts.owner());
|
||||
}
|
||||
NodeType type = facts.kind() != null ? facts.kind().nodeType() : NodeType.MODULE;
|
||||
return new AstNode(UUID.randomUUID(), type, facts.naturalName(), sourceFile, LANGUAGE,
|
||||
1, Math.max(1, lineCount), null, null, properties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.FieldAccessExpr;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* The XML wire payload a module emits, as {@code PAYLOAD_FIELD} nodes — the same contract item 45
|
||||
* models for Natural, reached through a different idiom.
|
||||
*
|
||||
* <p>None of the Natural patterns exist in the NatJav corpus: {@code COMPRESS '<' tag '>' value},
|
||||
* {@code DEFINE SUBROUTINE ADD-XML-LINE} and {@code CALLNAT 'YFRAMN07'} occur zero times. This estate
|
||||
* builds its XML through a framework subprogram instead, filling a parameter area line by line:
|
||||
*
|
||||
* <pre>{@code
|
||||
* assign(XMLLINP.TYP, "TI");
|
||||
* assign(XMLLINP.TAG, "ADRZEILE1");
|
||||
* assign(XMLLINP.INHALT, YPADR2.ZEILE.get(1));
|
||||
* callnat("XMLLIN", XMLLINP, ...);
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Two emitters share that shape — {@code XMLLIN} ("Ausgabe XML-Zeile", 7,086 calls) and
|
||||
* {@code XMLDYN} ("… dynamische Länge", 1,818) — through their own parameter areas
|
||||
* {@code XMLLINP}/{@code XMLDYNP}.
|
||||
*
|
||||
* <p><b>The direction is measured, not assumed.</b> Natural derives {@code REQUEST}/{@code RESPONSE}
|
||||
* from {@code ADD-XML-LINE} vs {@code GET-XML-LINE}. Here the read counterpart {@code XMLREAD}
|
||||
* ("Lesen 'abgeflachter' XML-Daten") exists as a program and carries the mirror-image
|
||||
* {@code #TAG}/{@code #INHALT} outputs, but is called <b>zero</b> times in the corpus — so every one
|
||||
* of the 10,238 tags is an emit, and {@code REQUEST} is a fact rather than a default.
|
||||
*/
|
||||
final class NatJavPayload {
|
||||
|
||||
/**
|
||||
* The parameter areas the XML framework's emitters are driven through. Keyed by the Java field
|
||||
* name a module exposes them under, which is also how the assignments address them.
|
||||
*/
|
||||
private static final Set<String> EMIT_AREAS = Set.of("XMLLINP", "XMLDYNP");
|
||||
|
||||
/**
|
||||
* Field of the emit area holding the tag name.
|
||||
*/
|
||||
private static final String TAG = "TAG";
|
||||
|
||||
/**
|
||||
* Fields holding the tag's content. {@code XMLDYNP} offers a fixed-length variant alongside the
|
||||
* dynamic one.
|
||||
*/
|
||||
private static final Set<String> CONTENT = Set.of("INHALT", "INHALT_200");
|
||||
|
||||
/**
|
||||
* Field holding the line type ({@code TI} text item, {@code ST}/{@code ET} element start/end, …).
|
||||
*/
|
||||
private static final String TYPE = "TYP";
|
||||
|
||||
/**
|
||||
* Every tag in the corpus is written, never read — see the class comment.
|
||||
*/
|
||||
private static final String DIRECTION = "REQUEST";
|
||||
|
||||
private NatJavPayload() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the module's payload fields, and marks the module as an XML wrapper when it has any.
|
||||
*
|
||||
* @return whether this module emits XML at all
|
||||
*/
|
||||
static boolean collect(TypeDeclaration<?> type, AstNode module, String sourceFile,
|
||||
List<AstNode> nodes, List<AstEdge> edges) {
|
||||
boolean emits = false;
|
||||
for (MethodDeclaration method : type.getMethods()) {
|
||||
// Statement order carries the meaning: a TAG assignment is followed by the content
|
||||
// assignment for that tag. Walking the calls in source order is what pairs them.
|
||||
@Nullable String tag = null;
|
||||
@Nullable String lineType = null;
|
||||
int tagLine = 1;
|
||||
for (MethodCallExpr call : method.findAll(MethodCallExpr.class)) {
|
||||
if (!call.getNameAsString().equals("assign") || call.getScope().isPresent()
|
||||
|| call.getArguments().size() < 2) {
|
||||
continue;
|
||||
}
|
||||
Target target = targetOf(call.getArgument(0));
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
if (target.field.equals(TYPE)) {
|
||||
lineType = literalOf(call.getArgument(1));
|
||||
} else if (target.field.equals(TAG)) {
|
||||
tag = literalOf(call.getArgument(1));
|
||||
tagLine = call.getBegin().map(p -> p.line).orElse(1);
|
||||
} else if (CONTENT.contains(target.field) && tag != null && !tag.isEmpty()) {
|
||||
AstNode field = payload(tag, call.getArgument(1), lineType, sourceFile, tagLine);
|
||||
if (field != null) {
|
||||
nodes.add(field);
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.CONTAINS, module.id(),
|
||||
field.id(), tagLine, null));
|
||||
emits = true;
|
||||
}
|
||||
// One content assignment closes one tag; a following one belongs to the next.
|
||||
tag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return emits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the payload field, or {@code null} when this line carries no content at all — a
|
||||
* structural start/end tag, which the framework writes by assigning a blank. 1,200 of the
|
||||
* corpus's 1,493 literal contents are that blank, and each would otherwise become a payload
|
||||
* field whose "field" is a space.
|
||||
*/
|
||||
private static @Nullable AstNode payload(String tag, Expression content, @Nullable String lineType,
|
||||
String sourceFile, int lineNo) {
|
||||
@Nullable String literal = literalOf(content);
|
||||
if (literal != null && literal.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
properties.put("tag", tag);
|
||||
properties.put("field", literal != null ? literal : content.toString().trim());
|
||||
properties.put("direction", DIRECTION);
|
||||
if (literal != null) {
|
||||
// A constant reaching the wire, not a field of the module's data. Marked rather than
|
||||
// dropped: it is still part of the payload contract. The Natural side never sees these —
|
||||
// its COMPRESS idiom matches an identifier — so this is a small, deliberate superset.
|
||||
properties.put("constant", "true");
|
||||
}
|
||||
if (lineType != null) {
|
||||
properties.put("lineType", lineType);
|
||||
}
|
||||
return new AstNode(UUID.randomUUID(), NodeType.PAYLOAD_FIELD, tag, sourceFile,
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, null, null, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the emit area and field an assignment targets ({@code XMLLINP.TAG}), or {@code null}
|
||||
* when the assignment has nothing to do with the XML framework
|
||||
*/
|
||||
private static @Nullable Target targetOf(Expression operand) {
|
||||
if (!(operand instanceof FieldAccessExpr access)) {
|
||||
return null;
|
||||
}
|
||||
String area = access.getScope().toString();
|
||||
int dot = area.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
area = area.substring(dot + 1);
|
||||
}
|
||||
return EMIT_AREAS.contains(area) ? new Target(area, access.getNameAsString()) : null;
|
||||
}
|
||||
|
||||
private static @Nullable String literalOf(Expression expression) {
|
||||
return expression instanceof StringLiteralExpr literal ? literal.asString().trim() : null;
|
||||
}
|
||||
|
||||
private record Target(String area, String field) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* The {@code DB_TABLE} nodes of one parse, deduped by table name.
|
||||
*
|
||||
* <p>A {@code DB_TABLE} deliberately carries <b>no source file</b>. That is not an omission but the
|
||||
* mechanism: the node merge key is {@code (type, name, sourceFile, project)}, so an empty
|
||||
* {@code sourceFile} makes every module that touches a table converge on one node for it. Both the
|
||||
* Natural and the Java parser build them that way, and all 72 tables of the {@code ac} project are
|
||||
* file-less — a table with a file would sit <em>beside</em> the shared node instead of being it.
|
||||
*/
|
||||
final class NatJavTables {
|
||||
|
||||
private final Map<String, AstNode> byName = new HashMap<>();
|
||||
private final List<AstNode> nodes;
|
||||
|
||||
NatJavTables(List<AstNode> nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tableName the table's Natural name
|
||||
* @param lineNo the line first referencing it, for the node's position
|
||||
* @return the shared node for {@code tableName}, creating it on first reference
|
||||
*/
|
||||
AstNode get(String tableName, int lineNo) {
|
||||
return byName.computeIfAbsent(tableName, name -> {
|
||||
AstNode table = new AstNode(UUID.randomUUID(), NodeType.DB_TABLE, name, "",
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, null, null, null);
|
||||
nodes.add(table);
|
||||
return table;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import com.github.javaparser.ast.expr.StringLiteralExpr;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Renders a NatJav field declaration's type into the <b>Natural</b> {@code dataType} spelling the
|
||||
* {@link com.agenticcode.parsercore.ast.model.AstNode#dataType() unified AST} already uses for
|
||||
* Natural sources — {@code A6}, {@code N15,3}, {@code A8/1:10} — so a query that filters on a data
|
||||
* type finds a module and its migration alike.
|
||||
*
|
||||
* <p>Two conversions matter and are easy to get wrong:
|
||||
* <ul>
|
||||
* <li><b>A zero scale is dropped.</b> The transpiler always writes both digits ({@code N(3,0)}),
|
||||
* Natural writes only the significant ones ({@code N3}). Keeping the {@code ,0} would give
|
||||
* every integer field in the corpus a {@code dataType} its Natural original never had.</li>
|
||||
* <li><b>Array bounds are appended with {@code /}</b>, not kept as a call: {@code A(200,"DATA")
|
||||
* .defineArray(range(1, 9999))} is Natural's {@code (A200/1:9999)} → {@code A200/1:9999}.</li>
|
||||
* </ul>
|
||||
*/
|
||||
final class NatJavTypeSpec {
|
||||
|
||||
/**
|
||||
* The Natural format letters the transpiler emits as constructor names: alphanumeric, numeric,
|
||||
* packed, binary, logical, date, time and control. A declared field type outside this set is not
|
||||
* a Natural format (a {@code Group}, a {@code Redefine} marker, a view's DDM class).
|
||||
*/
|
||||
private static final List<String> FORMATS = List.of("A", "N", "P", "B", "L", "D", "T", "C");
|
||||
|
||||
private NatJavTypeSpec() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether {@code typeName} is a Natural format letter rather than a structural marker
|
||||
*/
|
||||
static boolean isFormat(String typeName) {
|
||||
return FORMATS.contains(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders {@code A(8)}/{@code N(3,0)}/{@code A()} plus any {@code defineArray(...)} into the
|
||||
* Natural spelling.
|
||||
*
|
||||
* @param format the format letter, e.g. {@code A}
|
||||
* @param baseCall the format constructor call, or {@code null} for a declaration with no
|
||||
* initializer (a view column, whose length lives in the DDM)
|
||||
* @param arrayCall the {@code defineArray(...)} call, or {@code null} if the field is scalar
|
||||
* @param resolveName maps a Java identifier used as an array bound to its Natural name
|
||||
* @return e.g. {@code A8}, {@code N15,3}, {@code A200/1:9999}, or bare {@code A} for a dynamic
|
||||
* or DDM-typed field
|
||||
*/
|
||||
static String render(String format, @Nullable MethodCallExpr baseCall,
|
||||
@Nullable MethodCallExpr arrayCall, UnaryOperator<String> resolveName) {
|
||||
StringBuilder spec = new StringBuilder(format);
|
||||
if (baseCall != null) {
|
||||
spec.append(lengthOf(baseCall));
|
||||
}
|
||||
if (arrayCall != null) {
|
||||
String dims = dimensionsOf(arrayCall, resolveName);
|
||||
if (!dims.isEmpty()) {
|
||||
spec.append('/').append(dims);
|
||||
}
|
||||
}
|
||||
return spec.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length/precision part of a format call: {@code ""} for {@code A()} (Natural
|
||||
* {@code DYNAMIC}), {@code "8"} for {@code A(8)}, {@code "15,3"} for {@code N(15,3)} and
|
||||
* {@code "3"} — not {@code "3,0"} — for {@code N(3,0)}
|
||||
*/
|
||||
private static String lengthOf(MethodCallExpr call) {
|
||||
List<String> numbers = new ArrayList<>();
|
||||
for (Expression argument : call.getArguments()) {
|
||||
if (argument instanceof StringLiteralExpr) {
|
||||
break; // the Natural-name literal ends the numeric part
|
||||
}
|
||||
numbers.add(argument.toString().trim());
|
||||
}
|
||||
if (numbers.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
if (numbers.size() >= 2 && "0".equals(numbers.get(1))) {
|
||||
return numbers.get(0);
|
||||
}
|
||||
return String.join(",", numbers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the array bounds of {@code defineArray(range(a, b), ...)} as {@code a:b,c:d}. An
|
||||
* unbounded {@code range(1, $)} becomes Natural's variable bound {@code 1:V}; a bound given as a
|
||||
* variable is rendered under that variable's Natural name.
|
||||
*/
|
||||
private static String dimensionsOf(MethodCallExpr arrayCall, UnaryOperator<String> resolveName) {
|
||||
List<String> dimensions = new ArrayList<>();
|
||||
for (Expression argument : arrayCall.getArguments()) {
|
||||
if (!(argument instanceof MethodCallExpr range) || !range.getNameAsString().equals("range")
|
||||
|| range.getArguments().size() != 2) {
|
||||
continue;
|
||||
}
|
||||
dimensions.add(bound(range.getArgument(0), resolveName) + ":" + bound(range.getArgument(1), resolveName));
|
||||
}
|
||||
return String.join(",", dimensions);
|
||||
}
|
||||
|
||||
private static String bound(Expression expression, UnaryOperator<String> resolveName) {
|
||||
String text = expression.toString().trim();
|
||||
if (text.equals("$")) {
|
||||
return "V";
|
||||
}
|
||||
return text.matches("\\d+") ? text : resolveName.apply(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.FieldDeclaration;
|
||||
import com.github.javaparser.ast.body.TypeDeclaration;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Emits the {@code INCLUDES} edges of a NatJav module's {@code @Using} declarations — the transpiled
|
||||
* form of Natural's {@code LOCAL/PARAMETER/GLOBAL USING <data-area>} (72,145 of them in the
|
||||
* reference corpus).
|
||||
*
|
||||
* <p>The target is an unresolved placeholder, exactly as in the Natural parser, but keyed
|
||||
* <b>differently</b>: a {@code @Using} names the <em>Java class</em> ({@code WSAIVP_DA}) while the
|
||||
* data area's node carries its <em>Natural</em> name ({@code WSAIVP}), and the two cannot be bridged
|
||||
* by a string rule — stripping the {@code _DA} suffix produces the right Natural name for only
|
||||
* 90.46 % of the corpus's 5,818 data areas (it misses {@code V_MIT03P_DA} → {@code V-MIT03P}, whose
|
||||
* remaining underscores are ambiguous). The placeholder therefore carries the class name in
|
||||
* {@link #CLASS_NAME} and is redirected after ingest by matching that against the real node's own
|
||||
* {@code className}, which is exact.
|
||||
*/
|
||||
final class NatJavUsings {
|
||||
|
||||
/**
|
||||
* Property holding the referenced (or declaring) Java class's simple name — the key the
|
||||
* post-ingest NatJav placeholder resolution joins on.
|
||||
*/
|
||||
static final String CLASS_NAME = "className";
|
||||
|
||||
/**
|
||||
* Property on the {@code INCLUDES} edge naming the {@code DEFINE DATA} section the area belongs
|
||||
* to: {@code PARAMETER}, {@code LOCAL}, {@code GLOBAL} or {@code INDEPENDENT}.
|
||||
*/
|
||||
/**
|
||||
* The Java package the {@code @Using} takes its area from — the qualifier of the declared type,
|
||||
* or, for an unqualified type, the including file's own package (Java's same-package rule;
|
||||
* measured 1,193 of 1,193 unqualified {@code @Using} in the corpus name a class in the same
|
||||
* directory, none an import). Item 184: this is what tells {@code COMMON_DA} in {@code KORIN}
|
||||
* from {@code COMMON_DA} in {@code ABFRAGE}, which the class name alone cannot. The real area
|
||||
* carries the same string as {@code package}, so the resolution is a plain equality. Absent when
|
||||
* one module includes the same class name from two libraries — then no single library is right
|
||||
* and the placeholder is left for the resolution to refuse, as it does today.
|
||||
*/
|
||||
static final String USING_PACKAGE = "usingPackage";
|
||||
|
||||
static final String SCOPE = "scope";
|
||||
|
||||
/**
|
||||
* Property on a {@code PARAMETER} {@code INCLUDES} edge: the area's 0-based position in the
|
||||
* subprogram's argument list.
|
||||
*/
|
||||
static final String PARAM_POSITION = "paramPosition";
|
||||
|
||||
private static final String PARAMETER = "PARAMETER";
|
||||
|
||||
private NatJavUsings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends one placeholder plus {@code INCLUDES} edge per distinct {@code @Using} declaration.
|
||||
*
|
||||
* @param type the module's primary type
|
||||
* @param module the file's shell node, the source of every {@code INCLUDES} edge
|
||||
* @param nodes collects the placeholder nodes
|
||||
* @param edges collects the {@code INCLUDES} edges
|
||||
* @return the placeholders by data-area class name, so a dataflow operand naming a field of one
|
||||
* can be hung underneath it
|
||||
*/
|
||||
static Map<String, AstNode> collect(TypeDeclaration<?> type, AstNode module, List<AstNode> nodes, List<AstEdge> edges) {
|
||||
// First pass: which library each area comes from, before any node exists — a node's
|
||||
// properties are fixed at construction, and the second @Using of the same class name may
|
||||
// contradict the first.
|
||||
String ownPackage = type.findCompilationUnit()
|
||||
.flatMap(CompilationUnit::getPackageDeclaration)
|
||||
.map(pd -> pd.getNameAsString())
|
||||
.orElse(null);
|
||||
Map<String, String> packages = new HashMap<>();
|
||||
Set<String> ambiguous = new HashSet<>();
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
if (!isUsingDeclaration(declaration)) {
|
||||
continue;
|
||||
}
|
||||
String typeName = declaration.getVariable(0).getTypeAsString();
|
||||
String className = simpleName(typeName);
|
||||
String pkg = packageOf(typeName, ownPackage);
|
||||
if (className.isEmpty() || pkg == null) {
|
||||
continue;
|
||||
}
|
||||
String previous = packages.putIfAbsent(className, pkg);
|
||||
if (previous != null && !previous.equals(pkg)) {
|
||||
ambiguous.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, AstNode> placeholders = new HashMap<>();
|
||||
int parameterSlot = 0;
|
||||
for (FieldDeclaration declaration : type.getFields()) {
|
||||
if (!isUsingDeclaration(declaration)) {
|
||||
continue;
|
||||
}
|
||||
String className = simpleName(declaration.getVariable(0).getTypeAsString());
|
||||
if (className.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int lineNo = declaration.getBegin().map(p -> p.line).orElse(1);
|
||||
// Deduped by class name: a repeated @Using must not create duplicate nodes that then
|
||||
// collide on the (type, name, sourceFile, project) merge key. The key stays the simple
|
||||
// class name because NatJavDbAccess.exposures() looks areas up by it.
|
||||
AstNode placeholder = placeholders.computeIfAbsent(className, name -> {
|
||||
Map<String, String> nodeProperties = new LinkedHashMap<>();
|
||||
nodeProperties.put(CLASS_NAME, name);
|
||||
String pkg = packages.get(name);
|
||||
if (pkg != null && !ambiguous.contains(name)) {
|
||||
nodeProperties.put(USING_PACKAGE, pkg);
|
||||
}
|
||||
AstNode node = new AstNode(UUID.randomUUID(), NodeType.DATA_STRUCTURE, name, "",
|
||||
NatJavParser.LANGUAGE, lineNo, lineNo, null, null, nodeProperties);
|
||||
nodes.add(node);
|
||||
return node;
|
||||
});
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
String scope = scopeOf(declaration);
|
||||
properties.put(SCOPE, scope);
|
||||
if (PARAMETER.equals(scope)) {
|
||||
// The position this area occupies in the subprogram's CALLNAT argument list. On the
|
||||
// EDGE, never on the node: a data area is shared, and the very same one is parameter 0
|
||||
// of one subprogram and parameter 2 of another — stamping the node would let whichever
|
||||
// module was ingested last decide the answer for all of them.
|
||||
properties.put(PARAM_POSITION, Integer.toString(parameterSlot++));
|
||||
}
|
||||
edges.add(new AstEdge(UUID.randomUUID(), EdgeType.INCLUDES, module.id(), placeholder.id(),
|
||||
lineNo, null, properties));
|
||||
}
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code DEFINE DATA} section this {@code @Using} stands for, read off the factory
|
||||
* the transpiler called: {@code parameterUsing} is {@code PARAMETER USING}, {@code localUsing} is
|
||||
* {@code LOCAL USING}, {@code globalUsing} is {@code GLOBAL USING}. The corpus holds 13,696 /
|
||||
* 59,895 / 1,698 of them, and without this the three are indistinguishable — a subprogram's
|
||||
* interface would look exactly like its scratch data.
|
||||
*/
|
||||
private static String scopeOf(FieldDeclaration declaration) {
|
||||
Expression initializer = declaration.getVariable(0).getInitializer().orElse(null);
|
||||
if (initializer instanceof MethodCallExpr call) {
|
||||
String factory = call.getNameAsString();
|
||||
if (factory.equals("parameterUsing")) {
|
||||
return PARAMETER;
|
||||
}
|
||||
if (factory.equals("globalUsing")) {
|
||||
return "GLOBAL";
|
||||
}
|
||||
if (factory.equals("independentUsing")) {
|
||||
return "INDEPENDENT";
|
||||
}
|
||||
}
|
||||
return "LOCAL";
|
||||
}
|
||||
|
||||
private static boolean isUsing(AnnotationExpr annotation) {
|
||||
return annotation.getNameAsString().equals("Using");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last segment of a possibly fully-qualified type name, without generic arguments
|
||||
*/
|
||||
private static boolean isUsingDeclaration(FieldDeclaration declaration) {
|
||||
return declaration.getAnnotations().stream().anyMatch(NatJavUsings::isUsing)
|
||||
&& !declaration.getVariables().isEmpty();
|
||||
}
|
||||
|
||||
private static String withoutGenerics(String typeName) {
|
||||
String name = typeName.trim();
|
||||
int generics = name.indexOf('<');
|
||||
return generics >= 0 ? name.substring(0, generics) : name;
|
||||
}
|
||||
|
||||
private static String simpleName(String typeName) {
|
||||
String name = withoutGenerics(typeName);
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot >= 0 ? name.substring(dot + 1) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the package a {@code @Using}'s declared type lives in: its qualifier, or for an
|
||||
* unqualified type the including file's own package ({@code null} in the default package)
|
||||
*/
|
||||
private static @Nullable String packageOf(String typeName, @Nullable String ownPackage) {
|
||||
String name = withoutGenerics(typeName);
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot >= 0 ? name.substring(0, dot) : ownPackage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Parser for <b>NatJav</b> — Software AG Natural transpiled to Java by the innoWake maxenso
|
||||
* enterprise-engine. NatJav is syntactically Java but semantically Natural: a module is a class
|
||||
* annotated {@code @Natural} extending one of the {@code innowake.mee.natural.api} base types, a
|
||||
* data area is a {@code NaturalData} subclass, and Natural statements survive as API calls
|
||||
* ({@code perform}, {@code callnat}, {@code read}, {@code store}, ...).
|
||||
*
|
||||
* <p>The parser therefore maps onto the <em>same</em> unified AST as
|
||||
* {@code com.agenticcode.parsernatural}, so a NatJav project answers the identical API as its
|
||||
* Natural original.
|
||||
*/
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package com.agenticcode.parsernatjav;
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.CopycodeProperties;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* The call graph. {@code CDDCINP.java} is an unmodified corpus file that happens to hold every shape
|
||||
* at once: three subroutines, calls from {@code run()} and from a subroutine, and two copycode
|
||||
* expansions, one of which contains a call.
|
||||
*/
|
||||
class NatJavCallsTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ParseResult parse(String name) {
|
||||
return parser.parse("fixtures/natjav/" + name, fixture(name));
|
||||
}
|
||||
|
||||
private static AstNode node(ParseResult result, NodeType type, String name) {
|
||||
return result.nodes().stream()
|
||||
.filter(n -> n.type() == type && n.name().equals(name))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("no " + type + " named '" + name + "'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code CALLS} edge from {@code source} to the node named {@code target}
|
||||
*/
|
||||
private static AstEdge call(ParseResult result, AstNode source, String target) {
|
||||
return result.edges().stream()
|
||||
.filter(e -> e.type() == EdgeType.CALLS && e.sourceId().equals(source.id())
|
||||
&& result.nodes().stream().anyMatch(n -> n.id().equals(e.targetId())
|
||||
&& n.name().equals(target)))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("no CALLS " + source.name() + " -> " + target));
|
||||
}
|
||||
|
||||
private static @Nullable String prop(AstEdge edge, String key) {
|
||||
Map<String, String> properties = edge.properties();
|
||||
assertNotNull(properties, "edge has no properties");
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
void subroutinesBecomeFunctions() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode module = node(result, NodeType.MODULE, "CDDCINP");
|
||||
|
||||
for (String name : List.of("CAPTURE_RESTORE_ENVIRONMENT", "FINAL_PROCESSING", "SET_USER_MSG")) {
|
||||
AstNode function = node(result, NodeType.FUNCTION, name);
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(module.id()) && e.targetId().equals(function.id())),
|
||||
name + " must be contained by its module");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code run()} is the module body, not a subroutine — as in Natural, where only
|
||||
* {@code DEFINE SUBROUTINE} makes a function. A {@code FUNCTION} here would add a module hop to
|
||||
* every call the program makes.
|
||||
*/
|
||||
@Test
|
||||
void runIsNotAFunction() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
|
||||
assertTrue(result.nodes().stream()
|
||||
.noneMatch(n -> n.type() == NodeType.FUNCTION && n.name().equals("run")),
|
||||
"run() is the module body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void performFromRunIsAttributedToTheModule() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode module = node(result, NodeType.MODULE, "CDDCINP");
|
||||
AstNode target = node(result, NodeType.FUNCTION, "CAPTURE_RESTORE_ENVIRONMENT");
|
||||
|
||||
AstEdge edge = call(result, module, "CAPTURE_RESTORE_ENVIRONMENT");
|
||||
assertEquals(target.id(), edge.targetId(), "a local PERFORM resolves inside the file");
|
||||
assertEquals("PERFORM", prop(edge, "callKind"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code CALLNAT} names another module, which lives in another file — an unresolved placeholder
|
||||
* keyed on the class name, because the target module's node carries its Natural name.
|
||||
*/
|
||||
@Test
|
||||
void callnatBecomesAPlaceholderKeyedOnTheClassName() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode module = node(result, NodeType.MODULE, "CDDCINP");
|
||||
AstNode target = node(result, NodeType.MODULE, "CDUTRANS");
|
||||
|
||||
assertEquals("", target.sourceFile());
|
||||
assertNotNull(target.properties());
|
||||
assertEquals("CDUTRANS", target.properties().get(NatJavUsings.CLASS_NAME));
|
||||
assertEquals("CALLNAT", prop(call(result, module, "CDUTRANS"), "callKind"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A call inside a subroutine belongs to that subroutine, not to the module — otherwise every
|
||||
* caller list would report the program where it should report the routine.
|
||||
*/
|
||||
@Test
|
||||
void callInsideASubroutineIsAttributedToIt() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode subroutine = node(result, NodeType.FUNCTION, "SET_USER_MSG");
|
||||
|
||||
assertEquals("CALLNAT", prop(call(result, subroutine, "CDUMSG"), "callKind"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The transpiler inlined the copycode, so the call physically sits in this file — but it came
|
||||
* from {@code CCENVIR}, and a caller list that says otherwise misattributes 23.3 % of the
|
||||
* corpus's calls.
|
||||
*/
|
||||
@Test
|
||||
void callInsideACopycodeExpansionCarriesItsOrigin() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode subroutine = node(result, NodeType.FUNCTION, "CAPTURE_RESTORE_ENVIRONMENT");
|
||||
|
||||
AstEdge edge = call(result, subroutine, "CDENVIR");
|
||||
assertEquals("CCENVIR", prop(edge, CopycodeProperties.VIA_COPYCODE));
|
||||
assertNotNull(prop(edge, CopycodeProperties.INCLUDED_AT), "the host's expansion line");
|
||||
assertTrue(Integer.parseInt(requireProp(edge, CopycodeProperties.INCLUDED_AT)) < edge.lineNo(),
|
||||
"the expansion starts before the call it contains");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CopycodeProperties#INCLUDE_PATH} identifies one expansion site among the several that
|
||||
* share a Natural copycode node. NatJav has no shared node — every node lives in the host file —
|
||||
* so writing it would move nodes onto the copycode-resident merge key for no reason.
|
||||
*/
|
||||
@Test
|
||||
void includePathIsNeverWritten() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
|
||||
assertTrue(result.edges().stream().allMatch(e -> e.properties() == null
|
||||
|| !e.properties().containsKey(CopycodeProperties.INCLUDE_PATH)),
|
||||
"no edge carries includePath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void callOutsideAnExpansionCarriesNoOrigin() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
AstNode module = node(result, NodeType.MODULE, "CDDCINP");
|
||||
|
||||
AstEdge edge = call(result, module, "CDUTRANS");
|
||||
assertNull(prop(edge, CopycodeProperties.VIA_COPYCODE),
|
||||
"this call is the program's own code");
|
||||
}
|
||||
|
||||
/**
|
||||
* The trailing comment on a subroutine looks like the Natural name and is not one — {@code
|
||||
* FINAL_PROCESSING} is followed by "Required by CCDCOUT". The node keeps the Java method name,
|
||||
* which is also what {@code perform} names.
|
||||
*/
|
||||
@Test
|
||||
void subroutineKeepsTheJavaMethodName() {
|
||||
ParseResult result = parse("CDDCINP.java");
|
||||
|
||||
assertEquals("FINAL_PROCESSING", node(result, NodeType.FUNCTION, "FINAL_PROCESSING").name());
|
||||
}
|
||||
|
||||
/**
|
||||
* A file with no subroutine at all still yields its calls, attributed to the module.
|
||||
*/
|
||||
@Test
|
||||
void moduleWithoutSubroutinesStillEmitsCalls() {
|
||||
ParseResult result = parse("ACOMMON.java");
|
||||
AstNode module = node(result, NodeType.MODULE, "ACOMMON");
|
||||
|
||||
assertEquals("CALLNAT", prop(call(result, module, "WSAIV"), "callKind"));
|
||||
}
|
||||
|
||||
private static String requireProp(AstEdge edge, String key) {
|
||||
return Optional.ofNullable(prop(edge, key)).orElseThrow(() -> new AssertionError("no " + key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.model.SourceHash;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class NatJavCoarseScannerTest {
|
||||
|
||||
private final NatJavCoarseScanner scanner = new NatJavCoarseScanner();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String prop(AstNode node, String key) {
|
||||
Map<String, String> properties = node.properties();
|
||||
assertNotNull(properties, "shell " + node.name() + " has no properties");
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
void languageIsNatjav() {
|
||||
assertEquals("natjav", scanner.language());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shellCarriesSourceHashAndLineMetrics() {
|
||||
String content = fixture("MDBT74B.java");
|
||||
ParseResult result = scanner.scan("fixtures/natjav/MDBT74B.java", content);
|
||||
|
||||
assertEquals(1, result.nodes().size());
|
||||
AstNode shell = result.nodes().get(0);
|
||||
assertEquals(NodeType.MODULE, shell.type());
|
||||
assertEquals(SourceHash.of(content), prop(shell, SourceHash.PROPERTY));
|
||||
assertEquals(Integer.toString(LocMetrics.physicalLineCount(content)),
|
||||
prop(shell, LocMetrics.LOC));
|
||||
assertNotNull(prop(shell, LocMetrics.SLOC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 41 relies on the digest being a pure function of the file text: an unchanged file must
|
||||
* reproduce it, or every refresh looks like a change.
|
||||
*/
|
||||
@Test
|
||||
void sourceHashIsStableAcrossScans() {
|
||||
String content = fixture("V_MIT04.java");
|
||||
|
||||
assertEquals(prop(scanner.scan("a/V_MIT04.java", content).nodes().get(0), SourceHash.PROPERTY),
|
||||
prop(scanner.scan("a/V_MIT04.java", content).nodes().get(0), SourceHash.PROPERTY));
|
||||
}
|
||||
|
||||
/**
|
||||
* The coarse scan must not lose the identity the parser established — a Tier-1 project would
|
||||
* otherwise key its modules differently from a Tier-2 one.
|
||||
*/
|
||||
@Test
|
||||
void coarseScanKeepsTheNaturalNameAndKind() {
|
||||
AstNode shell = scanner.scan("fixtures/natjav/V_MIT04.java", fixture("V_MIT04.java")).nodes().get(0);
|
||||
|
||||
assertEquals("V-MIT04", shell.name());
|
||||
assertEquals("SUBPROGRAM", prop(shell, "moduleKind"));
|
||||
assertEquals("V_MIT04", prop(shell, "className"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataAreaShellIsStampedToo() {
|
||||
AstNode shell = scanner.scan("fixtures/natjav/YPUMPRUE_DA.java", fixture("YPUMPRUE_DA.java")).nodes().get(0);
|
||||
|
||||
assertEquals(NodeType.DATA_STRUCTURE, shell.type());
|
||||
assertEquals("YPUMPRUE", shell.name());
|
||||
assertNotNull(prop(shell, SourceHash.PROPERTY));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Comment blocks. The grouping is the Java parser's; what is tested here is the one thing that
|
||||
* differs — separating a Natural developer's comments from the transpiler's bookkeeping.
|
||||
*/
|
||||
class NatJavCommentsTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<AstNode> comments(ParseResult result) {
|
||||
return result.nodes().stream().filter(n -> n.type() == NodeType.COMMENT).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* The migration header looks like pure framing and is not: 10,466 of the corpus's 14,749 files
|
||||
* carry real prose inside it, often a fuller description than the single {@code @FUNCTION} line.
|
||||
* Dropping the block would delete the documentation of all of them.
|
||||
*/
|
||||
@Test
|
||||
void headerFramingGoesButItsProseStays() {
|
||||
String retained = NatJavComments.retained("""
|
||||
<pre>
|
||||
*LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* header created during migration process *
|
||||
**************************************************************
|
||||
* @FUNCTION LOESCHEN UMKONTIERUNGEN
|
||||
* @OWNER P36
|
||||
**************************************************************
|
||||
*
|
||||
* Löschen Umkontierungen
|
||||
*
|
||||
</pre>
|
||||
""");
|
||||
|
||||
assertEquals("Löschen Umkontierungen", retained);
|
||||
}
|
||||
|
||||
/**
|
||||
* The expansion markers carry nothing a reader wants — their content is already a call's
|
||||
* {@code viaCopycode} — so they are dropped whole.
|
||||
*/
|
||||
@Test
|
||||
void transpilerBookkeepingIsDroppedWhole() {
|
||||
assertEquals("", NatJavComments.retained("@@INCLUDE:BEGIN(YCISN)"));
|
||||
assertEquals("", NatJavComments.retained("@@USING:END(KORIN.Diverse.WSAIVP_DA)"));
|
||||
assertEquals("", NatJavComments.retained("Generated by innoWake maxenso enterprise-engine."));
|
||||
assertEquals("", NatJavComments.retained("additional loop and database objects"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Two gaps the first corpus run exposed, both pure framing that reached the graph: the generator
|
||||
* stamps per-line metadata into the header, and the corpus spells "migration" two ways —
|
||||
* 57,484 times correctly and 1,347 times as "migraion".
|
||||
*/
|
||||
@Test
|
||||
void generatorStampsAndTheMisspelledBoilerplateAreDroppedToo() {
|
||||
assertEquals("", NatJavComments.retained("""
|
||||
*innoWake versionRep2Str: 1.4.0211
|
||||
*innoWake datumUmsetzung: 2014-02-11
|
||||
*innoWake originalObjectName: MDBT74B
|
||||
"""));
|
||||
assertEquals("", NatJavComments.retained("header created during migraion process"));
|
||||
// The label is not one word, and the corpus also carries it HTML-escaped.
|
||||
assertEquals("", NatJavComments.retained("*innoWake Scanpunkte für manuelle Eingriffe: #iw#"));
|
||||
assertEquals("", NatJavComments.retained("*innoWake Scanpunkte für manuelle Eingriffe: #iw#"));
|
||||
assertEquals("", NatJavComments.retained("header created during migration process"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A block mixing a generator stamp with a real comment must keep the comment — which is the whole
|
||||
* reason the filter works per line rather than per block.
|
||||
*/
|
||||
@Test
|
||||
void aStampNextToARealCommentLosesOnlyTheStamp() {
|
||||
assertEquals("Setzen Attribute vor Input-Statement", NatJavComments.retained("""
|
||||
*innoWake Scanpunkte für manuelle Eingriffe: #iw#
|
||||
Setzen Attribute vor Input-Statement
|
||||
"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void anOrdinaryCommentSurvivesUntouched() {
|
||||
assertEquals("Steuerungsprogramm für GDA-Initialisierungen",
|
||||
NatJavComments.retained("Steuerungsprogramm für GDA-Initialisierungen"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A row of asterisks is the comment box's drawing, not a statement.
|
||||
*/
|
||||
@Test
|
||||
void decorationOnlyLinesAreDropped() {
|
||||
assertEquals("", NatJavComments.retained("*****************************"));
|
||||
assertEquals("", NatJavComments.retained("-----------------------------"));
|
||||
assertEquals("Text", NatJavComments.retained("=====\nText\n====="));
|
||||
}
|
||||
|
||||
@Test
|
||||
void commentsAreAttachedToWhatTheyDocument() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
@Override
|
||||
public void run() { perform("SUB"); }
|
||||
// Prüft die Eingabe
|
||||
void SUB() { assign(A, 1); }
|
||||
}
|
||||
""");
|
||||
|
||||
AstNode comment = comments(result).stream()
|
||||
.filter(n -> "Prüft die Eingabe".equals(n.value()))
|
||||
.findFirst().orElseThrow(() -> new AssertionError("comment not kept: "
|
||||
+ comments(result).stream().map(AstNode::value).toList()));
|
||||
AstNode function = result.nodes().stream()
|
||||
.filter(n -> n.type() == NodeType.FUNCTION && n.name().equals("SUB"))
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.DOCUMENTS
|
||||
&& e.sourceId().equals(comment.id()) && e.targetId().equals(function.id())),
|
||||
"the comment documents the subroutine that follows it");
|
||||
}
|
||||
|
||||
/**
|
||||
* A real corpus file: its comments must survive and its markers must not.
|
||||
*/
|
||||
@Test
|
||||
void aCorpusFileKeepsItsCommentsAndNotItsMarkers() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/ACOMMON.java", fixture("ACOMMON.java"));
|
||||
|
||||
List<String> texts = comments(result).stream().map(AstNode::value).toList();
|
||||
assertTrue(texts.stream().anyMatch(t -> t != null && t.contains("Steuerungsprogramm")),
|
||||
"the developer's comment survives: " + texts);
|
||||
assertTrue(texts.stream().noneMatch(t -> t != null && t.contains("@@USING")),
|
||||
"no expansion marker became a comment");
|
||||
assertTrue(texts.stream().noneMatch(t -> t != null && t.contains("Generated by innoWake")),
|
||||
"no generation banner became a comment");
|
||||
}
|
||||
|
||||
@Test
|
||||
void languageIsNatjav() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/ACOMMON.java", fixture("ACOMMON.java"));
|
||||
|
||||
assertTrue(comments(result).stream().allMatch(n -> n.language().equals("natjav")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Control-flow blocks. The two things that matter here are what is <em>not</em> emitted: a database
|
||||
* loop is not control flow, and a lowered {@code DECIDE} is one construct rather than a chain of ifs.
|
||||
*/
|
||||
class NatJavControlFlowTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<AstNode> blocks(ParseResult result, String kind) {
|
||||
return result.nodes().stream()
|
||||
.filter(n -> n.type() == NodeType.CONTROL_FLOW && kind.equals(n.name()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private ParseResult parse(String body) {
|
||||
return parser.parse("W.java", "package X;\nimport innowake.mee.natural.api.*;\n@Natural\n"
|
||||
+ "public class W extends NaturalProgram {\n"
|
||||
+ " @Override\n public void run() {\n" + body + "\n }\n}\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifForAndWhileBecomeBlocks() {
|
||||
ParseResult result = parse("""
|
||||
if (EQ(A, "X")) { assign(B, 1); }
|
||||
for (int i = 0; i < 3; i++) { assign(C, 2); }
|
||||
while (LT(D, 9)) { addTo(D, 1); }
|
||||
""");
|
||||
|
||||
assertEquals(1, blocks(result, "IF").size());
|
||||
assertEquals(1, blocks(result, "FOR").size());
|
||||
assertEquals(1, blocks(result, "REPEAT").size(), "a real while is Natural's REPEAT");
|
||||
}
|
||||
|
||||
/**
|
||||
* 20,749 of the corpus's 23,696 {@code while}s are read loops. The Natural parser models
|
||||
* {@code IF}/{@code FOR}/{@code REPEAT}/{@code DECIDE} and never a {@code READ} loop, so these
|
||||
* would be 20,749 blocks the original does not have.
|
||||
*/
|
||||
@Test
|
||||
void aDatabaseLoopIsNotControlFlow() {
|
||||
ParseResult result = parse("""
|
||||
readUMK = read(MDUMKONT);
|
||||
while (readUMK.hasNext()) { assign(A, 1); }
|
||||
""");
|
||||
|
||||
assertTrue(blocks(result, "REPEAT").isEmpty(), "while (x.hasNext()) is a READ loop");
|
||||
}
|
||||
|
||||
/**
|
||||
* The transpiler lowers {@code DECIDE} into an if-chain around a counter. Emitting each arm
|
||||
* separately would turn 9,340 dispatches into some 30,000 unrelated IFs.
|
||||
*/
|
||||
@Test
|
||||
void aLoweredDecideIsOneBlockWithItsBranchValues() {
|
||||
ParseResult result = parse("""
|
||||
int decideCounter1 = 0;
|
||||
if (EQ(xAPP, "KORIN")) { decideCounter1++; fetch("ACC_KR"); }
|
||||
else if (EQ(xAPP, "HAGEL")) { decideCounter1++; fetch("ACC_HA"); }
|
||||
""");
|
||||
|
||||
List<AstNode> decides = blocks(result, "DECIDE");
|
||||
assertEquals(1, decides.size(), "one dispatch, not two ifs");
|
||||
assertTrue(blocks(result, "IF").isEmpty(), "the arms are not separate IF blocks");
|
||||
|
||||
Map<String, String> properties = decides.get(0).properties();
|
||||
assertNotNull(properties);
|
||||
assertEquals("KORIN,HAGEL", properties.get("decideValues"));
|
||||
}
|
||||
|
||||
/**
|
||||
* An ordinary if-chain has no counter and must stay a plain {@code IF} — the counter is the only
|
||||
* thing that distinguishes the two.
|
||||
*/
|
||||
@Test
|
||||
void anOrdinaryIfChainIsNotADecide() {
|
||||
ParseResult result = parse("""
|
||||
if (EQ(A, "X")) { assign(B, 1); }
|
||||
else if (EQ(A, "Y")) { assign(B, 2); }
|
||||
""");
|
||||
|
||||
assertTrue(blocks(result, "DECIDE").isEmpty());
|
||||
assertEquals(2, blocks(result, "IF").size(), "both arms are blocks");
|
||||
}
|
||||
|
||||
/**
|
||||
* Every arm of a chain must appear. The earlier assertion only demanded that <em>some</em> IF
|
||||
* existed, and under it the walk silently dropped every second arm: the else-branch of an
|
||||
* {@code if} <b>is</b> an {@code IfStmt}, and it was handed to a children-walk instead of being
|
||||
* treated as a block.
|
||||
*/
|
||||
@Test
|
||||
void everyArmOfAnIfChainBecomesABlock() {
|
||||
ParseResult result = parse("""
|
||||
if (EQ(A, "1")) { assign(B, 1); }
|
||||
else if (EQ(A, "2")) { assign(B, 2); }
|
||||
else if (EQ(A, "3")) { assign(B, 3); }
|
||||
else { assign(B, 4); }
|
||||
""");
|
||||
|
||||
List<String> conditions = blocks(result, "IF").stream()
|
||||
.map(AstNode::value).sorted().toList();
|
||||
assertEquals(List.of("EQ(A, \"1\")", "EQ(A, \"2\")", "EQ(A, \"3\")"), conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code for} and a {@code switch} must not hand themselves back as their own body — that would
|
||||
* make the walk recognise them again without end.
|
||||
*/
|
||||
@Test
|
||||
void loopsAndSwitchesTerminate() {
|
||||
ParseResult result = parse("""
|
||||
for (int i = 0; i < 3; i++) { assign(A, 1); }
|
||||
switch (X) { case 1: assign(A, 2); break; default: break; }
|
||||
""");
|
||||
|
||||
assertEquals(1, blocks(result, "FOR").size());
|
||||
assertEquals(1, blocks(result, "DECIDE").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void blocksNestByContainment() {
|
||||
ParseResult result = parse("""
|
||||
if (EQ(A, "X")) {
|
||||
for (int i = 0; i < 3; i++) { assign(B, 1); }
|
||||
}
|
||||
""");
|
||||
|
||||
AstNode outer = blocks(result, "IF").get(0);
|
||||
AstNode inner = blocks(result, "FOR").get(0);
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(outer.id()) && e.targetId().equals(inner.id())),
|
||||
"the FOR nests inside the IF, not beside it");
|
||||
}
|
||||
|
||||
/**
|
||||
* A block inside a subroutine belongs to that subroutine, as its statements do.
|
||||
*/
|
||||
@Test
|
||||
void aBlockInASubroutineBelongsToIt() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
@Override
|
||||
public void run() { perform("SUB"); }
|
||||
void SUB() {
|
||||
if (EQ(A, "X")) { assign(B, 1); }
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
AstNode function = result.nodes().stream()
|
||||
.filter(n -> n.type() == NodeType.FUNCTION && n.name().equals("SUB"))
|
||||
.findFirst().orElseThrow();
|
||||
AstNode block = blocks(result, "IF").get(0);
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(function.id()) && e.targetId().equals(block.id())));
|
||||
}
|
||||
|
||||
/**
|
||||
* The counter increment must be a <em>direct</em> statement of the arm. Searching the arm's whole
|
||||
* subtree also found it through a nested {@code if}, so one dispatch was recognised again at each
|
||||
* nesting level — 1,917 DECIDEs inside their own DECIDE in the corpus.
|
||||
*/
|
||||
@Test
|
||||
void aNestedIfInsideADecideArmIsNotASecondDecide() {
|
||||
ParseResult result = parse("""
|
||||
int decideCounter1 = 0;
|
||||
if (EQ(A, "X")) {
|
||||
decideCounter1++;
|
||||
if (EQ(B, "Y")) { assign(C, 1); }
|
||||
}
|
||||
else if (EQ(A, "Z")) { decideCounter1++; assign(C, 2); }
|
||||
""");
|
||||
|
||||
assertEquals(1, blocks(result, "DECIDE").size(), "one dispatch, not one per nesting level");
|
||||
assertEquals(1, blocks(result, "IF").size(), "the nested if stays a plain IF");
|
||||
}
|
||||
|
||||
/**
|
||||
* A dispatch on numbers has no string literals in its arms — it is a dispatch nonetheless.
|
||||
* Deciding "is this a DECIDE?" by "does it have literal values?" classified those as plain ifs.
|
||||
* The counter's name is not always numbered either ({@code decideCounterGWFMCHKC}).
|
||||
*/
|
||||
@Test
|
||||
void aDispatchWithoutStringLiteralsIsStillADecide() {
|
||||
ParseResult result = parse("""
|
||||
int decideCounterGWFMCHKC = 0;
|
||||
if (EQ(xIDX, 57)) { decideCounterGWFMCHKC++; assign(A, 1); }
|
||||
else if (EQ(xIDX, 58)) { decideCounterGWFMCHKC++; assign(A, 2); }
|
||||
""");
|
||||
|
||||
assertEquals(1, blocks(result, "DECIDE").size());
|
||||
assertTrue(blocks(result, "IF").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void aRealCorpusFileYieldsBlocks() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/MDBT74B.java", fixture("MDBT74B.java"));
|
||||
|
||||
assertFalse(blocks(result, "IF").isEmpty(), "MDBT74B has conditionals");
|
||||
assertTrue(blocks(result, "REPEAT").isEmpty(),
|
||||
"its two while loops are both read/sort loops");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Statement-level dataflow. The operands name Java identifiers throughout while the field nodes
|
||||
* carry Natural names, so every test here is really about that translation.
|
||||
*/
|
||||
class NatJavDataflowTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static AstNode node(ParseResult result, NodeType type, String name) {
|
||||
return result.nodes().stream()
|
||||
.filter(n -> n.type() == type && n.name().equals(name))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("no " + type + " named '" + name + "'"));
|
||||
}
|
||||
|
||||
private static boolean hasEdge(ParseResult result, EdgeType type, AstNode from, AstNode to) {
|
||||
return result.edges().stream().anyMatch(e -> e.type() == type
|
||||
&& e.sourceId().equals(from.id()) && e.targetId().equals(to.id()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code assign(target, source)} writes its first operand and reads the rest — and both are
|
||||
* found through the file's own symbol table, under their Natural names.
|
||||
*/
|
||||
@Test
|
||||
void assignWritesTheTargetAndReadsTheSource() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/MDBT74B.java", fixture("MDBT74B.java"),
|
||||
NatJavDdmResolver.NONE);
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "MDBT74B");
|
||||
AstNode target = node(result, NodeType.VARIABLE, "#WN-DATN");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.WRITES, module, target),
|
||||
"assign(xWN_DATN, $DATN()) writes #WN-DATN");
|
||||
}
|
||||
|
||||
/**
|
||||
* A subscript is not a member: {@code xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get($)} names the field,
|
||||
* not a field called {@code get}.
|
||||
*/
|
||||
@Test
|
||||
void arraySubscriptIsStrippedFromTheOperand() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/MDBT74B.java", fixture("MDBT74B.java"),
|
||||
NatJavDdmResolver.NONE);
|
||||
|
||||
AstNode field = node(result, NodeType.VARIABLE, "#RN-UMKONT-MENGE");
|
||||
AstNode module = node(result, NodeType.MODULE, "MDBT74B");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.WRITES, module, field)
|
||||
|| hasEdge(result, EdgeType.READS, module, field),
|
||||
"the subscripted field is still the operand");
|
||||
}
|
||||
|
||||
/**
|
||||
* The cross-file case, and the reason item E1 needs an index: {@code WSAIVP.INHALT} names a field
|
||||
* of a {@code USING}'d data area. The placeholder must carry the field's <em>Natural</em> name,
|
||||
* so the existing placeholder-field resolution — which joins on name — can redirect it.
|
||||
*/
|
||||
@Test
|
||||
void fieldOfAUsedDataAreaBecomesAPlaceholderUnderItsNaturalName() {
|
||||
NatJavDdmResolver ddms = new NatJavDdmResolver() {
|
||||
@Override
|
||||
public NatJavDdmResolver.@Nullable Ddm resolve(String ddmClassName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String resolveAreaField(String areaClassName, String javaFieldName) {
|
||||
return areaClassName.equals("WSAIVP_DA") && javaFieldName.equals("INHALT")
|
||||
? "IN-HALT" : null;
|
||||
}
|
||||
};
|
||||
|
||||
ParseResult result = parser.parse("fixtures/natjav/ACOMMON.java", fixture("ACOMMON.java"), ddms);
|
||||
|
||||
AstNode placeholder = node(result, NodeType.VARIABLE, "IN-HALT");
|
||||
assertEquals("", placeholder.sourceFile(), "an unresolved field reference carries no file");
|
||||
|
||||
AstNode area = node(result, NodeType.DATA_STRUCTURE, "WSAIVP_DA");
|
||||
assertTrue(hasEdge(result, EdgeType.CONTAINS, area, placeholder),
|
||||
"it hangs under the data area it came from");
|
||||
}
|
||||
|
||||
/**
|
||||
* A subscript is not the end of the path. Cutting at the first {@code .get(} attributed
|
||||
* {@code ARR.get(1).FLD} to the group {@code ARR}, so the field it names never saw the access.
|
||||
*/
|
||||
@Test
|
||||
void anIndexedGroupMemberResolvesToTheMemberNotTheGroup() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
@Local
|
||||
@L1 Group ARR = group("ARR");
|
||||
@L2 A FLD = A(8 ,"#FLD");
|
||||
@L1 A ZIEL = A(8 ,"#ZIEL");
|
||||
@Override
|
||||
public void run() {
|
||||
move(ARR.get(1).FLD, into(ZIEL));
|
||||
}
|
||||
}
|
||||
""", NatJavDdmResolver.NONE);
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "W");
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, node(result, NodeType.VARIABLE, "#FLD")),
|
||||
"the indexed member is read");
|
||||
assertFalse(hasEdge(result, EdgeType.READS, module, node(result, NodeType.DATA_STRUCTURE, "ARR")),
|
||||
"the group is not");
|
||||
}
|
||||
|
||||
/**
|
||||
* Without the index nothing is invented: an unresolvable operand yields no edge rather than a
|
||||
* plausible-looking wrong one.
|
||||
*/
|
||||
@Test
|
||||
void unresolvableOperandYieldsNothing() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/ACOMMON.java", fixture("ACOMMON.java"),
|
||||
NatJavDdmResolver.NONE);
|
||||
|
||||
assertTrue(result.nodes().stream().noneMatch(n -> n.type() == NodeType.VARIABLE
|
||||
&& n.sourceFile().isEmpty()),
|
||||
"no placeholder field without a known Natural name");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* DB access, views and the DDM index they resolve through. {@code MDBT74B.java} and
|
||||
* {@code MDUMKONT_DDM.java} are unmodified corpus files: the program reads a view, positions a
|
||||
* second one and deletes through the loop object it kept.
|
||||
*/
|
||||
class NatJavDbAccessTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a resolver holding just the corpus DDM this test needs
|
||||
*/
|
||||
private static NatJavDdmResolver ddms() {
|
||||
NatJavDdmResolver.Ddm ddm = NatJavDdm.parse(fixture("MDUMKONT_DDM.java"), "MDUMKONT_DDM");
|
||||
assertNotNull(ddm, "the DDM fixture must parse");
|
||||
return name -> name.equals("MDUMKONT_DDM") ? ddm : null;
|
||||
}
|
||||
|
||||
private ParseResult parse(String name, NatJavDdmResolver ddms) {
|
||||
return parser.parse("fixtures/natjav/" + name, fixture(name), ddms);
|
||||
}
|
||||
|
||||
private static AstNode node(ParseResult result, NodeType type, String name) {
|
||||
return result.nodes().stream()
|
||||
.filter(n -> n.type() == type && n.name().equals(name))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("no " + type + " named '" + name + "'"));
|
||||
}
|
||||
|
||||
private static List<AstNode> nodes(ParseResult result, NodeType type) {
|
||||
return result.nodes().stream().filter(n -> n.type() == type).toList();
|
||||
}
|
||||
|
||||
private static boolean hasEdge(ParseResult result, EdgeType type, AstNode from, AstNode to) {
|
||||
return result.edges().stream().anyMatch(e -> e.type() == type
|
||||
&& e.sourceId().equals(from.id()) && e.targetId().equals(to.id()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ddmYieldsTableNameAndNaturalColumnNames() {
|
||||
NatJavDdmResolver.Ddm ddm = NatJavDdm.parse(fixture("MDUMKONT_DDM.java"), "MDUMKONT_DDM");
|
||||
|
||||
assertNotNull(ddm);
|
||||
assertEquals("MDUMKONT", ddm.tableName());
|
||||
assertEquals("V-FREIGABE", ddm.columns().get("V_FREIGABE"),
|
||||
"the view declares V_FREIGABE; only the DDM knows it is V-FREIGABE");
|
||||
assertEquals("AUSLEER-DATUM", ddm.columns().get("AUSLEER_DATUM"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code DB_TABLE} carries no source file anywhere in this system — that is what makes every
|
||||
* reference to a table converge on one node. The DDM file stays a module and is joined to the
|
||||
* table by {@code MAPS_TO}, as the Java parser joins a JPA entity to its table.
|
||||
*/
|
||||
@Test
|
||||
void ddmFileIsAModuleMappedToAFilelessTable() {
|
||||
ParseResult result = parse("MDUMKONT_DDM.java", NatJavDdmResolver.NONE);
|
||||
AstNode module = node(result, NodeType.MODULE, "MDUMKONT");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
|
||||
assertEquals("DDM", properties(module).get("moduleKind"));
|
||||
assertEquals("", table.sourceFile(), "a DB_TABLE is never file-backed");
|
||||
assertTrue(hasEdge(result, EdgeType.MAPS_TO, module, table));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ddmColumnsBecomeFieldsOfTheDdmModule() {
|
||||
ParseResult result = parse("MDUMKONT_DDM.java", NatJavDdmResolver.NONE);
|
||||
AstNode module = node(result, NodeType.MODULE, "MDUMKONT");
|
||||
AstNode column = node(result, NodeType.FIELD, "AUSLEER-DATUM");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.CONTAINS, module, column));
|
||||
assertEquals("AUSLEER_DATUM", properties(column).get(NatJavFields.JAVA_NAME));
|
||||
assertEquals("AF", properties(column).get("adabasShortName"),
|
||||
"the ADABAS short name is only ever visible in the DDM");
|
||||
}
|
||||
|
||||
@Test
|
||||
void viewIsLinkedToItsTable() {
|
||||
ParseResult result = parse("MDBT74B.java", ddms());
|
||||
AstNode view = node(result, NodeType.DATA_STRUCTURE, "MDUMKONT");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.USES_TYPE, view, table));
|
||||
}
|
||||
|
||||
/**
|
||||
* The columns deferred in item B: a view declares them by Java name with no length, and the DDM
|
||||
* supplies both the Natural name and, where it states one, the format.
|
||||
*/
|
||||
@Test
|
||||
void viewColumnsGetTheirNaturalNamesFromTheDdm() {
|
||||
ParseResult result = parse("MDBT74B.java", ddms());
|
||||
AstNode view = node(result, NodeType.DATA_STRUCTURE, "MDUMKONT");
|
||||
AstNode column = node(result, NodeType.FIELD, "AUSLEER-DATUM");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.CONTAINS, view, column));
|
||||
assertEquals("AUSLEER_DATUM", properties(column).get(NatJavFields.JAVA_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readEmitsAnAccessAndAReadsEdge() {
|
||||
ParseResult result = parse("MDBT74B.java", ddms());
|
||||
AstNode module = node(result, NodeType.MODULE, "MDBT74B");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, table));
|
||||
assertTrue(nodes(result, NodeType.DB_ACCESS).stream()
|
||||
.anyMatch(n -> "READ".equals(n.dataType()) && n.name().equals("MDUMKONT")),
|
||||
"a READ access on MDUMKONT");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code delete(getDEL)} names the loop object, not the view. The table is only known at the
|
||||
* {@code get} that positioned it, twelve lines earlier and in another block.
|
||||
*/
|
||||
@Test
|
||||
void deleteResolvesThroughTheLoopObject() {
|
||||
ParseResult result = parse("MDBT74B.java", ddms());
|
||||
AstNode module = node(result, NodeType.MODULE, "MDBT74B");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
|
||||
assertTrue(hasEdge(result, EdgeType.WRITES, module, table),
|
||||
"the DELETE must reach the table the loop object was positioned on");
|
||||
assertTrue(nodes(result, NodeType.DB_ACCESS).stream()
|
||||
.anyMatch(n -> "DELETE".equals(n.dataType())),
|
||||
"a DELETE access");
|
||||
}
|
||||
|
||||
/**
|
||||
* An array subscript is not a database statement. {@code UMK.SPARTEN.get(range(1, 4))} occurs
|
||||
* 435,129 times in the corpus against 9,904 real {@code GET}s — counting it would report a
|
||||
* fortyfold phantom.
|
||||
*/
|
||||
@Test
|
||||
void arraySubscriptIsNotADatabaseGet() {
|
||||
ParseResult result = parse("MDBT74B.java", ddms());
|
||||
|
||||
long reads = nodes(result, NodeType.DB_ACCESS).stream()
|
||||
.filter(n -> "READ".equals(n.dataType())).count();
|
||||
assertEquals(2, reads, "exactly the read(MDUMKONT) and the get(MDUMKONT_DEL, ...)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Without a DDM index a view still exists — it is declared in the file — but nothing about the
|
||||
* table is invented.
|
||||
*/
|
||||
@Test
|
||||
void viewWithoutADdmIndexHasNoTable() {
|
||||
ParseResult result = parse("MDBT74B.java", NatJavDdmResolver.NONE);
|
||||
|
||||
assertNotNull(node(result, NodeType.DATA_STRUCTURE, "MDUMKONT"));
|
||||
assertTrue(nodes(result, NodeType.DB_TABLE).isEmpty(), "no table is guessed");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code workfile-accesses} aggregates the record buffer from the {@code READS}/{@code WRITES}
|
||||
* edge, not from the access node — putting it on the node alone left every buffer list empty.
|
||||
*/
|
||||
@Test
|
||||
void workfileRecordBufferIsOnTheEdgeAsWellAsTheNode() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
@Override
|
||||
public void run() {
|
||||
writeWorkFile(1, REC);
|
||||
}
|
||||
}
|
||||
""", NatJavDdmResolver.NONE);
|
||||
|
||||
AstNode workfile = node(result, NodeType.WORKFILE, "1");
|
||||
AstEdge write = result.edges().stream()
|
||||
.filter(e -> e.type() == EdgeType.WRITES && e.targetId().equals(workfile.id()))
|
||||
.findFirst().orElseThrow(() -> new AssertionError("no WRITES to the work file"));
|
||||
|
||||
assertNotNull(write.properties());
|
||||
assertEquals("REC", write.properties().get("recordBuffer"));
|
||||
assertEquals("REC", properties(node(result, NodeType.WORKFILE_ACCESS, "1")).get("recordBuffer"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A statement rarely names the {@code @ViewOf} field itself. {@code VVSTICH.VT VTO = VT;} makes
|
||||
* {@code VTO} an alias, and the declared type says so — 1,390 accesses in the corpus reach their
|
||||
* table only this way.
|
||||
*/
|
||||
@Test
|
||||
void aliasOfALocalViewResolvesToTheSameTable() {
|
||||
ParseResult result = parser.parse("A.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class A extends NaturalProgram {
|
||||
@Local
|
||||
@L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT = view("MDUMKONT");
|
||||
@L2 A SATZART;
|
||||
A.MDUMKONT ALIAS = MDUMKONT;
|
||||
@Override
|
||||
public void run() {
|
||||
read(ALIAS);
|
||||
}
|
||||
}
|
||||
""", ddms());
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "A");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, table),
|
||||
"the alias must reach the table its view reads");
|
||||
}
|
||||
|
||||
/**
|
||||
* A view declared in a {@code USING}'d data area: the program re-declares it as a field typed by
|
||||
* the area, and only that other file knows which DDM it reads.
|
||||
*/
|
||||
@Test
|
||||
void viewOfAUsedDataAreaResolvesThroughTheIndex() {
|
||||
NatJavDdmResolver.Ddm ddm = NatJavDdm.parse(fixture("MDUMKONT_DDM.java"), "MDUMKONT_DDM");
|
||||
assertNotNull(ddm);
|
||||
NatJavDdmResolver ddms = new NatJavDdmResolver() {
|
||||
@Override
|
||||
public NatJavDdmResolver.@Nullable Ddm resolve(String ddmClassName) {
|
||||
return ddmClassName.equals("MDUMKONT_DDM") ? ddm : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NatJavDdmResolver.@Nullable Ddm resolveAreaView(String areaClassName, String viewName) {
|
||||
return areaClassName.equals("YLX_DA") && viewName.equals("SWPOS") ? ddm : null;
|
||||
}
|
||||
};
|
||||
|
||||
ParseResult result = parser.parse("B.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class B extends NaturalProgram {
|
||||
@Using KORIN.YLX_DA YLX_DA = localUsing(KORIN.YLX_DA.class);
|
||||
public final KORIN.deep.YLX_DA.SWPOS SWPOS = YLX_DA.SWPOS;
|
||||
@Override
|
||||
public void run() {
|
||||
find(SWPOS, withLimit(1));
|
||||
}
|
||||
}
|
||||
""", ddms);
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "B");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, table),
|
||||
"a fully qualified data-area type must still resolve");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code findEvents} is {@code FIND} written with event blocks. Leaving it out lost 695 reads and
|
||||
* left every {@code UPDATE} positioned by one without a table — 73 of them in the corpus.
|
||||
*/
|
||||
@Test
|
||||
void eventFormOfAReadIsADatabaseStatementToo() {
|
||||
ParseResult result = parser.parse("C.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class C extends NaturalProgram {
|
||||
@Local
|
||||
@L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT = view("MDUMKONT");
|
||||
@L2 A SATZART;
|
||||
FindEvents findU;
|
||||
@Override
|
||||
public void run() {
|
||||
findU = findEvents(MDUMKONT, withHold());
|
||||
update(findU);
|
||||
}
|
||||
}
|
||||
""", ddms());
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "C");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, table), "findEvents is a read");
|
||||
assertTrue(hasEdge(result, EdgeType.WRITES, module, table),
|
||||
"and the update it positions reaches the same table");
|
||||
}
|
||||
|
||||
/**
|
||||
* The alias can also be written with the view's own generated type, unqualified —
|
||||
* {@code VERTRAG_VD VD = VERTRAG_VD;} — and a {@code STORE} positions a record an
|
||||
* {@code UPDATE} may follow on. Both were the last four unresolved accesses in the corpus.
|
||||
*/
|
||||
@Test
|
||||
void unqualifiedAliasAndStorePositionedUpdateResolve() {
|
||||
ParseResult result = parser.parse("D.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class D extends NaturalProgram {
|
||||
@Local
|
||||
@L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT = view("MDUMKONT");
|
||||
@L2 A SATZART;
|
||||
MDUMKONT VD = MDUMKONT;
|
||||
Store storeS1;
|
||||
@Override
|
||||
public void run() {
|
||||
read(VD);
|
||||
storeS1 = store(MDUMKONT);
|
||||
update(storeS1);
|
||||
}
|
||||
}
|
||||
""", ddms());
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "D");
|
||||
AstNode table = node(result, NodeType.DB_TABLE, "MDUMKONT");
|
||||
assertTrue(hasEdge(result, EdgeType.READS, module, table), "the unqualified alias resolves");
|
||||
assertTrue(nodes(result, NodeType.DB_ACCESS).stream()
|
||||
.allMatch(a -> !a.name().isEmpty()),
|
||||
"no access is left without a table: " + nodes(result, NodeType.DB_ACCESS).stream()
|
||||
.filter(a -> a.name().isEmpty()).map(AstNode::value).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyEdgeConnectsEmittedNodes() {
|
||||
for (String name : List.of("MDBT74B.java", "MDUMKONT_DDM.java")) {
|
||||
ParseResult result = parse(name, ddms());
|
||||
for (AstEdge edge : result.edges()) {
|
||||
assertTrue(result.nodes().stream().anyMatch(n -> n.id().equals(edge.sourceId())),
|
||||
name + ": edge source missing");
|
||||
assertTrue(result.nodes().stream().anyMatch(n -> n.id().equals(edge.targetId())),
|
||||
name + ": edge target missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> properties(AstNode node) {
|
||||
Map<String, String> properties = node.properties();
|
||||
assertNotNull(properties, node.name() + " has no properties");
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstEdge;
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* The {@code DEFINE DATA} field tree. Fixtures are unmodified corpus files.
|
||||
*/
|
||||
class NatJavFieldsTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
try {
|
||||
return Files.readString(Path.of("src/test/resources/fixtures/natjav", name), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static AstNode node(ParseResult result, NodeType type, String name) {
|
||||
return result.nodes().stream()
|
||||
.filter(n -> n.type() == type && n.name().equals(name))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("no " + type + " named '" + name + "' among "
|
||||
+ result.nodes().stream().map(AstNode::name).toList()));
|
||||
}
|
||||
|
||||
private static @Nullable String prop(AstNode node, String key) {
|
||||
Map<String, String> properties = node.properties();
|
||||
assertNotNull(properties, "node " + node.name() + " has no properties");
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
private static boolean contains(ParseResult result, AstNode parent, AstNode child) {
|
||||
return result.edges().stream().anyMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(parent.id()) && e.targetId().equals(child.id()));
|
||||
}
|
||||
|
||||
private ParseResult parse(String name) {
|
||||
return parser.parse("fixtures/natjav/" + name, fixture(name));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupAndItsFieldsUseTheNaturalNames() {
|
||||
ParseResult result = parse("YPMA71VK_DA.java");
|
||||
|
||||
AstNode area = node(result, NodeType.DATA_STRUCTURE, "YPMA71VK");
|
||||
AstNode taste = node(result, NodeType.VARIABLE, "MENU-TASTE");
|
||||
|
||||
assertEquals("A4", taste.dataType());
|
||||
assertEquals("MENU_TASTE", prop(taste, NatJavFields.JAVA_NAME));
|
||||
assertTrue(contains(result, area, taste), "the field hangs off the data area");
|
||||
}
|
||||
|
||||
/**
|
||||
* 3,773 of the corpus's 5,818 data areas open with a top-level group named exactly like the area.
|
||||
* That group is the area, not a member of it — emitting both would put two identically named
|
||||
* {@code DATA_STRUCTURE}s in one file.
|
||||
*/
|
||||
@Test
|
||||
void topLevelGroupNamedLikeTheAreaIsNotASecondNode() {
|
||||
ParseResult result = parse("YPMA71VK_DA.java");
|
||||
|
||||
assertEquals(1, result.nodes().stream()
|
||||
.filter(n -> n.name().equals("YPMA71VK")).count(), "exactly one node carries the area name");
|
||||
}
|
||||
|
||||
/**
|
||||
* The transpiler always writes a scale ({@code N(9,0)}); Natural writes only significant digits
|
||||
* ({@code N9}). Without this the migration's every integer field would carry a {@code dataType}
|
||||
* its original never had.
|
||||
*/
|
||||
@Test
|
||||
void zeroScaleIsDroppedFromNumericTypes() {
|
||||
ParseResult result = parse("COMMON_DA.java");
|
||||
|
||||
assertEquals("N9", node(result, NodeType.VARIABLE, "+BBST-POLNR").dataType());
|
||||
assertEquals("A17", node(result, NodeType.VARIABLE, "+BPOL-PARM").dataType());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Redefine xSYS_SCHALTER} names a Java field, and its Natural name is only known from that
|
||||
* field's own declaration — resolved through the class's symbol table, not by a string rule.
|
||||
*/
|
||||
@Test
|
||||
void redefineTakesTheNameOfTheFieldItRedefines() {
|
||||
ParseResult result = parse("COMMON_DA.java");
|
||||
|
||||
AstNode redefine = result.nodes().stream()
|
||||
.filter(n -> n.type() == NodeType.DATA_STRUCTURE && "#SYS-SCHALTER".equals(n.name()))
|
||||
.findFirst().orElseThrow(() -> new AssertionError("no redefine of #SYS-SCHALTER"));
|
||||
|
||||
assertEquals("#SYS-SCHALTER", prop(redefine, NatJavFields.REDEFINES));
|
||||
AstNode nested = node(result, NodeType.VARIABLE, "#ERROR-OPTN");
|
||||
assertTrue(contains(result, redefine, nested), "redefined subfields nest under the redefine");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scopeComesFromTheAnnotation() {
|
||||
assertEquals("GLOBAL", prop(node(parse("COMMON_DA.java"), NodeType.VARIABLE, "+PROGRAMM"), "scope"));
|
||||
assertEquals("PARAMETER", prop(node(parse("YPMA71VK_DA.java"), NodeType.VARIABLE, "VR"), "scope"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayBoundsAreAppendedTheNaturalWay() {
|
||||
ParseResult result = parse("YPUMKLOG_DA.java");
|
||||
|
||||
assertEquals("A200/1:9999", node(result, NodeType.VARIABLE, "DATA").dataType());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code A()} with no length is Natural's {@code A DYNAMIC} — a length must not be invented.
|
||||
*/
|
||||
@Test
|
||||
void dynamicLengthYieldsTheBareFormat() {
|
||||
assertEquals("A", node(parse("YPUMKLOG_DA.java"), NodeType.VARIABLE, "OPID").dataType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedGroupsNestByLevelAnnotation() {
|
||||
ParseResult result = parse("YPUMKLOG_DA.java");
|
||||
|
||||
AstNode area = node(result, NodeType.DATA_STRUCTURE, "YPUMKLOG");
|
||||
AstNode in = node(result, NodeType.DATA_STRUCTURE, "IN");
|
||||
AstNode vrKey = node(result, NodeType.VARIABLE, "VR-KEY");
|
||||
|
||||
assertTrue(contains(result, area, in), "IN nests under the area");
|
||||
assertTrue(contains(result, in, vrKey), "the field nests under IN, not under the area");
|
||||
}
|
||||
|
||||
/**
|
||||
* A field whose Natural name the source does not state keeps its Java name and says so, rather
|
||||
* than carrying a derived name that looks authoritative and is wrong 4.81 % of the time.
|
||||
*/
|
||||
@Test
|
||||
void fieldWithoutANameLiteralKeepsTheJavaNameAndIsFlagged() {
|
||||
AstNode field = node(parse("YPUMKLOG_DA.java"), NodeType.VARIABLE, "TRACE_JN");
|
||||
|
||||
assertEquals("false", prop(field, NatJavFields.NATURAL_NAME_KNOWN));
|
||||
assertNull(prop(field, NatJavFields.JAVA_NAME), "name and Java name are the same here");
|
||||
}
|
||||
|
||||
/**
|
||||
* A view's columns carry neither name nor length — both live in the DDM, another file — so the
|
||||
* view is emitted and its columns are left to the DB-access phase rather than invented.
|
||||
*/
|
||||
@Test
|
||||
void viewIsAStructureWithoutColumnsForNow() {
|
||||
ParseResult result = parse("MDBT74B.java");
|
||||
|
||||
AstNode view = node(result, NodeType.DATA_STRUCTURE, "MDUMKONT");
|
||||
assertTrue(result.edges().stream().noneMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(view.id())), "no column nodes under the view yet");
|
||||
}
|
||||
|
||||
/**
|
||||
* A program declares local data inline, exactly as Natural's {@code DEFINE DATA LOCAL} does —
|
||||
* 15,775 non-data-area files in the corpus do.
|
||||
*/
|
||||
@Test
|
||||
void programLocalDataIsParsedToo() {
|
||||
ParseResult result = parse("MDBT74B.java");
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "MDBT74B");
|
||||
AstNode local = node(result, NodeType.VARIABLE, "#WN-ISN");
|
||||
|
||||
assertEquals("P9", local.dataType());
|
||||
assertEquals("LOCAL", prop(local, "scope"));
|
||||
assertTrue(contains(result, module, local));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingBecomesAnIncludesEdgeKeyedOnTheClassName() {
|
||||
ParseResult result = parse("ACOMMON.java");
|
||||
|
||||
AstNode module = node(result, NodeType.MODULE, "ACOMMON");
|
||||
AstNode placeholder = node(result, NodeType.DATA_STRUCTURE, "WSAIVP_DA");
|
||||
|
||||
assertEquals("", placeholder.sourceFile(), "an unresolved reference carries no source file");
|
||||
assertEquals("WSAIVP_DA", prop(placeholder, NatJavUsings.CLASS_NAME));
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.INCLUDES
|
||||
&& e.sourceId().equals(module.id()) && e.targetId().equals(placeholder.id())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedUsingOfTheSameAreaYieldsOnePlaceholder() {
|
||||
ParseResult result = parse("ACOMMON.java");
|
||||
|
||||
List<AstNode> placeholders = result.nodes().stream()
|
||||
.filter(n -> n.sourceFile().isEmpty() && n.name().equals("GWLOGNAM_DA"))
|
||||
.toList();
|
||||
assertEquals(1, placeholders.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* The transpiler distinguishes {@code parameterUsing} from {@code localUsing}, which is Natural's
|
||||
* {@code PARAMETER USING} vs {@code LOCAL USING} — without it a subprogram's interface is
|
||||
* indistinguishable from its scratch data.
|
||||
*/
|
||||
@Test
|
||||
void usingScopeAndParameterSlotAreRecordedOnTheEdge() {
|
||||
ParseResult result = parser.parse("V.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class V extends NaturalSubprogram {
|
||||
@Using KORIN.A_DA A_DA = parameterUsing(KORIN.A_DA.class);
|
||||
@Using KORIN.B_DA B_DA = parameterUsing(KORIN.B_DA.class);
|
||||
@Using KORIN.C_DA C_DA = localUsing(KORIN.C_DA.class);
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertEquals("0", includeProp(result, "A_DA", NatJavUsings.PARAM_POSITION));
|
||||
assertEquals("1", includeProp(result, "B_DA", NatJavUsings.PARAM_POSITION));
|
||||
assertEquals("PARAMETER", includeProp(result, "A_DA", NatJavUsings.SCOPE));
|
||||
assertEquals("LOCAL", includeProp(result, "C_DA", NatJavUsings.SCOPE));
|
||||
assertNull(includeProp(result, "C_DA", NatJavUsings.PARAM_POSITION),
|
||||
"a LOCAL area occupies no argument slot");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a property of the {@code INCLUDES} edge pointing at the area named {@code area}
|
||||
*/
|
||||
/**
|
||||
* Item 184: {@code COMMON_DA} exists in {@code KORIN} and in {@code ABFRAGE}. The class name
|
||||
* cannot tell them apart; the qualifier the transpiler wrote can, and it is the full Java
|
||||
* package the real area carries as {@code package}.
|
||||
*/
|
||||
@Test
|
||||
void qualifiedUsingRecordsTheLibraryItNamesOnThePlaceholder() {
|
||||
ParseResult result = parser.parse("V.java", """
|
||||
package CSTENTW;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class V extends NaturalProgram {
|
||||
@Using KORIN.Vertrag.COMMON_DA COMMON_DA = globalUsing(KORIN.Vertrag.COMMON_DA.class);
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
AstNode placeholder = node(result, NodeType.DATA_STRUCTURE, "COMMON_DA");
|
||||
assertEquals("KORIN.Vertrag", prop(placeholder, NatJavUsings.USING_PACKAGE),
|
||||
"the qualifier, not the including module's own package");
|
||||
assertEquals("COMMON_DA", placeholder.name(), "the node keeps the class name - the merge key must not move");
|
||||
}
|
||||
|
||||
/**
|
||||
* An unqualified type resolves to the file's own package (Java's same-package rule; in the corpus
|
||||
* 1,193 of 1,193 unqualified {@code @Using} name a class in the same directory).
|
||||
*/
|
||||
@Test
|
||||
void unqualifiedUsingRecordsTheIncludingFilesOwnPackage() {
|
||||
ParseResult result = parser.parse("V.java", """
|
||||
package KORIN.Vertrag.Beteiligung;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class V extends NaturalProgram {
|
||||
@Using ABGB02P_DA ABGB02P_DA = localUsing(ABGB02P_DA.class);
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertEquals("KORIN.Vertrag.Beteiligung",
|
||||
prop(node(result, NodeType.DATA_STRUCTURE, "ABGB02P_DA"), NatJavUsings.USING_PACKAGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* The same class name from two libraries in one module has no single right library. The
|
||||
* placeholder stays one node (the merge key is the class name) and records no package, so the
|
||||
* resolution refuses instead of binding to whichever came last.
|
||||
*/
|
||||
@Test
|
||||
void usingTheSameClassNameFromTwoLibrariesRecordsNoPackage() {
|
||||
ParseResult result = parser.parse("V.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class V extends NaturalProgram {
|
||||
@Using KORIN.COMMON_DA A = globalUsing(KORIN.COMMON_DA.class);
|
||||
@Using ABFRAGE.COMMON_DA B = globalUsing(ABFRAGE.COMMON_DA.class);
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
List<AstNode> placeholders = result.nodes().stream()
|
||||
.filter(n -> n.sourceFile().isEmpty() && n.name().equals("COMMON_DA"))
|
||||
.toList();
|
||||
assertEquals(1, placeholders.size());
|
||||
assertNull(prop(placeholders.get(0), NatJavUsings.USING_PACKAGE));
|
||||
assertEquals("COMMON_DA", prop(placeholders.get(0), NatJavUsings.CLASS_NAME));
|
||||
}
|
||||
|
||||
private static @Nullable String includeProp(ParseResult result, String area, String key) {
|
||||
AstNode target = node(result, NodeType.DATA_STRUCTURE, area);
|
||||
AstEdge edge = result.edges().stream()
|
||||
.filter(e -> e.type() == EdgeType.INCLUDES && e.targetId().equals(target.id()))
|
||||
.findFirst().orElseThrow(() -> new AssertionError("no INCLUDES to " + area));
|
||||
assertNotNull(edge.properties(), "the INCLUDES edge carries no properties");
|
||||
return edge.properties().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every emitted edge must connect two nodes of this parse result — a dangling endpoint would be
|
||||
* persisted as an edge to nothing.
|
||||
*/
|
||||
@Test
|
||||
void everyEdgeConnectsEmittedNodes() {
|
||||
for (String name : List.of("COMMON_DA.java", "YPUMKLOG_DA.java", "MDBT74B.java", "ACOMMON.java")) {
|
||||
ParseResult result = parse(name);
|
||||
for (AstEdge edge : result.edges()) {
|
||||
assertTrue(result.nodes().stream().anyMatch(n -> n.id().equals(edge.sourceId())),
|
||||
name + ": edge source missing");
|
||||
assertTrue(result.nodes().stream().anyMatch(n -> n.id().equals(edge.targetId())),
|
||||
name + ": edge target missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parserjava.JavaLineCounter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class NatJavLineCounterTest {
|
||||
|
||||
private final NatJavLineCounter counter = new NatJavLineCounter();
|
||||
|
||||
/**
|
||||
* The reason this class exists rather than a reused {@link JavaLineCounter}: reporting
|
||||
* {@code "java"} here would merge transpiled Natural into the hand-written-Java bucket of every
|
||||
* per-language LoC rollup.
|
||||
*/
|
||||
@Test
|
||||
void languageIsNatjavNotJava() {
|
||||
assertEquals("natjav", counter.language());
|
||||
assertEquals("java", new JavaLineCounter().language());
|
||||
}
|
||||
|
||||
@Test
|
||||
void countsWithJavaCommentRules() {
|
||||
String content = """
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN;
|
||||
|
||||
@Natural
|
||||
public class X extends NaturalProgram {
|
||||
// a comment
|
||||
public void run() {
|
||||
callnat("Y"); /* trailing */
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
LocMetrics metrics = counter.count(content);
|
||||
assertEquals(12, metrics.loc());
|
||||
assertEquals(new JavaLineCounter().count(content).sloc(), metrics.sloc());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.LocMetrics;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Fixtures are unmodified files from the reference corpus (innoWake-transpiled Natural), one per
|
||||
* shape the naming and classification rules have to survive.
|
||||
*/
|
||||
class NatJavParserTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static String fixture(String name) {
|
||||
Path path = Path.of("src/test/resources/fixtures/natjav", name);
|
||||
try {
|
||||
return Files.readString(path, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("cannot read fixture " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The file's shell: the one node whose line range spans the whole file. Since the field tree
|
||||
* landed, a parse result also holds the module's fields and its USING placeholders.
|
||||
*/
|
||||
private static AstNode only(LanguageParser.ParseResult result) {
|
||||
List<AstNode> shells = result.nodes().stream()
|
||||
.filter(n -> !n.sourceFile().isEmpty() && n.startLine() == 1
|
||||
&& (n.type() == NodeType.MODULE || n.type() == NodeType.DATA_STRUCTURE)
|
||||
&& n.properties() != null && n.properties().containsKey("className"))
|
||||
.toList();
|
||||
assertEquals(1, shells.size(), "exactly one file shell");
|
||||
return shells.get(0);
|
||||
}
|
||||
|
||||
private static @Nullable String rawProp(AstNode node, String key) {
|
||||
Map<String, String> properties = node.properties();
|
||||
return properties == null ? null : properties.get(key);
|
||||
}
|
||||
|
||||
private static String prop(AstNode node, String key) {
|
||||
Map<String, String> properties = node.properties();
|
||||
assertNotNull(properties, "node " + node.name() + " has no properties");
|
||||
return String.valueOf(properties.get(key));
|
||||
}
|
||||
|
||||
private AstNode parseFixture(String name) {
|
||||
return only(parser.parse("fixtures/natjav/" + name, fixture(name)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void languageIsNatjav() {
|
||||
assertEquals("natjav", parser.language());
|
||||
}
|
||||
|
||||
@Test
|
||||
void plainNaturalAnnotationTakesTheClassNameAsModuleName() {
|
||||
AstNode module = parseFixture("ACOMMON.java");
|
||||
|
||||
assertEquals(NodeType.MODULE, module.type());
|
||||
assertEquals("ACOMMON", module.name());
|
||||
assertEquals("natjav", module.language());
|
||||
assertEquals("PROGRAM", prop(module, "moduleKind"));
|
||||
assertEquals("KORIN", prop(module, "package"));
|
||||
assertEquals("ACOMMON", prop(module, "className"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The whole point of reading {@code @Natural(moduleName=...)}: the graph must key this module as
|
||||
* the Natural name {@code V-MIT04}, or a query written against the Natural original misses it.
|
||||
*/
|
||||
@Test
|
||||
void hyphenatedNaturalNameWinsOverTheJavaClassName() {
|
||||
AstNode module = parseFixture("V_MIT04.java");
|
||||
|
||||
assertEquals("V-MIT04", module.name());
|
||||
assertEquals("V_MIT04", prop(module, "className"));
|
||||
assertEquals("SUBPROGRAM", prop(module, "moduleKind"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The transpiler spells a leading {@code #} as {@code X}, which no string rule could invert
|
||||
* (plenty of modules legitimately start with X) — only the annotation carries the truth.
|
||||
*/
|
||||
@Test
|
||||
void hashPrefixedNaturalNameIsRecovered() {
|
||||
AstNode module = parseFixture("XABBRUCH.java");
|
||||
|
||||
assertEquals("#ABBRUCH", module.name());
|
||||
assertEquals("XABBRUCH", prop(module, "className"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataAreaIsADataStructureNamedWithoutTheDaSuffix() {
|
||||
AstNode area = parseFixture("YPUMPRUE_DA.java");
|
||||
|
||||
assertEquals(NodeType.DATA_STRUCTURE, area.type());
|
||||
assertEquals("YPUMPRUE", area.name());
|
||||
assertEquals("YPUMPRUE_DA", prop(area, "className"));
|
||||
assertEquals("DATA_AREA", prop(area, "moduleKind"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ddmIsRecognisedAndNamedWithoutTheDdmSuffix() {
|
||||
AstNode ddm = parseFixture("BANKEZ_DDM.java");
|
||||
|
||||
assertEquals(NodeType.MODULE, ddm.type());
|
||||
assertEquals("BANKEZ", ddm.name());
|
||||
assertEquals("DDM", prop(ddm, "moduleKind"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void migratedHeaderSuppliesDescriptionAndOwner() {
|
||||
AstNode module = parseFixture("MDBT74B.java");
|
||||
|
||||
assertEquals("LOESCHEN UMKONTIERUNGEN", prop(module, "description"));
|
||||
assertEquals("P36", prop(module, "owner"));
|
||||
assertEquals("KORIN.Mitarbeiter.Verwaltung", prop(module, "package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code XABBRUCH} carries a second {@code @FUNCTION} line further down, inside an inlined
|
||||
* copycode block. The header read must come from the class's own Javadoc, not from the first
|
||||
* {@code @FUNCTION} anywhere in the file.
|
||||
*/
|
||||
@Test
|
||||
void descriptionComesFromTheClassHeaderNotFromAnInlinedCopycode() {
|
||||
assertEquals("Zentrale Abbruch-Routinen", prop(parseFixture("XABBRUCH.java"), "description"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A module is its source file, as in Natural — the source and comment endpoints depend on the
|
||||
* shell spanning the whole file rather than just the type declaration.
|
||||
*/
|
||||
@Test
|
||||
void shellSpansTheWholeFile() {
|
||||
String content = fixture("MDBT74B.java");
|
||||
AstNode module = only(parser.parse("fixtures/natjav/MDBT74B.java", content));
|
||||
|
||||
assertEquals(1, module.startLine());
|
||||
assertEquals(LocMetrics.physicalLineCount(content), module.endLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* A hand-written Java class that slipped into a NatJav tree still gets a node — dropping it
|
||||
* would make the file invisible rather than merely unclassified — but claims no Natural kind.
|
||||
*/
|
||||
@Test
|
||||
void nonNatJavClassYieldsAModuleWithoutAKind() {
|
||||
AstNode module = only(parser.parse("Helper.java",
|
||||
"package util;\npublic class Helper { void run() {} }\n"));
|
||||
|
||||
assertEquals(NodeType.MODULE, module.type());
|
||||
assertEquals("Helper", module.name());
|
||||
assertNull(rawProp(module, "moduleKind"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stemStripsPathAndExtension() {
|
||||
assertEquals("V_MIT04", NatJavParser.stem("KORIN/Mitarbeiter/V_MIT04.java"));
|
||||
assertEquals("ACOMMON", NatJavParser.stem("ACOMMON.java"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.agenticcode.parsernatjav;
|
||||
|
||||
import com.agenticcode.parsercore.ast.model.AstNode;
|
||||
import com.agenticcode.parsercore.ast.model.EdgeType;
|
||||
import com.agenticcode.parsercore.ast.model.NodeType;
|
||||
import com.agenticcode.parsercore.ast.spi.LanguageParser.ParseResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* The XML wire payload. The Natural idioms ({@code COMPRESS '<' tag '>'}, {@code ADD-XML-LINE},
|
||||
* {@code YFRAMN07}) occur zero times in this corpus; the estate drives a framework subprogram
|
||||
* through a parameter area instead.
|
||||
*/
|
||||
class NatJavPayloadTest {
|
||||
|
||||
private final NatJavParser parser = new NatJavParser();
|
||||
|
||||
private static List<AstNode> payloads(ParseResult result) {
|
||||
return result.nodes().stream().filter(n -> n.type() == NodeType.PAYLOAD_FIELD).toList();
|
||||
}
|
||||
|
||||
private static Map<String, String> properties(AstNode node) {
|
||||
Map<String, String> properties = node.properties();
|
||||
assertNotNull(properties, node.name() + " has no properties");
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Test
|
||||
void tagAndContentBecomeAPayloadField() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
@Using KORIN.XMLLINP_DA XMLLINP_DA = localUsing(KORIN.XMLLINP_DA.class);
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TYP, "TI");
|
||||
assign(XMLLINP.TAG, "ADRZEILE1");
|
||||
assign(XMLLINP.INHALT, YPADR2.ZEILE);
|
||||
callnat("XMLLIN", XMLLINP);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
List<AstNode> fields = payloads(result);
|
||||
assertEquals(1, fields.size());
|
||||
Map<String, String> properties = properties(fields.get(0));
|
||||
assertEquals("ADRZEILE1", properties.get("tag"));
|
||||
assertEquals("YPADR2.ZEILE", properties.get("field"));
|
||||
assertEquals("TI", properties.get("lineType"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The read counterpart {@code XMLREAD} exists but is called zero times in the corpus, so every
|
||||
* tag is an emit — {@code REQUEST} is measured, not defaulted.
|
||||
*/
|
||||
@Test
|
||||
void directionIsRequest() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TAG, "T");
|
||||
assign(XMLLINP.INHALT, F);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertEquals("REQUEST", properties(payloads(result).get(0)).get("direction"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code XMLDYN} is the dynamic-length emitter — the same shape through its own parameter area,
|
||||
* and 1,793 of the corpus's tags go through it.
|
||||
*/
|
||||
@Test
|
||||
void theDynamicEmitterCountsToo() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLDYNP_DA.XMLDYNP XMLDYNP = XMLDYNP_DA.XMLDYNP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLDYNP.TAG, "BETRAG");
|
||||
assign(XMLDYNP.INHALT_200, xWA_BETRAG);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertEquals("BETRAG", properties(payloads(result).get(0)).get("tag"));
|
||||
assertEquals("xWA_BETRAG", properties(payloads(result).get(0)).get("field"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void moduleThatEmitsIsMarkedAsAnXmlWrapper() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TAG, "T");
|
||||
assign(XMLLINP.INHALT, F);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
AstNode module = result.nodes().stream()
|
||||
.filter(n -> n.type() == NodeType.MODULE && n.name().equals("W"))
|
||||
.findFirst().orElseThrow();
|
||||
assertEquals("true", properties(module).get("xmlWrapper"));
|
||||
assertTrue(result.edges().stream().anyMatch(e -> e.type() == EdgeType.CONTAINS
|
||||
&& e.sourceId().equals(module.id())
|
||||
&& payloads(result).stream().anyMatch(p -> p.id().equals(e.targetId()))),
|
||||
"the payload field hangs off its module");
|
||||
}
|
||||
|
||||
/**
|
||||
* A structural tag (element start/end) has no content assignment and must not invent one.
|
||||
*/
|
||||
@Test
|
||||
void tagWithoutContentYieldsNoPayloadField() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TYP, "ST");
|
||||
assign(XMLLINP.TAG, "EMPFAENGER");
|
||||
callnat("XMLLIN", XMLLINP);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertTrue(payloads(result).isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* A structural start/end tag is written with a blank content — 1,200 of the corpus's 1,493
|
||||
* literal contents. Each would otherwise become a payload field whose "field" is a space.
|
||||
*/
|
||||
@Test
|
||||
void structuralTagWithBlankContentIsNotAPayloadField() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TYP, "ST");
|
||||
assign(XMLLINP.TAG, "EMPFAENGER");
|
||||
assign(XMLLINP.INHALT, " ");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertTrue(payloads(result).isEmpty(), "a blank is the absence of content, not content");
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-blank literal <em>is</em> part of the payload contract — 293 in the corpus — and is kept,
|
||||
* marked so it is distinguishable from a field of the module's own data.
|
||||
*/
|
||||
@Test
|
||||
void constantContentIsKeptAndMarked() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TAG, "KENNZEICHEN");
|
||||
assign(XMLLINP.INHALT, "J");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Map<String, String> properties = properties(payloads(result).get(0));
|
||||
assertEquals("J", properties.get("field"));
|
||||
assertEquals("true", properties.get("constant"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTagYieldsNoPayloadField() {
|
||||
ParseResult result = parser.parse("W.java", """
|
||||
package X;
|
||||
import innowake.mee.natural.api.*;
|
||||
@Natural
|
||||
public class W extends NaturalProgram {
|
||||
public final KORIN.XMLLINP_DA.XMLLINP XMLLINP = XMLLINP_DA.XMLLINP;
|
||||
@Override
|
||||
public void run() {
|
||||
assign(XMLLINP.TAG, "");
|
||||
assign(XMLLINP.INHALT, F);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertTrue(payloads(result).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void aModuleWithoutXmlIsNotAWrapper() {
|
||||
ParseResult result = parser.parse("fixtures/natjav/MDBT74B.java",
|
||||
NatJavPayloadTest.class.getClassLoader() == null ? "" : readFixture());
|
||||
|
||||
assertTrue(payloads(result).isEmpty());
|
||||
}
|
||||
|
||||
private static String readFixture() {
|
||||
try {
|
||||
return java.nio.file.Files.readString(
|
||||
java.nio.file.Path.of("src/test/resources/fixtures/natjav/MDBT74B.java"));
|
||||
} catch (java.io.IOException e) {
|
||||
throw new java.io.UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* * header created during migration process *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION Steuerung Initialisierungen der Libspezifischen GDA's
|
||||
* * @OWNER P35
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* </pre>
|
||||
*/
|
||||
@Natural
|
||||
public class ACOMMON extends NaturalProgram {
|
||||
|
||||
//
|
||||
// Steuerungsprogramm für GDA-Initialisierungen
|
||||
//
|
||||
@Using KORIN.Diverse.Webservices.WSAIVP_DA WSAIVP_DA = localUsing(KORIN.Diverse.Webservices.WSAIVP_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Webservices.WSAIVP_DA) */
|
||||
public final KORIN.Diverse.Webservices.WSAIVP_DA.WSAIVP WSAIVP = WSAIVP_DA.WSAIVP;
|
||||
/* @@USING:END(KORIN.Diverse.Webservices.WSAIVP_DA) */
|
||||
@Using KORIN.Diverse.Webservices.WSAIVL_DA WSAIVL_DA = localUsing(KORIN.Diverse.Webservices.WSAIVL_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Webservices.WSAIVL_DA) */
|
||||
public final KORIN.Diverse.Webservices.WSAIVL_DA.WSAIVL WSAIVL = WSAIVL_DA.WSAIVL;
|
||||
/* @@USING:END(KORIN.Diverse.Webservices.WSAIVL_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWPDA_M_DA GWPDA_M_DA = localUsing(KORIN.Diverse.Framework.GWPDA_M_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
public final KORIN.Diverse.Framework.GWPDA_M_DA.MSG_INFO MSG_INFO = GWPDA_M_DA.MSG_INFO;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWLOGNAM_DA GWLOGNAM_DA = localUsing(KORIN.Diverse.Framework.GWLOGNAM_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_RETCODE xSTD_RETCODE = GWLOGNAM_DA.xSTD_RETCODE;
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_LOOPCODE xSTD_LOOPCODE = GWLOGNAM_DA.xSTD_LOOPCODE;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
@Local
|
||||
@L1 A xGRAWE_APPLIKATION = A(8 ,"#GRAWE-APPLIKATION");
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//
|
||||
reset(WSAIVP);
|
||||
assign(WSAIVP.AUFRUFCODE, WSAIVL.xC_GET_ITEM);
|
||||
assign(WSAIVP.BEZEICHNER, WSAIVL.xB_GRAWE_APPLIKATION);
|
||||
callnat("WSAIV", WSAIVP, MSG_INFO);
|
||||
if (NE(MSG_INFO.xxRETURN_CODE, xSTD_RETCODE.OK)) {
|
||||
fetchReturn("XABBRUCH",
|
||||
cp("ACOMMON: Fehler WSAIV-Aufruf:"),
|
||||
MSG_INFO.xxMSG_NR,
|
||||
MSG_INFO.xxMSG_DATA.get(1),
|
||||
"NO");
|
||||
}
|
||||
assign(xGRAWE_APPLIKATION, WSAIVP.INHALT);
|
||||
//
|
||||
|
||||
if (EQ($LIBRARY_ID(), "KORIN ")
|
||||
|| EQ($LIBRARY_ID(), "TEST")) {
|
||||
int decideCounter1 = 0;
|
||||
|
||||
if (EQ(xGRAWE_APPLIKATION, "KORIN ")) {
|
||||
decideCounter1++;
|
||||
fetch("ACC_KR");
|
||||
}
|
||||
/* * VALUE 'KARTEI ' FETCH 'ACC-KA'
|
||||
* * VALUE 'SCHADEN ' FETCH 'ACC-SB' */
|
||||
else if (EQ(xGRAWE_APPLIKATION, "HAGEL ")) {
|
||||
decideCounter1++;
|
||||
fetch("ACC_HA");
|
||||
}
|
||||
/* * VALUE 'RVRETRO ' FETCH 'ACC-RV'
|
||||
* * VALUE 'SCHDPRF ' FETCH 'ACC-SP' */
|
||||
else if (EQ(xGRAWE_APPLIKATION, "ABFRAGE")) {
|
||||
decideCounter1++;
|
||||
fetch("SYSMENU2");
|
||||
}
|
||||
|
||||
if (decideCounter1 == 0) {
|
||||
// Wenn noch nicht gesetzt -> Default Korin
|
||||
fetch("ACC_KR");
|
||||
}
|
||||
}
|
||||
else if (EQ($LIBRARY_ID(), "ABFRAGE")) {
|
||||
fetch("SYSMENU2");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package SYSTEM;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
import database.FDT.*;
|
||||
|
||||
@Natural(moduleName="BANKEZ")
|
||||
public class BANKEZ_DDM extends NaturalDdm {
|
||||
public static final DbN HIST_NR = reference(FDT0050.AF, "HIST-NR", NUMERIC, 3, 0);
|
||||
public static final DbGroup ORDNR = reference(FDT0050.DO, "ORDNR");
|
||||
public static final DbN VR_KEY = reference(FDT0050.AG, "VR-KEY", NUMERIC, 3, 0);
|
||||
public static final DbA OART = reference(FDT0050.AH, "OART");
|
||||
public static final DbN OBNR = reference(FDT0050.AI, "OBNR", NUMERIC, 8, 0);
|
||||
public static final DbGroup STATUS_INFO = reference(FDT0050.AK, "STATUS-INFO");
|
||||
public static final DbA STATUS_BYTE = reference(FDT0050.AJ, "STATUS-BYTE");
|
||||
public static final DbN STATUS_DERZEIT = reference(FDT0050.AN, "STATUS-DERZEIT", NUMERIC, 3, 0);
|
||||
public static final DbN ZAHLUNGSWEISE = reference(FDT0050.AO, "ZAHLUNGSWEISE", NUMERIC, 1, 0);
|
||||
public static final DbN INKASSOWEG = reference(FDT0050.AP, "INKASSOWEG", NUMERIC, 1, 0);
|
||||
public static final DbN FOLGEDATUM = reference(FDT0050.AT, "FOLGEDATUM", NUMERIC, 8, 0);
|
||||
public static final DbA BV_NUMMER = reference(FDT0050.AV, "BV-NUMMER");
|
||||
public static final DbGroup OPID_DATEN = reference(FDT0050.CK, "OPID-DATEN");
|
||||
public static final DbA OPID_VAE = reference(FDT0050.BR, "OPID-VAE");
|
||||
public static final DbN OPID_AM = reference(FDT0050.BS, "OPID-AM", NUMERIC, 8, 0);
|
||||
public static final DbN OPID_TIMN = reference(FDT0050.BT, "OPID-TIMN");
|
||||
public static final DbGroup BANKINKASSO = reference(FDT0050.CR, "BANKINKASSO");
|
||||
public static final DbN BANKLEITZAHL = reference(FDT0050.CS, "BANKLEITZAHL", NUMERIC, 15, 0);
|
||||
public static final DbA BANKKONTONUMMER = reference(FDT0050.CT, "BANKKONTONUMMER");
|
||||
public static final DbN BANKEINZUG_STORNO = reference(FDT0050.ID, "BANKEINZUG-STORNO", NUMERIC, 8, 0);
|
||||
public static final DbA BANK_OPID = reference(FDT0050.IE, "BANK-OPID");
|
||||
public static final DbN BANK_AM = reference(FDT0050.IF, "BANK-AM", NUMERIC, 8, 0);
|
||||
public static final DbN BANK_UM = reference(FDT0050.IG, "BANK-UM");
|
||||
public static final DbN PNR_STORNO_AB = reference(FDT0050.CX, "PNR-STORNO-AB", NUMERIC, 8, 0);
|
||||
public static final DbGroup GRUND_ADR = reference(FDT0050.DL, "GRUND-ADR");
|
||||
public static final DbN GRUND_PLZ = reference(FDT0050.DN, "GRUND-PLZ", NUMERIC, 6, 0);
|
||||
public static final DbA GRUND_NAME = reference(FDT0050.DQ, "GRUND-NAME");
|
||||
public static final DbA GRUND_ADRESSE = reference(FDT0050.DR, "GRUND-ADRESSE");
|
||||
public static final DbA GRUND_ERGAENZUNG = reference(FDT0050.DS, "GRUND-ERGAENZUNG");
|
||||
public static final DbA GRUND_POSTNATION = reference(FDT0050.G6, "GRUND-POSTNATION");
|
||||
public static final DbA GRUND_POSTORT = reference(FDT0050.H8, "GRUND-POSTORT");
|
||||
public static final DbA INFO_EXTERN = reference(FDT0050.H9, "INFO-EXTERN");
|
||||
public static final DbA ORDNUNGSBEGRIFF = reference(FDT0050.DE, "ORDNUNGSBEGRIFF");
|
||||
public static final DbGroup AS_TAB = reference(FDT0050.CJ, "AS-TAB");
|
||||
public static final DbA VTAB = reference(FDT0050.CQ, "VTAB");
|
||||
public static final DbA ABZUG_STATUS = reference(FDT0050.FU, "ABZUG-STATUS");
|
||||
public static final DbA KZ_FELD = reference(FDT0050.D8, "KZ-FELD");
|
||||
public static final DbP SUB_VR_KEY = reference(FDT0050.IM, "SUB-VR-KEY");
|
||||
public static final DbB TIMESTMP = reference(FDT0050.JJ, "TIMESTMP");
|
||||
public static final DbA BANK_BIC = reference(FDT0050.KD, "BANK-BIC");
|
||||
public static final DbA BANK_IBAN = reference(FDT0050.KE, "BANK-IBAN");
|
||||
public static final DbA NAME_KONTOINHABER = reference(FDT0050.KF, "NAME-KONTOINHABER");
|
||||
public static final DbB SPBANKEZ_VR = reference(FDT0050.JS, "SPBANKEZ-VR", BINARY, 10);
|
||||
public static final DbA SPBANK_BIC_VR = reference(FDT0050.KX, "SPBANK-BIC-VR");
|
||||
public static final DbA SPOPID = reference(FDT0050.MB, "SPOPID");
|
||||
public static final DbA DSGVO_STATUS = reference(FDT0050.MG, "DSGVO-STATUS");
|
||||
|
||||
{
|
||||
setDbId(0);
|
||||
setFileName(50);
|
||||
setFdt(FDT0050.class);
|
||||
addDescriptor(BANKLEITZAHL);
|
||||
addDescriptor(ABZUG_STATUS);
|
||||
addDescriptor(BANK_BIC);
|
||||
addDescriptor(BANK_IBAN);
|
||||
addSuppression(ORDNUNGSBEGRIFF, Suppression.NullValueSuppression);
|
||||
addSuppression(SPBANKEZ_VR, Suppression.NullValueSuppression);
|
||||
addSuppression(SPBANK_BIC_VR, Suppression.NullValueSuppression);
|
||||
addSuppression(SPOPID, Suppression.NullValueSuppression);
|
||||
addSuppression(DSGVO_STATUS, Suppression.NullValueSuppression);
|
||||
addHeader(HIST_NR, "HIST/-NR");
|
||||
addHeader(VR_KEY, "VR");
|
||||
addHeader(OART, "ART");
|
||||
addHeader(OBNR, "OBNR");
|
||||
addHeader(STATUS_INFO, "STATUS/INFO");
|
||||
addHeader(STATUS_BYTE, "STAT");
|
||||
addHeader(STATUS_DERZEIT, "STATUS/DERZEIT");
|
||||
addHeader(ZAHLUNGSWEISE, "ZW");
|
||||
addHeader(INKASSOWEG, "INK/WEG");
|
||||
addHeader(FOLGEDATUM, "FOLGE");
|
||||
addHeader(BV_NUMMER, "BV");
|
||||
addHeader(OPID_AM, "VAE-/AM");
|
||||
addHeader(OPID_TIMN, "VAE-/TIME");
|
||||
addHeader(BANKEINZUG_STORNO, "BKEZ-STO");
|
||||
addHeader(BANK_OPID, "BKEZ/OPID");
|
||||
addHeader(ABZUG_STATUS, "ABZ/STA");
|
||||
}
|
||||
}
|
||||
143
ac-parser-natjav/src/test/resources/fixtures/natjav/CDDCINP.java
Normal file
143
ac-parser-natjav/src/test/resources/fixtures/natjav/CDDCINP.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package CSTENTW.HELP;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ********************************************
|
||||
* * *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION HELP-Routinen & Diverses
|
||||
* * @OWNER P01
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ********************************************
|
||||
* ***********************************************************************
|
||||
* Program : CDDCINP /* Input new direct command
|
||||
* System : NATURAL-CONSTRUCT
|
||||
* Application: All
|
||||
* Written on : 27 Jan. 1993
|
||||
* Description: This subprogram is normally associated with an
|
||||
* : extended PF-Key that is used to pop-up a direct
|
||||
* : command line.
|
||||
* :
|
||||
* History
|
||||
* ***********************************************************************
|
||||
* </pre>
|
||||
*/
|
||||
@Natural
|
||||
public class CDDCINP extends NaturalSubprogram {
|
||||
|
||||
@Using CDGETDCA_DA CDGETDCA_DA = parameterUsing(CDGETDCA_DA.class);
|
||||
/* @@USING:BEGIN(CDGETDCA_DA) */
|
||||
public final CDGETDCA_DA.CDGETDCA CDGETDCA = CDGETDCA_DA.CDGETDCA;
|
||||
/* @@USING:END(CDGETDCA_DA) */
|
||||
@Using CDPDA_D_DA CDPDA_D_DA = parameterUsing(CDPDA_D_DA.class);
|
||||
/* @@USING:BEGIN(CDPDA_D_DA) */
|
||||
public final CDPDA_D_DA.DIALOG_INFO DIALOG_INFO = CDPDA_D_DA.DIALOG_INFO;
|
||||
/* @@USING:END(CDPDA_D_DA) */
|
||||
@Using CDPDA_M_DA CDPDA_M_DA = parameterUsing(CDPDA_M_DA.class);
|
||||
/* @@USING:BEGIN(CDPDA_M_DA) */
|
||||
public final CDPDA_M_DA.MSG_INFO MSG_INFO = CDPDA_M_DA.MSG_INFO;
|
||||
/* @@USING:END(CDPDA_M_DA) */
|
||||
@Using CDGLBTXL_DA CDGLBTXL_DA = localUsing(CDGLBTXL_DA.class);
|
||||
/* @@USING:BEGIN(CDGLBTXL_DA) */
|
||||
public final CDGLBTXL_DA.CDGLBTXL CDGLBTXL = CDGLBTXL_DA.CDGLBTXL;
|
||||
public final A CDGLBTXL_NAME = CDGLBTXL_DA.CDGLBTXL_NAME;
|
||||
/* @@USING:END(CDGLBTXL_DA) */
|
||||
@Using CDENVIRA_DA CDENVIRA_DA = localUsing(CDENVIRA_DA.class);
|
||||
/* @@USING:BEGIN(CDENVIRA_DA) */
|
||||
public final CDENVIRA_DA.CDENVIRA CDENVIRA = CDENVIRA_DA.CDENVIRA;
|
||||
/* @@USING:END(CDENVIRA_DA) */
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
format(0, KD(false));
|
||||
setKey(all());
|
||||
/* @INCLUDE CCSETHLP */
|
||||
/* @@INCLUDE:BEGIN(CCSETHLP) */
|
||||
/* *LCM-HEADER BEGIN ******************************************** */
|
||||
/* * *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION HELP-Routinen & Diverses
|
||||
* * @OWNER P01
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ********************************************
|
||||
* Help key must be hardcoded. Change PF-KEY value to change default
|
||||
* help key. */
|
||||
setKey(PF1, HELP);
|
||||
/* @@INCLUDE:END(CCSETHLP) */
|
||||
perform("CAPTURE_RESTORE_ENVIRONMENT");
|
||||
setControl(cp("WBL79C4FB18/0"));
|
||||
callnat("CDUTRANS", CDGLBTXL.CDUTRANS.get($), CDGLBTXL_NAME, MSG_INFO);
|
||||
input(withText(MSG_INFO.xxMSG_NR, MSG_INFO.xxMSG_DATA.get(1), MSG_INFO.xxMSG_DATA.get(2), MSG_INFO.xxMSG_DATA.get(3)), AD("MI'_'"), IP(false), CDGLBTXL.xDIRECT_CMD_TXT, AD("O"),
|
||||
CDGETDCA.xDIRECT_COMMAND, AL(58), AD("M'_'"), HE("CDDCHELP"));
|
||||
reset(MSG_INFO.xxMSG);
|
||||
if (NE($PF_KEY(), "ENTR")) {
|
||||
reset(CDGETDCA.xDIRECT_COMMAND);
|
||||
assign(MSG_INFO.xxMSG_NR, 8040); // Function canceled using:1:
|
||||
assign(MSG_INFO.xxMSG_DATA.get(1), $PF_KEY());
|
||||
perform("SET_USER_MSG");
|
||||
setControl("K0"); // Set *PF-KEY = ENTR
|
||||
perform("CAPTURE_RESTORE_ENVIRONMENT");
|
||||
escapeRoutineImmediate();
|
||||
}
|
||||
perform("CAPTURE_RESTORE_ENVIRONMENT");
|
||||
// SET-USER-MSG
|
||||
}
|
||||
|
||||
/*
|
||||
* *********************************************************************** */
|
||||
void CAPTURE_RESTORE_ENVIRONMENT() { // CAPTURE-RESTORE-ENVIRONMENT
|
||||
/* ***********************************************************************
|
||||
*/
|
||||
/* @INCLUDE CCENVIR */
|
||||
/* @@INCLUDE:BEGIN(CCENVIR) */
|
||||
/* *LCM-HEADER BEGIN ******************************************** */
|
||||
/* * *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION HELP-Routinen & Diverses
|
||||
* * @OWNER P01
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ********************************************
|
||||
*
|
||||
* This subprogram is used to capture and restore the current window
|
||||
* setting, pagesize etc. in a online dialog environment. The first
|
||||
* call of CDENVIR captures the current environment, the second call
|
||||
* restores it to what it was. */
|
||||
if (GT($LEVEL(), 1)) {
|
||||
callnat("CDENVIR", CDENVIRA);
|
||||
}
|
||||
/* @@INCLUDE:END(CCENVIR) */
|
||||
}
|
||||
|
||||
/*
|
||||
* *********************************************************************** */
|
||||
void FINAL_PROCESSING() { // Required by CCDCOUT
|
||||
/* ***********************************************************************
|
||||
*/
|
||||
escapeRoutine();
|
||||
}
|
||||
|
||||
// CAPTURE-RESTORE-ENVIRONMENT
|
||||
/*
|
||||
* *********************************************************************** */
|
||||
void SET_USER_MSG() {
|
||||
/* ***********************************************************************
|
||||
*/
|
||||
callnat("CDUMSG", MSG_INFO);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="COMMON")
|
||||
public class COMMON_DA extends NaturalData {
|
||||
|
||||
@Global
|
||||
/*
|
||||
* GDA für LIBRARY KORIN INCLUSIVE
|
||||
* REDEFINITIONEN
|
||||
*/
|
||||
@L1 A gPROGRAMM = A(8 ,"+PROGRAMM");
|
||||
@L1 A gBPOL_PARM = A(17 ,"+BPOL-PARM");
|
||||
@L1 N gBBST_POLNR = N(9,0 ,"+BBST-POLNR");
|
||||
@L1 A gBBST_ART = A(9 ,"+BBST-ART");
|
||||
@L1 A gABTEILUNG = A(5 ,"+ABTEILUNG");
|
||||
@L1 A gPASSWORD = A(8 ,"+PASSWORD");
|
||||
@L1 A gGETMAIL = A(1 ,"+GETMAIL");
|
||||
@L1 A gERROR_PROGRAM = A(8 ,"+ERROR-PROGRAM");
|
||||
@L1 A gERROR_STAT = A(1 ,"+ERROR-STAT");
|
||||
@L1 N gERROR_LINE = N(4,0 ,"+ERROR-LINE");
|
||||
@L1 N gERROR_NR = N(4,0 ,"+ERROR-NR");
|
||||
@L1 A gERGEBNIS = A(79 ,"+ERGEBNIS");
|
||||
@L1 A gSPOOL_PARM = A(80 ,"+SPOOL-PARM");
|
||||
@L1 Redefine gSPOOL_PARM;
|
||||
@L2 A xSPUSER = A(8 ,"#SPUSER");
|
||||
@L2 A xSPDEST = A(8 ,"#SPDEST");
|
||||
@L2 A xSPFORM = A(1 ,"#SPFORM");
|
||||
@L1 A gINIT_VAR = A(240 ,"+INIT-VAR");
|
||||
@L1 Redefine gINIT_VAR; // REDEF. BEGIN : +INIT-VAR
|
||||
@L2 A xRA_INITVAR_GETMAIL = A(1 ,"#RA-INITVAR-GETMAIL");// GETMAIL duchgeführt Y/N
|
||||
@L2 N xINIT_DATIME = N(15,0 ,"#INIT-DATIME");// STARTUP Datum/Zeitder
|
||||
@L2 Redefine xINIT_DATIME; // letzten Startup-Durchführung
|
||||
@L3 N xINIT_DATE = N(8,0 ,"#INIT-DATE");// "YYYYMMDDHHMMSSZ"
|
||||
@L3 Redefine xINIT_DATE;
|
||||
@L4 A xINIT_DATE_ALPHA = A(8 ,"#INIT-DATE-ALPHA");
|
||||
@L3 N xINIT_TIME = N(7,0 ,"#INIT-TIME");
|
||||
@L3 Redefine xINIT_TIME;
|
||||
@L4 A xINIT_TIME_ALPHA = A(7 ,"#INIT-TIME-ALPHA");
|
||||
@L2 A xINIT_USER = A(8 ,"#INIT-USER"); // STARTUP U-ID d. Current LOGON
|
||||
@L2 A xINIT_ID = A(8 ,"#INIT-ID"); // STARTUP Terminal-ID
|
||||
@L2 A xINIT_GROUP = A(8 ,"#INIT-GROUP"); // STARTUP GROUP-ID, über die
|
||||
/* /* gelinkt */
|
||||
@L2 A xSTARTUP_PROGRAM = A(8 ,"#STARTUP-PROGRAM");// STARTUP Startup-Programm
|
||||
@L2 A xRESTART_PROGRAM = A(8 ,"#RESTART-PROGRAM");// STARTUP Restart-Programm
|
||||
@L2 A xERROR_PROGRAM = A(8 ,"#ERROR-PROGRAM");// #ERR letztes fehlerhaftes PRG
|
||||
/* /* STARTUP Error-Programm */
|
||||
@L2 A xRA_INITVAR_CICS_NAME = A(8 ,"#RA-INITVAR-CICS-NAME");// STARTUP und APPLID
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME; // "CSWVCICS"/"CSWVCICT"
|
||||
@L3 A xCICSNAME = A(8 ,"#CICSNAME"); // Redef. aus Namensgründen
|
||||
@L2 Redefine xRA_INITVAR_CICS_NAME;
|
||||
@L3 A xCICS_NAME = A(8 ,"#CICS-NAME"); // Redef. aus Namensgründen
|
||||
@L2 A FILLER = A(8 ,"FILLER");
|
||||
@L2 A xUSER_NAME = A(32 ,"#USER-NAME"); // STARTUP User-Name
|
||||
@L2 A xAPPL_NAME = A(32 ,"#APPL-NAME"); // STARTUP Anwendungs-Name
|
||||
@L2 A xSYS_SCHALTER = A(8 ,"#SYS-SCHALTER");// DIV SYSTEM-Schalter
|
||||
@L2 Redefine xSYS_SCHALTER;
|
||||
@L3 A xSYS_RESTART = A(1 ,"#SYS-RESTART");// #ERR bei Absturz Restart Y/N
|
||||
@L3 A xSYS_CANCEL = A(1 ,"#SYS-CANCEL");// #CAN BATCH CANCELNin #ERROR
|
||||
@L3 A xERROR_STAT = A(1 ,"#ERROR-STAT");// #ERR letzter Fehler-Status
|
||||
@L3 A xERROR_OPTN = A(1 ,"#ERROR-OPTN");// #ERR RETRY ON ERRORS Y/N
|
||||
@L3 A xERROR_SAME = A(1 ,"#ERROR-SAME");// #ERR same ERROR wie zuvor Y/N
|
||||
/* 0=NEIN 1=PROG 2=NUMMER 3=BEIDE */
|
||||
@L3 A xERROR_LOG = A(1 ,"#ERROR-LOG"); // #ERR N=kein ERROR-Logging
|
||||
@L3 A xMONITOR = A(1 ,"#MONITOR"); // STARTUP B=BATCH, C=CICS
|
||||
/* /* P=COMPLETE */
|
||||
@L3 A FILLER2 = A(1 ,"FILLER2");
|
||||
@L2 A xERROR_PROG = A(8 ,"#ERROR-PROG");
|
||||
@L2 N xERROR_NR = N(4,0 ,"#ERROR-NR"); // #ERR letzte Fehlernummer
|
||||
@L2 Redefine xERROR_NR;
|
||||
@L3 A xERRAX = A(4 ,"#ERRAX");
|
||||
@L2 N xERROR_LINE = N(4,0 ,"#ERROR-LINE"); // #ERR letzte fehlerhafte Zeile
|
||||
@L2 A xMESSAGE = A(72 ,"#MESSAGE"); // #ERR Fehlermeldung
|
||||
@L2 Redefine xMESSAGE;
|
||||
@L3 A xMANF = A(1 ,"#MANF");
|
||||
@L3 A STAR = A(1 ,"STAR");
|
||||
@L3 A RESTMSG = A(70 ,"RESTMSG");
|
||||
@L1 A gBERECHTIGUNGEN = A("+BERECHTIGUNGEN");
|
||||
@L1 A gVSU = A(250 ,"+VSU");
|
||||
@L1 A gKIS = A(250 ,"+KIS");
|
||||
@L1 Redefine gKIS; // REDEF. BEGIN : +KIS
|
||||
@L2 N xRN_KIS_ZAHL = N(3,0 ,"#RN-KIS-ZAHL");
|
||||
@L2 B xRA_KIS_KUISNT = B(3 ,"#RA-KIS-KUISNT").defineArray(range(1, 20));
|
||||
@L2 Redefine xRA_KIS_KUISNT; // REDEF. BEGIN : #RA-KIS-KUISNT
|
||||
@L3 B xRA_KIS_KUISN = B(3 ,"#RA-KIS-KUISN");
|
||||
@L2 N xRN_KIS_OVZL = N(3,0 ,"#RN-KIS-OVZL");
|
||||
@L2 B xRA_KIS_OVISN = B(3 ,"#RA-KIS-OVISN").defineArray(range(1, 60));
|
||||
@L1 A gUMS = A(250 ,"+UMS");
|
||||
@L1 Redefine gUMS; // REDEF. BEGIN : +UMS
|
||||
@L2 A xRA_UMS_CODE = A(8 ,"#RA-UMS-CODE");
|
||||
@L2 P xRN_UMS_LAST_VR = P(3,0 ,"#RN-UMS-LAST-VR");
|
||||
@L2 A xRA_UMS_MD_KZ1_BKLZ_KZ = A(1 ,"#RA-UMS-MD-KZ1-BKLZ-KZ");
|
||||
@L2 A xRA_UMS_MD_KZ1_TARIF = A(1 ,"#RA-UMS-MD-KZ1-TARIF");
|
||||
@L2 A xRA_UMS_WAEHRUNG_MD = A(3 ,"#RA-UMS-WAEHRUNG-MD");
|
||||
@L2 N xRN_UMS_LAST_LOAD = N(6,0 ,"#RN-UMS-LAST-LOAD");
|
||||
@L2 A xUMS_VORG_OPID = A(3 ,"#UMS-VORG-OPID");
|
||||
@L2 A xUMS_FORCE = A(1 ,"#UMS-FORCE"); // Beharrung auf eigene
|
||||
/* * /* PF-Tasten-Belegung */
|
||||
@L2 N xUMS_SNR_FIL = N(2,0 ,"#UMS-SNR-FIL");// Schaden-Filial-Nummer
|
||||
@L2 A xUMS_SNR_KREIS = A(2 ,"#UMS-SNR-KREIS");// Schaden-Kreis zB.:'SA'
|
||||
@L2 N xUMS_SNR_JAHR = N(4,0 ,"#UMS-SNR-JAHR");// Schaden-Jahr
|
||||
@L2 N xUMS_SNR_LFD_NR = N(6,0 ,"#UMS-SNR-LFD-NR");// Schaden-Laufende-Nummer
|
||||
@L2 A xUMS_SNR_STATUS = A(1 ,"#UMS-SNR-STATUS");// Status-Byte 'O','S','H'
|
||||
@L2 A xUMS_ART = A(2 ,"#UMS-ART"); // Schadenart
|
||||
@L2 A xUMS_REFERENT = A(8 ,"#UMS-REFERENT");// Referent
|
||||
@L2 A xUMS_EVI_TYP = A(1 ,"#UMS-EVI-TYP"); // eigen oder fremd-Vi
|
||||
@L2 P xUMS_IDENTCOUNTER = P(5,0 ,"#UMS-IDENTCOUNTER");
|
||||
@L2 P xUMS_OUTPUTNR = P(5,0 ,"#UMS-OUTPUTNR");
|
||||
@L2 P xUMS_EREIGNIS_DT = P(8,0 ,"#UMS-EREIGNIS-DT");// nur für LV (= Stornodatum)
|
||||
@L2 P xUMS_SCN_HIST = P(3,0 ,"#UMS-SCN-HIST");// merken Hist-Lesestand
|
||||
@L2 B xUMS_ANZEIGEDAT_KPL = B(3 ,"#UMS-ANZEIGEDAT-KPL");// für Aufruf SK10
|
||||
@L2 N xUMS_SKT_FIL = N(2,0 ,"#UMS-SKT-FIL");// Filial-Nummer Kontostamm
|
||||
@L2 A xUMS_SKT_KREIS = A(2 ,"#UMS-SKT-KREIS");// Schaden-Kreis Kontostamm
|
||||
@L2 N xUMS_SKT_JAHR = N(4,0 ,"#UMS-SKT-JAHR");// Schaden-Jahr Kontostamm
|
||||
@L2 N xUMS_SKT_LFD_NR = N(6,0 ,"#UMS-SKT-LFD-NR");// Schaden-Lfd-Nr Kontostamm
|
||||
@L2 N xUMS_SKT_BRAN = N(2,0 ,"#UMS-SKT-BRAN");// Schaden-Branche Kontostamm
|
||||
@L2 A xUMS_WORKSTATION_ID = A(8 ,"#UMS-WORKSTATION-ID");// Workstation (OD) *** frei ***
|
||||
@L2 A xRA_UMS_BRGR = A(1 ,"#RA-UMS-BRGR").defineArray(range(1,3));
|
||||
@L2 A xUMS_WF_ABT = A(8 ,"#UMS-WF-ABT"); // Workflow-Abteilung(für OD)
|
||||
@L2 A xUMS_WF_STAT = A(8 ,"#UMS-WF-STAT"); // Workflow-Station(für OD)
|
||||
@L2 A xUMS_WF_OPID = A(8 ,"#UMS-WF-OPID"); // Workflow-Opid(für OD)
|
||||
@L2 P xUMS_WF_SORT_DOKU_ART = P(3,0 ,"#UMS-WF-SORT-DOKU-ART").defineArray(range(1,5));// WF-Dokument(für OD)
|
||||
@L2 P xUMS_KEYCOUNTER = P(5,0 ,"#UMS-KEYCOUNTER").defineArray(range(1,10));// Identcounter aus File111
|
||||
@L2 A xUMS_SKT_BELEGTYP = A(1 ,"#UMS-SKT-BELEGTYP");// Schaden-Konto-Belegtyp
|
||||
@L2 P xUMS_SKT_POS_IDC = P(5,0 ,"#UMS-SKT-POS-IDC");// Identcounter Pos.Buchung-SKT
|
||||
@L2 P xUMS_SKT_BRAN_IDC = P(5,0 ,"#UMS-SKT-BRAN-IDC");// Identcounter Bran.Buchung-SKT
|
||||
@L2 P xUMS_SKT_ABST_IDC = P(5,0 ,"#UMS-SKT-ABST-IDC");// Identcounter Abst.Buchung-SKT
|
||||
@L2 A xUMS_TEXT_FN95 = A(1 ,"#UMS-TEXT-FN95");// Text-Option im FN95
|
||||
@L2 A xUMS_REGRESS_RATE = A(2 ,"#UMS-REGRESS-RATE");// Schaden-Regreß oder -Rate
|
||||
@L2 P xUMS_REGRESS_NR = P(6,0 ,"#UMS-REGRESS-NR");// Schaden-Regreß-Nr.
|
||||
@L2 A xUMS_DAT2_FN95 = A(1 ,"#UMS-DAT2-FN95");// 2. Datumszeile FN95 optional
|
||||
@L2 N xUMS_PC_DAT = N(8,0 ,"#UMS-PC-DAT"); // Pc-Antragsdatum
|
||||
@L2 N xUMS_PC_NUM = N(4,0 ,"#UMS-PC-NUM"); // Pc-Antragsnummer
|
||||
@L2 A xUMS_DRUCKER_ID = A(10 ,"#UMS-DRUCKER-ID");// Drucker-Id (OD)
|
||||
@L2 A xUMS_KEY_STAT_DI32 = A(8 ,"#UMS-KEY-STAT-DI32");// Station für AufrufDI32 (OD)
|
||||
@L2 B xUMS_SKT_ANZEIGEDAT_KPL = B(3 ,"#UMS-SKT-ANZEIGEDAT-KPL");// Anzeigedatum-KPL für Sammelb.
|
||||
@L2 P xUMS_WINDOW_NUM = P(2,0 ,"#UMS-WINDOW-NUM");// Image-Display (OD)
|
||||
@L2 P xUMS_STORNO_GRUND = P(2,0 ,"#UMS-STORNO-GRUND");// stornogrund (nur LV)
|
||||
@L2 A xUMS_DOI_KEY_ART = A(2 ,"#UMS-DOI-KEY-ART");// Kz f. Einschränkung Di30 (OD)
|
||||
@L2 P xUMS_SKT_WERTEDATUM = P(8,0 ,"#UMS-SKT-WERTEDATUM");// Schaden-Konto-Wertedatum
|
||||
@L2 P xUMS_SKT_BANKSCHLUESSEL = P(2,0 ,"#UMS-SKT-BANKSCHLUESSEL");// Schaden-Konto-Bankschlüssel
|
||||
@L2 A xUMS_REST = A(28 ,"#UMS-REST"); // Bytes frei
|
||||
@L1 A gPEL = A(250 ,"+PEL");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 A xRA_PEL_ABT = A(1 ,"#RA-PEL-ABT");
|
||||
@L2 N xRN_PEL_VPOL = N(8,0 ,"#RN-PEL-VPOL");
|
||||
@L2 N xRN_PEL_KPNR = N(8,0 ,"#RN-PEL-KPNR");
|
||||
@L2 N xRN_PEL_GT = N(8,0 ,"#RN-PEL-GT");
|
||||
@L2 N xRN_PEL_BR1 = N(8,0 ,"#RN-PEL-BR1");
|
||||
@L2 N xRN_PEL_BR2 = N(8,0 ,"#RN-PEL-BR2");
|
||||
@L2 N xRN_PEL_BUCH = N(8,0 ,"#RN-PEL-BUCH");
|
||||
@L2 N xRN_PEL_EINL = N(6,0 ,"#RN-PEL-EINL");
|
||||
@L2 A xRA_PEL_FREI = A(2 ,"#RA-PEL-FREI");
|
||||
@L2 N xRN_PEL_TEXT = N(8,0 ,"#RN-PEL-TEXT");
|
||||
@L2 N xRN_PEL_STAT = N(8,0 ,"#RN-PEL-STAT");
|
||||
@L2 N xRN_PEL_AGT = N(8,0 ,"#RN-PEL-AGT");
|
||||
@L2 N xRN_PEL_ABR1 = N(8,0 ,"#RN-PEL-ABR1");
|
||||
@L2 N xRN_PEL_ABR2 = N(8,0 ,"#RN-PEL-ABR2");
|
||||
@L2 N xRN_PEL_ATXT = N(8,0 ,"#RN-PEL-ATXT");
|
||||
@L2 N xRN_PEL_INDX = N(2,0 ,"#RN-PEL-INDX");
|
||||
@L2 N xRN_PEL_VDAT = N(6,0 ,"#RN-PEL-VDAT");
|
||||
@L2 A xRA_PEL_EFKZ = A(1 ,"#RA-PEL-EFKZ");
|
||||
@L2 A xRA_PEL_DRUC = A(4 ,"#RA-PEL-DRUC");
|
||||
@L2 A xRA_PEL_VART = A(3 ,"#RA-PEL-VART");
|
||||
@L2 N xRN_PEL_KREI = N(3,0 ,"#RN-PEL-KREI");
|
||||
@L2 A xRA_PEL_VAE = A(3 ,"#RA-PEL-VAE");
|
||||
@L2 A xRA_PEL_BNEU = A(3 ,"#RA-PEL-BNEU");
|
||||
@L2 A xRA_PEL_FILL118 = A(118 ,"#RA-PEL-FILL118");
|
||||
@L2 A xRA_PEL_MENU = A(2 ,"#RA-PEL-MENU");
|
||||
@L1 Redefine gPEL; // REDEF. BEGIN : +PEL
|
||||
@L2 N xRN_KFZ_MENR = N(1,0 ,"#RN-KFZ-MENR");
|
||||
@L2 N xRN_KFZ_IEVI = N(3,0 ,"#RN-KFZ-IEVI");
|
||||
@L2 N xRN_KFZ_IVV = N(3,0 ,"#RN-KFZ-IVV");
|
||||
@L2 N xRN_KFZ_HIST = N(3,0 ,"#RN-KFZ-HIST");
|
||||
@L2 N xRN_KFZ_STND = N(3,0 ,"#RN-KFZ-STND");
|
||||
@L2 A xRA_KFZ_EVBR = A(30 ,"#RA-KFZ-EVBR");
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 A xRA_KFZ_BRA = A(3 ,"#RA-KFZ-BRA").defineArray(range(1, 10));
|
||||
@L2 Redefine xRA_KFZ_EVBR;
|
||||
@L3 N xRN_KFZ_BRA1 = N(3,0 ,"#RN-KFZ-BRA1");
|
||||
@L3 N xRN_KFZ_BRA2 = N(3,0 ,"#RN-KFZ-BRA2");
|
||||
@L3 N xRN_KFZ_BRA3 = N(3,0 ,"#RN-KFZ-BRA3");
|
||||
@L3 N xRN_KFZ_BRA4 = N(3,0 ,"#RN-KFZ-BRA4");
|
||||
@L3 N xRN_KFZ_BRA5 = N(3,0 ,"#RN-KFZ-BRA5");
|
||||
@L2 N xRN_KFZ_VDAT = N(8,0 ,"#RN-KFZ-VDAT");
|
||||
@L2 A xRA_KFZ_AREV = A(1 ,"#RA-KFZ-AREV");
|
||||
@L2 A xRA_KFZ_SABE = A(3 ,"#RA-KFZ-SABE");
|
||||
@L2 A xRA_KFZ_RAG = A(1 ,"#RA-KFZ-RAG");
|
||||
@L2 P xRN_KFZ_OZW = P(3,0 ,"#RN-KFZ-OZW");
|
||||
@L2 N xRN_KFZ_VVAE = N(8,0 ,"#RN-KFZ-VVAE");
|
||||
@L2 A xRA_KFZ_VEF = A(1 ,"#RA-KFZ-VEF");
|
||||
@L2 A xRA_KFZ_VVAR = A(3 ,"#RA-KFZ-VVAR");
|
||||
@L2 N xRN_KFZ_VEGD = N(8,0 ,"#RN-KFZ-VEGD");
|
||||
@L2 N xRN_KFZ_VELD = N(8,0 ,"#RN-KFZ-VELD");
|
||||
@L2 A xRA_KFZ_VDRU = A(4 ,"#RA-KFZ-VDRU");
|
||||
@L2 A xRA_KFZ_PZSG = A(3 ,"#RA-KFZ-PZSG");
|
||||
@L2 N xRN_KFZ_LESE = N(3,0 ,"#RN-KFZ-LESE");
|
||||
@L2 N xRN_KFZ_FZW = N(2,0 ,"#RN-KFZ-FZW");
|
||||
@L2 N xRN_KFZ_VI10 = N(2,0 ,"#RN-KFZ-VI10");
|
||||
@L2 N xRN_KFZ_VD = N(8,0 ,"#RN-KFZ-VD");
|
||||
@L2 N xRN_KFZ_VB = N(8,0 ,"#RN-KFZ-VB");
|
||||
@L2 A xRA_KFZ_KFKZ = A(11 ,"#RA-KFZ-KFKZ");
|
||||
@L2 N xRN_KFZ_AWDT = N(8,0 ,"#RN-KFZ-AWDT");
|
||||
@L2 A xRA_KFZ_AWKZ = A(1 ,"#RA-KFZ-AWKZ");
|
||||
@L2 N xRN_KFZ_AEND = N(8,0 ,"#RN-KFZ-AEND");
|
||||
@L2 A xRA_KFZ_VABT = A(3 ,"#RA-KFZ-VABT");
|
||||
@L2 A xRA_KFZ_OPID = A(3 ,"#RA-KFZ-OPID");
|
||||
@L2 N xRN_KFZ_DATV = N(8,0 ,"#RN-KFZ-DATV");
|
||||
@L2 N xRN_KFZ_DATB = N(8,0 ,"#RN-KFZ-DATB");
|
||||
@L2 A xRA_KFZ_FIL = A(2 ,"#RA-KFZ-FIL");
|
||||
@L2 N xRN_KFZ_KRBR = N(4,0 ,"#RN-KFZ-KRBR");
|
||||
@L2 Redefine xRN_KFZ_KRBR; // REDEF. BEGIN : #RN-KFZ-KRBR
|
||||
@L3 N xRN_KFZ_KR = N(2,0 ,"#RN-KFZ-KR");
|
||||
@L3 N xRN_KFZ_BR = N(2,0 ,"#RN-KFZ-BR");
|
||||
@L2 A xRA_KFZ_VAE1 = A(3 ,"#RA-KFZ-VAE1");
|
||||
@L2 A xRA_KFZ_VAE2 = A(3 ,"#RA-KFZ-VAE2");
|
||||
@L2 A xRA_KFZ_SUMM = A(1 ,"#RA-KFZ-SUMM");
|
||||
@L2 N xRN_KFZ_STAT = N(2,0 ,"#RN-KFZ-STAT");
|
||||
@L2 A xRA_KFZ_GSNA = A(10 ,"#RA-KFZ-GSNA");
|
||||
@L2 A xRA_KFZ_BVNR = A(7 ,"#RA-KFZ-BVNR");
|
||||
@L2 N xRN_KFZ_ANTR = N(8,0 ,"#RN-KFZ-ANTR");
|
||||
@L2 N xRN_KFZ_VINK = N(8,0 ,"#RN-KFZ-VINK");
|
||||
@L2 N xRN_KFZ_FPNR = N(3,0 ,"#RN-KFZ-FPNR");
|
||||
@L2 A xRA_KFZ_MAPA = A(1 ,"#RA-KFZ-MAPA");
|
||||
@L2 L xRL_KFZ_PRVV = L("#RL-KFZ-PRVV");
|
||||
@L2 L xRL_KFZ_ST01 = L("#RL-KFZ-ST01");
|
||||
@L2 L xRL_KFZ_AUTO = L("#RL-KFZ-AUTO");
|
||||
@L2 A xRA_KFZ_OPI2 = A(3 ,"#RA-KFZ-OPI2");
|
||||
@L2 L xRL_KFZ_BLND = L("#RL-KFZ-BLND");
|
||||
@L2 C xRC_KFZ_BLND = C("#RC-KFZ-BLND");
|
||||
@L2 A xRA_KFZ_ATXT = A(10 ,"#RA-KFZ-ATXT");
|
||||
@L2 A xRA_KFZ_KTXT = A(3 ,"#RA-KFZ-KTXT");
|
||||
@L2 A xRA_KFZ_AMNU = A(8 ,"#RA-KFZ-AMNU");
|
||||
@L2 A xRA_KFZ_MENU = A(2 ,"#RA-KFZ-MENU");
|
||||
@L1 A gISN = A(250 ,"+ISN");
|
||||
@L1 Redefine gISN; // REDEF. BEGIN : +ISN
|
||||
@L2 A xRA_ISN_ISN = A(5 ,"#RA-ISN-ISN");
|
||||
@L2 Redefine xRA_ISN_ISN; // REDEF. BEGIN : #RA-ISN-ISN
|
||||
@L3 P xRN_ISN_AKUN = P(9,0 ,"#RN-ISN-AKUN");
|
||||
@L2 P xRN_ISN_HKUN = P(9,0 ,"#RN-ISN-HKUN");
|
||||
@L2 P xRN_ISN_VKUN = P(9,0 ,"#RN-ISN-VKUN");
|
||||
@L2 P xRN_ISN_ORTE = P(9,0 ,"#RN-ISN-ORTE");
|
||||
@L2 P xRN_ISN_KORP = P(9,0 ,"#RN-ISN-KORP");
|
||||
@L2 P xRN_ISN_KTAB = P(9,0 ,"#RN-ISN-KTAB");
|
||||
@L2 P xRN_ISN_ABKU = P(9,0 ,"#RN-ISN-ABKU");
|
||||
@L2 P xRN_ISN_OPOL = P(9,0 ,"#RN-ISN-OPOL");
|
||||
@L2 P xRN_ISN_SPOL = P(9,0 ,"#RN-ISN-SPOL");
|
||||
@L2 P xRN_ISN_OEVI = P(9,0 ,"#RN-ISN-OEVI");
|
||||
@L2 P xRN_ISN_SEVI = P(9,0 ,"#RN-ISN-SEVI");
|
||||
@L2 P xRN_ISN_OVV = P(9,0 ,"#RN-ISN-OVV");
|
||||
@L2 P xRN_ISN_SVV = P(9,0 ,"#RN-ISN-SVV");
|
||||
@L2 P xRN_ISN_OBU = P(9,0 ,"#RN-ISN-OBU");
|
||||
@L2 P xRN_ISN_SBU = P(9,0 ,"#RN-ISN-SBU");
|
||||
@L2 P xRN_ISN_BTS = P(9,0 ,"#RN-ISN-BTS");
|
||||
@L2 P xRN_ISN_BTK = P(9,0 ,"#RN-ISN-BTK");
|
||||
@L2 P xRN_ISN_EKPO = P(9,0 ,"#RN-ISN-EKPO");
|
||||
@L2 P xRN_ISN_EKVV = P(9,0 ,"#RN-ISN-EKVV");
|
||||
@L2 P xRN_ISN_EKK1 = P(9,0 ,"#RN-ISN-EKK1");
|
||||
@L2 P xRN_ISN_OPOS = P(9,0 ,"#RN-ISN-OPOS");
|
||||
@L2 P xRN_ISN_SPOS = P(9,0 ,"#RN-ISN-SPOS");
|
||||
@L2 P xRN_ISN_EKG1 = P(9,0 ,"#RN-ISN-EKG1");
|
||||
@L2 P xRN_ISN_HGPO = P(9,0 ,"#RN-ISN-HGPO");
|
||||
@L2 P xRN_ISN_HGKO = P(9,0 ,"#RN-ISN-HGKO");
|
||||
@L2 P xRN_ISN_OLS = P(9,0 ,"#RN-ISN-OLS");
|
||||
@L2 P xRN_ISN_SLS = P(9,0 ,"#RN-ISN-SLS");
|
||||
@L2 P xRN_ISN_HLS = P(9,0 ,"#RN-ISN-HLS");
|
||||
@L2 P xRN_ISN_VDNR = P(9,0 ,"#RN-ISN-VDNR");
|
||||
@L2 P xRN_ISN_MDST = P(9,0 ,"#RN-ISN-MDST");
|
||||
@L2 P xRN_ISN_KTOS = P(9,0 ,"#RN-ISN-KTOS");
|
||||
@L2 P xRN_ISN_KTOO = P(9,0 ,"#RN-ISN-KTOO");
|
||||
@L2 P xRN_ISN_ORTS = P(9,0 ,"#RN-ISN-ORTS");
|
||||
@L2 P xRN_ISN_FPNR = P(9,0 ,"#RN-ISN-FPNR");
|
||||
@L2 P xRN_ISN_WA49 = P(9,0 ,"#RN-ISN-WA49");
|
||||
@L2 P xRN_ISN_SD34 = P(8,0 ,"#RN-ISN-SD34");
|
||||
@L2 P xRN_ISN_SPER = P(9,0 ,"#RN-ISN-SPER");
|
||||
@L2 P xRN_ISN_MDS = P(9,0 ,"#RN-ISN-MDS");
|
||||
@L2 P xRN_ISN_SUCH = P(9,0 ,"#RN-ISN-SUCH");
|
||||
@L2 P xRN_ISN_ABST = P(9,0 ,"#RN-ISN-ABST");
|
||||
@L2 P xRN_ISN_UMKON = P(9,0 ,"#RN-ISN-UMKON");
|
||||
@L2 P xRN_ISN_MUBU = P(9,0 ,"#RN-ISN-MUBU");// MUTTERBUCHUNG (SK)
|
||||
@L2 P xRN_ISN_RESERVE = P(9,0 ,"#RN-ISN-RESERVE").defineArray(range(1, 5));
|
||||
@L1 A gKUN = A(250 ,"+KUN");
|
||||
@L1 Redefine gKUN; // REDEF. BEGIN : +KUN
|
||||
@L2 A xRA_KUN_KUNA = A(27 ,"#RA-KUN-KUNA");
|
||||
@L2 A xRA_KUN_GHKZ = A(1 ,"#RA-KUN-GHKZ");
|
||||
@L2 N xRN_KUN_GHK1 = N(8,0 ,"#RN-KUN-GHK1");
|
||||
@L2 N xRN_KUN_GHK2 = N(8,0 ,"#RN-KUN-GHK2");
|
||||
@L2 N xRN_KUN_DATV = N(8,0 ,"#RN-KUN-DATV");
|
||||
@L2 N xRN_KUN_DATB = N(8,0 ,"#RN-KUN-DATB");
|
||||
@L2 A xRA_KUN_BRAX = A(30 ,"#RA-KUN-BRAX");
|
||||
@L2 N xRN_KUN_FILV = N(2,0 ,"#RN-KUN-FILV");
|
||||
@L2 N xRN_KUN_FILB = N(2,0 ,"#RN-KUN-FILB");
|
||||
@L2 N xRN_KUN_ANZEI = N(2,0 ,"#RN-KUN-ANZEI");
|
||||
@L2 N xRN_KUN_BILKR = N(2,0 ,"#RN-KUN-BILKR");
|
||||
@L2 N xRN_KUN_POLK = N(2,0 ,"#RN-KUN-POLK");
|
||||
@L2 N xRN_KUN_POLB = N(2,0 ,"#RN-KUN-POLB");
|
||||
@L2 A xRA_KUN_BANKKU = A(5 ,"#RA-KUN-BANKKU");
|
||||
@L2 A xRA_KUN_BANKOR = A(27 ,"#RA-KUN-BANKOR");
|
||||
@L2 N xRN_KUN_BANKLE = N(5,0 ,"#RN-KUN-BANKLE");
|
||||
@L2 N xRN_KUN_TDAT = N(8,0 ,"#RN-KUN-TDAT");
|
||||
@L2 N xRN_KUN_VR = N(3,0 ,"#RN-KUN-VR");
|
||||
@L2 N xRN_KUN_VGVR = N(3,0 ,"#RN-KUN-VGVR");
|
||||
@L2 N xRN_KUN_STAT = N(3,0 ,"#RN-KUN-STAT");
|
||||
@L2 N xRN_KUN_HGV = N(2,0 ,"#RN-KUN-HGV");
|
||||
@L2 A xRA_KUN_UG1V = A(2 ,"#RA-KUN-UG1V");
|
||||
@L2 A xRA_KUN_UG2V = A(2 ,"#RA-KUN-UG2V");
|
||||
@L2 A xRA_KUN_UG3V = A(2 ,"#RA-KUN-UG3V");
|
||||
@L2 A xRA_KUN_SPV = A(1 ,"#RA-KUN-SPV");
|
||||
@L2 N xRN_KUN_HGB = N(2,0 ,"#RN-KUN-HGB");
|
||||
@L2 A xRA_KUN_UG1B = A(2 ,"#RA-KUN-UG1B");
|
||||
@L2 A xRA_KUN_UG2B = A(2 ,"#RA-KUN-UG2B");
|
||||
@L2 A xRA_KUN_UG3B = A(2 ,"#RA-KUN-UG3B");
|
||||
@L2 A xRA_KUN_SPB = A(1 ,"#RA-KUN-SPB");
|
||||
@L2 A xRA_KUN_LEER1 = A(38 ,"#RA-KUN-LEER1");
|
||||
@L2 N xRN_KUN_OVGEM = N(5,0 ,"#RN-KUN-OVGEM");
|
||||
@L2 N xRN_KUN_OVOKZ = N(6,0 ,"#RN-KUN-OVOKZ");
|
||||
@L2 N xRN_KUN_OVOBK = N(4,0 ,"#RN-KUN-OVOBK");
|
||||
@L2 A xRA_KUN_OVNAM = A(15 ,"#RA-KUN-OVNAM");
|
||||
@L2 A xRA_KUN_LEER2 = A(4 ,"#RA-KUN-LEER2");
|
||||
@L2 A xRA_KUN_OVFIL = A(2 ,"#RA-KUN-OVFIL");
|
||||
@L2 A xRA_KUN_OVSA = A(1 ,"#RA-KUN-OVSA");
|
||||
@L1 A gKEY = A(250 ,"+KEY");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 N xRN_KEY_VKKZ = N(8,0 ,"#RN-KEY-VKKZ");
|
||||
@L2 A xRA_KEY_VVA = A(2 ,"#RA-KEY-VVA");
|
||||
@L2 N xRN_KEY_VVNR = N(8,0 ,"#RN-KEY-VVNR");
|
||||
@L2 N xRN_KEY_VHNR = N(3,0 ,"#RN-KEY-VHNR");
|
||||
@L2 N xRN_KEY_VGES = N(3,0 ,"#RN-KEY-VGES");
|
||||
@L2 N xRN_KEY_VRES = N(1,0 ,"#RN-KEY-VRES");
|
||||
@L2 N xRN_KEY_VVIN = N(2,0 ,"#RN-KEY-VVIN");
|
||||
@L2 N xRN_KEY_VBRA = N(2,0 ,"#RN-KEY-VBRA");
|
||||
@L2 N xRN_KEY_VBRN = N(1,0 ,"#RN-KEY-VBRN");
|
||||
@L2 A xRA_KEY_VBTART = A(1 ,"#RA-KEY-VBTART");
|
||||
@L2 A xRA_KEY_VBSTAT = A(1 ,"#RA-KEY-VBSTAT");
|
||||
@L2 N xRN_KEY_VUGES = N(3,0 ,"#RN-KEY-VUGES");
|
||||
@L2 N xRN_KEY_VBJAHR = N(2,0 ,"#RN-KEY-VBJAHR");
|
||||
@L2 N xRN_KEY_AKTONR = N(7,0 ,"#RN-KEY-AKTONR");
|
||||
@L2 Redefine xRN_KEY_AKTONR; // REDEF. BEGIN : #RN-KEY-AKTONR
|
||||
@L3 A xRA_KEY_VKTO = A(7 ,"#RA-KEY-VKTO");
|
||||
@L2 A xRA_KEY_KOSEL = A(1 ,"#RA-KEY-KOSEL");
|
||||
@L2 A xRA_KEY_LEER1 = A(4 ,"#RA-KEY-LEER1");
|
||||
@L2 N xRN_KEY_AKKZ = N(8,0 ,"#RN-KEY-AKKZ");
|
||||
@L2 A xRA_KEY_AVA = A(2 ,"#RA-KEY-AVA");
|
||||
@L2 N xRN_KEY_AVNR = N(8,0 ,"#RN-KEY-AVNR");
|
||||
@L2 N xRN_KEY_AHNR = N(3,0 ,"#RN-KEY-AHNR");
|
||||
@L2 N xRN_KEY_AGES = N(3,0 ,"#RN-KEY-AGES");
|
||||
@L2 N xRN_KEY_ARES = N(1,0 ,"#RN-KEY-ARES");
|
||||
@L2 N xRN_KEY_AVIN = N(2,0 ,"#RN-KEY-AVIN");
|
||||
@L2 N xRN_KEY_ABRA = N(2,0 ,"#RN-KEY-ABRA");
|
||||
@L2 N xRN_KEY_ABRN = N(1,0 ,"#RN-KEY-ABRN");
|
||||
@L2 A xRA_KEY_ABTART = A(1 ,"#RA-KEY-ABTART");
|
||||
@L2 A xRA_KEY_ABSTAT = A(1 ,"#RA-KEY-ABSTAT");
|
||||
@L2 N xRN_KEY_AUGES = N(3,0 ,"#RN-KEY-AUGES");
|
||||
@L2 N xRN_KEY_ABJAHR = N(2,0 ,"#RN-KEY-ABJAHR");
|
||||
@L2 N xRN_KEY_KTONR = N(7,0 ,"#RN-KEY-KTONR");
|
||||
@L2 N xRN_KEY_PROV = N(2,0 ,"#RN-KEY-PROV");
|
||||
@L2 N xRN_KEY_PROD = N(2,0 ,"#RN-KEY-PROD");
|
||||
@L2 A xRA_KEY_PRKZ = A(1 ,"#RA-KEY-PRKZ");
|
||||
@L2 N xRN_KEY_AFGS = N(3,0 ,"#RN-KEY-AFGS");
|
||||
@L2 A xRA_KEY_AFPO = A(14 ,"#RA-KEY-AFPO");
|
||||
@L2 N xRN_KEY_VFGS = N(3,0 ,"#RN-KEY-VFGS");
|
||||
@L2 A xRA_KEY_VFPO = A(14 ,"#RA-KEY-VFPO");
|
||||
@L2 N xRN_KEY_FBRA = N(2,0 ,"#RN-KEY-FBRA");
|
||||
@L2 N xRN_KEY_NOTD = N(6,0 ,"#RN-KEY-NOTD");
|
||||
@L2 Redefine xRN_KEY_NOTD;
|
||||
@L3 N xRN_KEY_NOTD_YYYY = N(4,0 ,"#RN-KEY-NOTD-YYYY");
|
||||
@L3 N xRN_KEY_NOTD_MM = N(2,0 ,"#RN-KEY-NOTD-MM");
|
||||
@L2 N xRN_KEY_LSNR = N(2,0 ,"#RN-KEY-LSNR");
|
||||
@L2 N xRN_KEY_KBJ = N(4,0 ,"#RN-KEY-KBJ");
|
||||
@L2 N xRN_KEY_KBJNR = N(1,0 ,"#RN-KEY-KBJNR");
|
||||
@L2 A xRA_KEY_ZNAM = A(31 ,"#RA-KEY-ZNAM");
|
||||
@L2 A xRA_KEY_VNAM = A(2 ,"#RA-KEY-VNAM");
|
||||
@L2 A xRA_KEY_STRA = A(1 ,"#RA-KEY-STRA");
|
||||
@L2 N xRN_KEY_HAUS = N(4,0 ,"#RN-KEY-HAUS");
|
||||
@L2 N xRN_KEY_POST = N(6,0 ,"#RN-KEY-POST");
|
||||
@L2 A xRA_KEY_KZEX = A(1 ,"#RA-KEY-KZEX");
|
||||
@L2 A xRA_KEY_ACHT = A(1 ,"#RA-KEY-ACHT");
|
||||
@L2 N xRN_KEY_ALFA = N(2,0 ,"#RN-KEY-ALFA");
|
||||
@L2 N xRN_KEY_BERU = N(5,0 ,"#RN-KEY-BERU");
|
||||
@L2 A xRA_KEY_LEER2 = A(4 ,"#RA-KEY-LEER2");
|
||||
@L2 A xRA_KEY_BRSL = A(1 ,"#RA-KEY-BRSL");
|
||||
@L2 A xRA_KEY_PSTA = A(1 ,"#RA-KEY-PSTA");
|
||||
@L2 A xRA_KEY_BRAW = A(1 ,"#RA-KEY-BRAW");
|
||||
@L2 N xRN_KEY_BRVO = N(2,0 ,"#RN-KEY-BRVO");
|
||||
@L2 N xRN_KEY_BRBI = N(2,0 ,"#RN-KEY-BRBI");
|
||||
@L2 N xRN_KEY_JRVO = N(2,0 ,"#RN-KEY-JRVO");
|
||||
@L2 N xRN_KEY_JRBI = N(2,0 ,"#RN-KEY-JRBI");
|
||||
@L2 A xRA_KEY_VNAW = A(1 ,"#RA-KEY-VNAW");
|
||||
@L2 A xRA_KEY_KEIG = A(1 ,"#RA-KEY-KEIG");
|
||||
@L2 A xRA_KEY_SBID = A(3 ,"#RA-KEY-SBID");
|
||||
@L2 A xRA_KEY_KURO = A(2 ,"#RA-KEY-KURO");
|
||||
@L2 A xRA_KEY_PRAE = A(1 ,"#RA-KEY-PRAE");
|
||||
@L2 A xRA_KEY_PROV = A(1 ,"#RA-KEY-PROV");
|
||||
@L2 A xRA_KEY_OFFE = A(1 ,"#RA-KEY-OFFE");
|
||||
@L2 A xRA_KEY_AKTU = A(1 ,"#RA-KEY-AKTU");
|
||||
@L2 A xRA_KEY_ALLE = A(1 ,"#RA-KEY-ALLE");
|
||||
@L2 A xRA_KEY_AOFA = A(1 ,"#RA-KEY-AOFA");
|
||||
@L2 N xRN_KEY_BUDT = N(8,0 ,"#RN-KEY-BUDT");
|
||||
@L2 P xRN_KEY_BUNR = P(9,0 ,"#RN-KEY-BUNR");
|
||||
@L2 A xRA_KEY_VKZ = A(1 ,"#RA-KEY-VKZ");
|
||||
@L2 A xRA_KEY_ABGA = A(1 ,"#RA-KEY-ABGA");
|
||||
@L2 A xRA_KEY_SART = A(2 ,"#RA-KEY-SART");
|
||||
@L2 A xRA_KEY_PFIL = A(2 ,"#RA-KEY-PFIL");
|
||||
@L2 L xRL_KEY_UHR = L("#RL-KEY-UHR");
|
||||
@L2 A xRA_KEY_STAT = A(1 ,"#RA-KEY-STAT");
|
||||
@L2 A xRA_KEY_GSMT = A(1 ,"#RA-KEY-GSMT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_1 = A(8 ,"#RA-KEY-08X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVO = A(22 ,"#RA-KEY-VVO");
|
||||
@L2 A xRA_KEY_27X_1 = A(27 ,"#RA-KEY-27X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVO = A(22 ,"#RA-KEY-AVO");
|
||||
@L2 A xRA_KEY_27X_2 = A(27 ,"#RA-KEY-27X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVO = A(22 ,"#RA-KEY-GVO");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_2 = A(8 ,"#RA-KEY-08X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VBT = A(24 ,"#RA-KEY-VBT");
|
||||
@L2 A xRA_KEY_24X = A(24 ,"#RA-KEY-24X"); // ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_ABT = A(24 ,"#RA-KEY-ABT");
|
||||
@L1 Redefine gKEY; // REDEF. BEGIN : +KEY
|
||||
@L2 A xRA_KEY_08X_3 = A(8 ,"#RA-KEY-08X-3");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_VVK = A(16 ,"#RA-KEY-VVK");
|
||||
@L2 A xRA_KEY_33X_1 = A(33 ,"#RA-KEY-33X-1");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_AVK = A(16 ,"#RA-KEY-AVK");
|
||||
@L2 A xRA_KEY_33X_2 = A(33 ,"#RA-KEY-33X-2");// ZWISCHENRAUM
|
||||
@L2 A xRA_KEY_GVK = A(16 ,"#RA-KEY-GVK");
|
||||
@L1 A gSTV = A(250 ,"+STV");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_APRG = A(8 ,"#RA-STV-APRG");
|
||||
@L2 A xRA_STV_VPRG = A(8 ,"#RA-STV-VPRG");
|
||||
@L2 A xRA_STV_FUNK = A(4 ,"#RA-STV-FUNK");
|
||||
@L2 Redefine xRA_STV_FUNK; // REDEF. BEGIN : #RA-STV-FUNK
|
||||
@L3 N xRN_STV_FUNR = N(2,0 ,"#RN-STV-FUNR");
|
||||
@L3 A xRA_STV_FUNR = A(2 ,"#RA-STV-FUNR");
|
||||
@L2 N xRN_STV_AUSW = N(2,0 ,"#RN-STV-AUSW");
|
||||
@L2 A xRA_STV_CODE = A(4 ,"#RA-STV-CODE"); // 30
|
||||
@L2 N xRN_STV_OBNR = N(8,0 ,"#RN-STV-OBNR");
|
||||
@L2 A xRA_STV_OART = A(2 ,"#RA-STV-OART");
|
||||
@L2 N xRN_STV_HINR = N(3,0 ,"#RN-STV-HINR");
|
||||
@L2 N xRN_STV_VINR = N(2,0 ,"#RN-STV-VINR");
|
||||
@L2 N xRN_STV_BRAN = N(2,0 ,"#RN-STV-BRAN");
|
||||
@L2 N xRN_STV_BRNR = N(1,0 ,"#RN-STV-BRNR");
|
||||
@L2 N xRN_STV_GENR = N(3,0 ,"#RN-STV-GENR");
|
||||
@L2 A xRA_STV_GMOD = A(1 ,"#RA-STV-GMOD");
|
||||
@L2 A xRA_STV_AMOD = A(1 ,"#RA-STV-AMOD");
|
||||
@L2 A xRA_STV_VIKZ = A(1 ,"#RA-STV-VIKZ");
|
||||
@L2 A xRA_STV_DATI = A(8 ,"#RA-STV-DATI");
|
||||
@L2 N xRN_STV_DATA = N(3,0 ,"#RN-STV-DATA");
|
||||
@L2 A xRA_STV_OPID = A(3 ,"#RA-STV-OPID");
|
||||
@L2 A xRA_STV_PFKY = A(96 ,"#RA-STV-PFKY");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PFF = A(4 ,"#RA-STV-PFF");
|
||||
@L2 Redefine xRA_STV_PFKY; // REDEF. BEGIN : #RA-STV-PFKY
|
||||
@L3 A xRA_STV_PF = A(4 ,"#RA-STV-PF").defineArray(range(1, 24));
|
||||
@L2 N xRN_STV_DATN = N(6,0 ,"#RN-STV-DATN");
|
||||
@L2 A xRA_STV_MMOD = A(3 ,"#RA-STV-MMOD");
|
||||
@L1 Redefine gSTV; // REDEF. BEGIN : +STV
|
||||
@L2 A xRA_STV_241X = A(241 ,"#RA-STV-241X");
|
||||
@L2 A xRA_STV_APAR = A(1 ,"#RA-STV-APAR");
|
||||
@L2 P xRN_STV_CURS = P(6,0 ,"#RN-STV-CURS");
|
||||
@L2 P xRN_STV_MARK = P(3,0 ,"#RN-STV-MARK");
|
||||
@L2 B xRN_STV_FNUM = B(2 ,"#RN-STV-FNUM");
|
||||
@L1 A gKEY1 = A(250 ,"+KEY1");
|
||||
@L1 Redefine gKEY1; // REDEF. BEGIN : +KEY1
|
||||
@L2 A xRA_KEY1_ASSTART = A(4 ,"#RA-KEY1-ASSTART");
|
||||
@L2 N xRN_KEY1_POSVI = N(2,0 ,"#RN-KEY1-POSVI");
|
||||
@L2 N xRN_KEY1_POSVII = N(3,0 ,"#RN-KEY1-POSVII");
|
||||
@L2 A xRA_KEY1_RVART = A(4 ,"#RA-KEY1-RVART");
|
||||
@L2 N xRN_KEY1_RVVON = N(8,0 ,"#RN-KEY1-RVVON");
|
||||
@L2 N xRN_KEY1_RVBHJAHR = N(4,0 ,"#RN-KEY1-RVBHJAHR");
|
||||
@L2 N xRN_KEY1_RVBBNR = N(2,0 ,"#RN-KEY1-RVBBNR");
|
||||
@L2 P xRN_KEY1_SBVANZ = P(1,0 ,"#RN-KEY1-SBVANZ");
|
||||
@L2 P xRN_KEY1_SBVINR = P(2,0 ,"#RN-KEY1-SBVINR").defineArray(range(1, 2));
|
||||
@L2 P xRN_KEY1_SBVII = P(2,0 ,"#RN-KEY1-SBVII").defineArray(range(1, 2));
|
||||
@L2 N xRN_KEY1_BRA = N(2,0 ,"#RN-KEY1-BRA").defineArray(range(1, 3));
|
||||
@L2 N xRN_KEY1_GEBDT = N(8,0 ,"#RN-KEY1-GEBDT");
|
||||
@L2 A xRA_KEY1_2X = A(2 ,"#RA-KEY1-2X");
|
||||
@L2 A xRA_KEY1_WAEHRUNG = A(3 ,"#RA-KEY1-WAEHRUNG");
|
||||
@L2 A xRA_KEY1_SK_ALLE = A(1 ,"#RA-KEY1-SK-ALLE");
|
||||
@L2 N xRN_KEY1_HNR = N(4,0 ,"#RN-KEY1-HNR");
|
||||
@L2 A xRA_KEY1_MTVKZ = A(1 ,"#RA-KEY1-MTVKZ");
|
||||
@L2 A xRA_KEY1_VIL = A(1 ,"#RA-KEY1-VIL");
|
||||
@L2 A xRA_KEY1_OKZ = A(3 ,"#RA-KEY1-OKZ");
|
||||
@L2 N xRN_KEY1_OPLZ = N(6,0 ,"#RN-KEY1-OPLZ");
|
||||
@L2 N xRN_KEY1_OHNR = N(4,0 ,"#RN-KEY1-OHNR");
|
||||
@L2 P xRN_KEY1_SUBVR = P(3,0 ,"#RN-KEY1-SUBVR");
|
||||
@L2 P xRN_KEY1_SUBKTO = P(7,0 ,"#RN-KEY1-SUBKTO");
|
||||
@L2 P xRN_KEY1_SUBZWST = P(3,0 ,"#RN-KEY1-SUBZWST");
|
||||
@L2 A xRA_KEY1_SKART = A(3 ,"#RA-KEY1-SKART");
|
||||
@L2 A xRA_KEY1_TCSART = A(1 ,"#RA-KEY1-TCSART");
|
||||
@L2 A xRA_KEY1_TCZ1 = A(2 ,"#RA-KEY1-TCZ1");
|
||||
@L2 Redefine xRA_KEY1_TCZ1; // REDEF. BEGIN : #RA-KEY1-TCZ1
|
||||
@L3 N xRN_KEY1_TCZ1 = N(2,0 ,"#RN-KEY1-TCZ1");
|
||||
@L2 P xRN_KEY1_TCODE = P(5,0 ,"#RN-KEY1-TCODE");
|
||||
@L2 P xRN_KEY1_TCZ2 = P(2,0 ,"#RN-KEY1-TCZ2");
|
||||
@L2 A xRA_KEY1_NATV = A(3 ,"#RA-KEY1-NATV");
|
||||
@L2 A xRA_KEY1_NATO = A(3 ,"#RA-KEY1-NATO");
|
||||
@L2 N xRN_KEY1_SNR = N(8,0 ,"#RN-KEY1-SNR");
|
||||
@L2 N xRN_KEY1_LNR = N(5,0 ,"#RN-KEY1-LNR");
|
||||
@L2 A xRA_KEY1_ZISB = A(3 ,"#RA-KEY1-ZISB");
|
||||
@L2 N xRN_KEY1_STAT = N(5,0 ,"#RN-KEY1-STAT");
|
||||
@L2 N xRN_KEY1_LZ = N(2,0 ,"#RN-KEY1-LZ");
|
||||
@L2 A xRA_KEY1_GRDHA = A(2 ,"#RA-KEY1-GRDHA");
|
||||
@L2 A xRA_KEY1_NATOV = A(3 ,"#RA-KEY1-NATOV");
|
||||
@L2 N xRN_KEY1_AWDT = N(8,0 ,"#RN-KEY1-AWDT");
|
||||
@L2 A xRA_KEY1_KOSP = A(1 ,"#RA-KEY1-KOSP");
|
||||
@L2 N xRN_KEY1_EDSTAT = N(2,0 ,"#RN-KEY1-EDSTAT");
|
||||
@L2 A xRA_KEY1_FREI = A(7 ,"#RA-KEY1-FREI");
|
||||
@L2 N xRN_KEY1_KDAT = N(8,0 ,"#RN-KEY1-KDAT");
|
||||
@L2 N xRN_KEY1_KGRD = N(2,0 ,"#RN-KEY1-KGRD");
|
||||
@L2 N xRN_KEY1_PERSNR = N(5,0 ,"#RN-KEY1-PERSNR");
|
||||
@L2 A xRA_KEY1_RPART = A(2 ,"#RA-KEY1-RPART");
|
||||
@L2 N xRN_KEY1_RPDAT = N(8,0 ,"#RN-KEY1-RPDAT");
|
||||
@L2 A xRA_KEY1_RVKZ = A(1 ,"#RA-KEY1-RVKZ");
|
||||
@L2 A xRA_KEY1_RVKZ2 = A(1 ,"#RA-KEY1-RVKZ2");
|
||||
@L2 A xRA_KEY1_LK = A(30 ,"#RA-KEY1-LK");
|
||||
@L2 A xRA_KEY1_SCHEMA = A(2 ,"#RA-KEY1-SCHEMA");
|
||||
@L2 N xRN_KEY1_RSOBNR = N(8,0 ,"#RN-KEY1-RSOBNR");
|
||||
@L2 A xRA_KEY1_RSKZ = A(1 ,"#RA-KEY1-RSKZ");
|
||||
@L2 A xRA_KEY1_BRFA = A(3 ,"#RA-KEY1-BRFA");
|
||||
@L2 A xRA_KEY1_BUCHART = A(3 ,"#RA-KEY1-BUCHART");
|
||||
@L2 N xRN_KEY1_BANKSCHL = N(3,0 ,"#RN-KEY1-BANKSCHL");
|
||||
@L2 N xRN_KEY1_WERTEDAT = N(8,0 ,"#RN-KEY1-WERTEDAT");
|
||||
@L2 A xRA_KEY1_VORPROG = A(8 ,"#RA-KEY1-VORPROG");
|
||||
@L2 N xRN_KEY1_GILT_BIS = N(8,0 ,"#RN-KEY1-GILT-BIS");
|
||||
@L2 P xRN_KEY1_FNR = P(3,0 ,"#RN-KEY1-FNR");
|
||||
@L2 A xRA_KEY1_KO55MELD = A(1 ,"#RA-KEY1-KO55MELD");
|
||||
@L1 A gKEY2 = A(250 ,"+KEY2");
|
||||
@L1 Redefine gKEY2; // REDEF. BEGIN : +KEY2
|
||||
@L2 A xRA_KEY2_TARIF = A(7 ,"#RA-KEY2-TARIF");
|
||||
@L2 A xRA_KEY2_BANKNA = A(27 ,"#RA-KEY2-BANKNA");
|
||||
@L2 A xRA_KEY2_USERGRUPPE = A(8 ,"#RA-KEY2-USERGRUPPE");
|
||||
@L2 N xRN_KEY2_KOSTST = N(5,0 ,"#RN-KEY2-KOSTST");
|
||||
@L2 A xRA_KEY2_AVISO_OP = A(3 ,"#RA-KEY2-AVISO-OP");
|
||||
@L2 N xRN_KEY2_AVISO_DT = N(8,0 ,"#RN-KEY2-AVISO-DT");
|
||||
@L2 N xRN_KEY2_AVISO_GD = N(3,0 ,"#RN-KEY2-AVISO-GD");
|
||||
@L2 N xRN_KEY2_AVISO_DTB = N(8,0 ,"#RN-KEY2-AVISO-DTB");
|
||||
@L2 A xRA_KEY2_SPRACH_KZ = A(2 ,"#RA-KEY2-SPRACH-KZ");
|
||||
@L2 A xRA_KEY2_GEWINN_VB = A(4 ,"#RA-KEY2-GEWINN-VB");
|
||||
@L2 N xRN_KEY2_GEWINN_JAHR = N(4,0 ,"#RN-KEY2-GEWINN-JAHR");
|
||||
@L2 A xRA_KEY2_ABST_SA = A(2 ,"#RA-KEY2-ABST-SA");
|
||||
@L2 A xRA_KEY2_ABST_ZA = A(1 ,"#RA-KEY2-ABST-ZA");
|
||||
@L2 P xRN_KEY2_ABST_ZR = P(9,0 ,"#RN-KEY2-ABST-ZR");
|
||||
@L2 A xRA_KEY2_ABST_NK = A(1 ,"#RA-KEY2-ABST-NK");
|
||||
@L2 A xRA_KEY2_ABST_NA = A(8 ,"#RA-KEY2-ABST-NA");
|
||||
@L2 A xRA_KEY2_FAB = A(3 ,"#RA-KEY2-FAB");
|
||||
@L2 A xRA_KEY2_TYP = A(10 ,"#RA-KEY2-TYP");
|
||||
@L2 A xRA_KEY2_FZA = A(1 ,"#RA-KEY2-FZA");
|
||||
@L2 N xRN_KEY2_LST = N(1,0 ,"#RN-KEY2-LST");
|
||||
@L2 N xRN_KEY2_KTBJ = N(4,0 ,"#RN-KEY2-KTBJ");
|
||||
@L2 N xRN_KEY2_KW = N(3,0 ,"#RN-KEY2-KW");
|
||||
@L2 P xRN_KEY2_UEBWNR = P(6,0 ,"#RN-KEY2-UEBWNR");
|
||||
@L2 P xRN_KEY2_UNTNR = P(4,0 ,"#RN-KEY2-UNTNR");
|
||||
@L2 A xRA_KEY2_SERIE = A(1 ,"#RA-KEY2-SERIE");
|
||||
@L2 P xRN_KEY2_PAKNR = P(9,0 ,"#RN-KEY2-PAKNR");
|
||||
@L2 A xRA_KEY2_WHG_VON = A(3 ,"#RA-KEY2-WHG-VON");
|
||||
@L2 A xRA_KEY2_WHG_NACH = A(3 ,"#RA-KEY2-WHG-NACH");
|
||||
@L2 A xRA_KEY2_EXTSA = A(3 ,"#RA-KEY2-EXTSA");
|
||||
@L2 N xRN_KEY2_GKNR = N(8,0 ,"#RN-KEY2-GKNR");
|
||||
@L1 C xCV_WHBS = C("#CV-WHBS"); // GW FUER WIEDERHOLTE BS-AUSG.
|
||||
@L1 C xCV_OUTP = C("#CV-OUTP"); // PROTECT BEI A+B - PROGRAMMEN
|
||||
@L1 C xCV_STV = C("#CV-STV"); // KEY-FELDER STEUERLEISTE
|
||||
@L1 L xWL_KEYWECHS = L("#WL-KEYWECHS"); // SCHALTER, OB KEYWECHSEL
|
||||
@L1 L xWL_INTERN = L("#WL-INTERN"); // SCHALTER, OB BLINDER DURCHL.
|
||||
@L1 A xWA_HELP_TASTE = A(4 ,"#WA-HELP-TASTE"); // PF-TASTE FUER 'HELP'
|
||||
@L1 A xWA_MENU_TASTE = A(4 ,"#WA-MENU-TASTE"); // PF-TASTE FUER 'MENU'
|
||||
@L1 P xWN_SEL_ISN = P(9,0 ,"#WN-SEL-ISN").defineArray(range(1,24));
|
||||
@L1 A xWA_FUNKTION = A(5 ,"#WA-FUNKTION").defineArray(range(1,100));// FUNKTIONSTABELLE
|
||||
@L1 A xWA_VORWEG_AUFRUF = A(8 ,"#WA-VORWEG-AUFRUF");// VORWEG-AUFRUF (Z.B. SCHWEBE)
|
||||
@L1 L xWL_TEST = L("#WL-TEST"); // KENNZEICHEN, OB TEST
|
||||
@L1 N xWN_WARTEN_SUMME = N(5,0 ,"#WN-WARTEN-SUMME");// ZAEHLER FUER SUSPEND
|
||||
@L1 A xWA_AUSWAHL_KZ = A(1 ,"#WA-AUSWAHL-KZ").defineArray(range(1,24));// MARKIERUNGSLEISTE
|
||||
@L1 N xWN_AUSWAHL_ANZAHL = N(2,0 ,"#WN-AUSWAHL-ANZAHL");
|
||||
@L1 A xWA_AUSWAHL_GUELTIG = A(20 ,"#WA-AUSWAHL-GUELTIG");
|
||||
/*
|
||||
* Platz für neue globale Variable
|
||||
* Definitionen in:
|
||||
* YLNEUGLO/YCNEUGLO
|
||||
*/
|
||||
@L1 A gNEUGLO_01 = A(250 ,"+NEUGLO-01");
|
||||
@L1 Redefine gNEUGLO_01;
|
||||
@L2 A xBTC_STEUERUNG = A(8 ,"#BTC-STEUERUNG");
|
||||
@L2 A xUMS_MIS_BILANZKREIS = A(1 ,"#UMS-MIS-BILANZKREIS").defineArray(range(1, 7));
|
||||
@L2 A xUMS_MIS_BRAN = A(2 ,"#UMS-MIS-BRAN").defineArray(range(1, 10));
|
||||
@L2 P xKEY1_SUCH_BETRAG = P(9,2 ,"#KEY1-SUCH-BETRAG");
|
||||
@L2 P xKEY2_ARCHIV_DB = P(3,0 ,"#KEY2-ARCHIV-DB");
|
||||
@L2 A xKEY2_EXTKZ = A(1 ,"#KEY2-EXTKZ");
|
||||
@L2 N xRN_KEY_JAHRV = N(4,0 ,"#RN-KEY-JAHRV");
|
||||
@L2 N xRN_KEY_JAHRB = N(4,0 ,"#RN-KEY-JAHRB");
|
||||
@L2 A xRA_KEY_EXT_GRUND_NAME = A(31 ,"#RA-KEY-EXT-GRUND-NAME");
|
||||
@L2 P xRN_KEY_EXT_BB_VON = P(8,0 ,"#RN-KEY-EXT-BB-VON");
|
||||
@L2 P xRN_KEY_EXT_BB_BIS = P(8,0 ,"#RN-KEY-EXT-BB-BIS");
|
||||
@L2 P xUMS_MA_SUB_SATZART_1 = P(3,0 ,"#UMS-MA-SUB-SATZART-1");
|
||||
@L2 A xRA_KEY_EXT_ANTRAGSART = A(3 ,"#RA-KEY-EXT-ANTRAGSART");
|
||||
@L2 P xFLV_TRANS = P(7,0 ,"#FLV-TRANS");
|
||||
@L2 A xFLV_ISIN = A(12 ,"#FLV-ISIN");
|
||||
@L2 P xFLV_BUNR = P(7,0 ,"#FLV-BUNR");
|
||||
@L2 P xFLV_GILT_BIS = P(8,0 ,"#FLV-GILT-BIS");
|
||||
@L2 P xFLV_DATUM = P(8,0 ,"#FLV-DATUM");
|
||||
@L2 P xFLV_VV_VINR = P(3,0 ,"#FLV-VV-VINR");
|
||||
@L2 P xFLV_VV_BRAN = P(3,0 ,"#FLV-VV-BRAN");
|
||||
@L2 P xFLV_VV_BRNR = P(3,0 ,"#FLV-VV-BRNR");
|
||||
@L2 P xFLV_VV_HINR = P(3,0 ,"#FLV-VV-HINR");
|
||||
@L2 A xVLK_EXVERARB = A(30 ,"#VLK-EXVERARB");
|
||||
@L2 P xRN_KEY_IVKNR = P(8,0 ,"#RN-KEY-IVKNR");
|
||||
@L2 A xRA_SC_MELDUNGS_ART = A(1 ,"#RA-SC-MELDUNGS-ART");
|
||||
@L2 A xRA_OS_GFALL = A(2 ,"#RA-OS-GFALL");
|
||||
@L2 A xRA_OS_DOKART = A(4 ,"#RA-OS-DOKART");
|
||||
@L2 A xRA_OS_BR_ART = A(3 ,"#RA-OS-BR-ART");
|
||||
@L2 N xRN_KEY_YU_PERSNR = N(13,0 ,"#RN-KEY-YU-PERSNR");
|
||||
@L2 N xSCN_VERS_BEGINN = N(8,0 ,"#SCN-VERS-BEGINN");
|
||||
@L2 A xDI_ABTEILUNG = A(8 ,"#DI-ABTEILUNG");
|
||||
@L2 P xDI_STAT_DZT = P(3,0 ,"#DI-STAT-DZT");
|
||||
@L2 P xDI_STAT_DZT_BIS = P(3,0 ,"#DI-STAT-DZT-BIS");
|
||||
@L2 A xDI_SPARTE = A(2 ,"#DI-SPARTE");
|
||||
@L2 A xDI_STATION = A(8 ,"#DI-STATION");
|
||||
@L2 A xDI_WM_OPID = A(8 ,"#DI-WM-OPID");
|
||||
@L2 A xDI_AW_POL_FIL = A(1 ,"#DI-AW-POL-FIL");
|
||||
@L2 A xDI_VAE_ART = A(3 ,"#DI-VAE-ART");
|
||||
@L2 A xDI_WK_VAE_ART = A(3 ,"#DI-WK-VAE-ART");
|
||||
@L2 A xDI_AW_SUB_VR = A(1 ,"#DI-AW-SUB-VR");
|
||||
@L2 P xDI_SUB_VR = P(3,0 ,"#DI-SUB-VR");
|
||||
@L2 P xDI_POL_KREIS = P(2,0 ,"#DI-POL-KREIS");
|
||||
@L2 P xDI_POL_BRAN = P(2,0 ,"#DI-POL-BRAN");
|
||||
@L2 A xDI_VAE_HINW = A(3 ,"#DI-VAE-HINW");
|
||||
@L1 A gNEUGLO_02 = A(250 ,"+NEUGLO-02");
|
||||
@L1 Redefine gNEUGLO_02;
|
||||
@L2 A xDI_BEARB_GRP = A(3 ,"#DI-BEARB-GRP");
|
||||
@L2 A xDI_AW_DRF = A(1 ,"#DI-AW-DRF");
|
||||
@L2 A xDI_AW_E_ANT = A(1 ,"#DI-AW-E-ANT");
|
||||
@L2 P xDI_PC_ANT_VKTO = P(7,0 ,"#DI-PC-ANT-VKTO");
|
||||
@L2 P xDI_PLZ_VON = P(7,0 ,"#DI-PLZ-VON");
|
||||
@L2 P xDI_PLZ_BIS = P(7,0 ,"#DI-PLZ-BIS");
|
||||
@L2 A xDI_POSTNATION = A(3 ,"#DI-POSTNATION");
|
||||
@L2 P xDI_WM_DATUM = P(8,0 ,"#DI-WM-DATUM");
|
||||
@L2 P xDI_POL_FILIALE = P(2,0 ,"#DI-POL-FILIALE");
|
||||
@L2 A xDI_AW_VAE_HINW = A(1 ,"#DI-AW-VAE-HINW");
|
||||
@L2 A xDI_AW_VKTO = A(1 ,"#DI-AW-VKTO");
|
||||
@L2 A xDI_AW_BEARB_GRP = A(1 ,"#DI-AW-BEARB-GRP");
|
||||
@L2 A xDI_AW_KR_BR = A(1 ,"#DI-AW-KR-BR");
|
||||
@L2 A xDI_AW_VAE_ART = A(1 ,"#DI-AW-VAE-ART");
|
||||
@L2 P xDI_VR_KEY = P(3,0 ,"#DI-VR-KEY");
|
||||
@L2 A xDI_OART = A(2 ,"#DI-OART");
|
||||
@L2 P xDI_WM_DATUM_VON = P(8,0 ,"#DI-WM-DATUM-VON");
|
||||
@L2 A xDI_WM_AUSWAHL = A(1 ,"#DI-WM-AUSWAHL");
|
||||
@L2 A xRA_KEY_OIB = A(20 ,"#RA-KEY-OIB");
|
||||
|
||||
|
||||
}
|
||||
158
ac-parser-natjav/src/test/resources/fixtures/natjav/MDBT74B.java
Normal file
158
ac-parser-natjav/src/test/resources/fixtures/natjav/MDBT74B.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* * header created during migration process *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION LOESCHEN UMKONTIERUNGEN
|
||||
* * @OWNER P36
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* *
|
||||
* * Löschen Umkontierungen
|
||||
* *
|
||||
* </pre>
|
||||
*/
|
||||
@Natural
|
||||
public class MDBT74B extends NaturalProgram {
|
||||
|
||||
@Local
|
||||
@L1 N xWN_DATN = N(8,0 ,"#WN-DATN");
|
||||
@L1 Redefine xWN_DATN;
|
||||
@L2 N xRN_DATN_YY = N(4,0 ,"#RN-DATN-YY");
|
||||
@L2 N xRN_DATN_MM = N(2,0 ,"#RN-DATN-MM");
|
||||
@L2 N xRN_DATN_TT = N(2,0 ,"#RN-DATN-TT");
|
||||
@L1 P xWN_ISN = P(9,0 ,"#WN-ISN");
|
||||
@L1 P xWN_Z1 = P(9,0 ,"#WN-Z1");
|
||||
@L1 A xWA_SPARTEN = A(14 ,"#WA-SPARTEN");
|
||||
@L1 A xWA_PRV_ART = A(3 ,"#WA-PRV-ART");
|
||||
@L1 A xWA_UMKONT_MENGE = A(31 ,"#WA-UMKONT-MENGE");
|
||||
@L1 Redefine xWA_UMKONT_MENGE;
|
||||
@L2 P xRN_UMKONT_MENGE = P(7,0 ,"#RN-UMKONT-MENGE").defineArray(range(1, 4));
|
||||
@L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT = view("MDUMKONT");
|
||||
@L2 A SPARTEN = A().defineArray(range(1, 4));
|
||||
@L2 A PRV_ART = A().defineArray(range(1, 2));
|
||||
@L2 A SATZART;
|
||||
@L2 N AUSLEER_DATUM;
|
||||
@L2 N VR_KEY;
|
||||
@L2 N VKTO;
|
||||
@L2 N VKTO_ZIEL;
|
||||
@L2 N UMKONT_PER;
|
||||
@L2 A UMKONT_ART;
|
||||
@L2 N OBNR;
|
||||
@L1 @ViewOf SYSTEM.MDUMKONT_DDM MDUMKONT_DEL = view("MDUMKONT-DEL");
|
||||
@L2 A SPARTEN = A().defineArray(range(1, 4));
|
||||
@L2 A PRV_ART = A().defineArray(range(1, 2));
|
||||
@L2 A OVKEY = A().defineArray(range(1, 4));
|
||||
@L2 A UMKONT_MENGE;
|
||||
@L2 N VR_KEY;
|
||||
@L2 N VKTO;
|
||||
@L2 N VKTO_ZIEL;
|
||||
@L2 N UMKONT_PER;
|
||||
@L2 A UMKONT_ART;
|
||||
@L2 N OBNR;
|
||||
@L2 A ANLAGE_VON;
|
||||
@L2 N ANLAGE_AM;
|
||||
@L2 N AUSLEER_DATUM;
|
||||
|
||||
/* additional loop and database objects */
|
||||
Read readUMK;
|
||||
Get getDEL;
|
||||
MDBT74B.MDUMKONT UMK = MDUMKONT;
|
||||
MDBT74B.MDUMKONT_DEL DEL = MDUMKONT_DEL;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
/* * */
|
||||
format(1, PS(68), LS(132));
|
||||
//
|
||||
displayDefinition(1, ES(true), DEL.VR_KEY, DEL.VKTO, EM("99^99999"), DEL.VKTO_ZIEL, EM("99^99999"), DEL.UMKONT_PER, DEL.UMKONT_ART, "SPARTEN", xWA_SPARTEN, newline(), "PRV-ART", xWA_PRV_ART, DEL.OVKEY, EM("XXXXXXXXXXXXXXX"), DEL.OBNR, DEL.ANLAGE_VON, DEL.ANLAGE_AM, DEL.AUSLEER_DATUM, "KUNDEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE, EM("Z^ZZZ^ZZ9"), newline(), "POLIZZEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE, EM("Z^ZZZ^ZZ9"), "SCHAEDEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE, EM("Z^ZZZ^ZZ9"), newline(), "BRANCHEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE, EM("Z^ZZZ^ZZ9"));
|
||||
assign(xWN_DATN, $DATN());
|
||||
subtractFrom(xWN_DATN.xRN_DATN_YY, 3);
|
||||
assign(xWN_DATN.xRN_DATN_MM, 1);
|
||||
assign(xWN_DATN.xRN_DATN_TT, 1);
|
||||
/* * */
|
||||
readUMK = read(MDUMKONT);
|
||||
readUMK.registerForSort(UMK.VR_KEY, UMK.VKTO, UMK.VKTO_ZIEL, UMK.UMKONT_PER, UMK.UMKONT_ART, descending(), xWA_SPARTEN, xWA_PRV_ART, UMK.OBNR, using(xWN_ISN));
|
||||
UMK:
|
||||
while (readUMK.hasNext()) {
|
||||
UMK = MDUMKONT;
|
||||
if ( ! ((EQ(UMK.SATZART, "M")
|
||||
&& EQ(UMK.AUSLEER_DATUM, thru(1, xWN_DATN))))) { /* ACCEPT */
|
||||
reject();
|
||||
}
|
||||
compress(UMK.SPARTEN.get(range(1, 4)), into(xWA_SPARTEN));
|
||||
compress(UMK.PRV_ART.get(range(1, 2)), into(xWA_PRV_ART));
|
||||
assign(xWN_ISN, $ISN(readUMK));
|
||||
readUMK.addForSort(UMK.VR_KEY, UMK.VKTO, UMK.VKTO_ZIEL, UMK.UMKONT_PER, UMK.UMKONT_ART, xWA_SPARTEN, xWA_PRV_ART, UMK.OBNR, xWN_ISN);
|
||||
}
|
||||
SortEvents sort1 = readUMK.sortEvents(UMK.VR_KEY, UMK.VKTO, UMK.VKTO_ZIEL, UMK.UMKONT_PER, UMK.UMKONT_ART, descending(), xWA_SPARTEN, xWA_PRV_ART, UMK.OBNR, using(xWN_ISN));
|
||||
SRT:
|
||||
while(sort1.hasNext()) {
|
||||
if(sort1.isAtBreak(UMK.VKTO_ZIEL)) {
|
||||
skip(1, 1);
|
||||
}
|
||||
if(sort1.isAtBreak(UMK.VKTO)) {
|
||||
skip(1, 3);
|
||||
}
|
||||
if(sort1.isAtBreak(UMK.VKTO, 2)) {
|
||||
skip(1, 5);
|
||||
}
|
||||
if(sort1.isAtBreak(UMK.VKTO, 1)) {
|
||||
newpage(1);
|
||||
}
|
||||
if(sort1.isAtBreak(UMK.VR_KEY)) {
|
||||
newpage(1);
|
||||
}
|
||||
if (sort1.mustProcess()) {
|
||||
//
|
||||
//
|
||||
getDEL = get(MDUMKONT_DEL, withHold(), xWN_ISN);
|
||||
DEL = MDUMKONT_DEL;
|
||||
if (EQ(DEL.UMKONT_MENGE, " ")) {
|
||||
reset(xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get($));
|
||||
} else {
|
||||
assign(xWA_UMKONT_MENGE, DEL.UMKONT_MENGE);
|
||||
}
|
||||
compress(DEL.SPARTEN.get(range(1, 4)), into(xWA_SPARTEN));
|
||||
compress(DEL.PRV_ART.get(range(1, 2)), into(xWA_PRV_ART));
|
||||
display(1, ES(true), DEL.VR_KEY, DEL.VKTO, EM("99^99999"), DEL.VKTO_ZIEL, EM("99^99999"), DEL.UMKONT_PER, DEL.UMKONT_ART, "SPARTEN", xWA_SPARTEN, newline(), "PRV-ART", xWA_PRV_ART, DEL.OVKEY.get($), EM("XXXXXXXXXXXXXXX"), DEL.OBNR, DEL.ANLAGE_VON, DEL.ANLAGE_AM, DEL.AUSLEER_DATUM, "KUNDEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get(1), EM("Z^ZZZ^ZZ9"), newline(), "POLIZZEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get(2), EM("Z^ZZZ^ZZ9"), "SCHAEDEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get(3), EM("Z^ZZZ^ZZ9"), newline(), "BRANCHEN", xWA_UMKONT_MENGE.xRN_UMKONT_MENGE.get(4), EM("Z^ZZZ^ZZ9"));
|
||||
skip(1, 1);
|
||||
delete(getDEL);
|
||||
|
||||
addTo(xWN_Z1, 1);
|
||||
if (EQ(xWN_Z1, mask(cp(".......00")))) {
|
||||
endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
// (SRT.)
|
||||
endTransaction();
|
||||
/* * */
|
||||
}
|
||||
|
||||
/* Event handling methods */
|
||||
|
||||
@Override
|
||||
protected void atWriteTitle(Number report) {
|
||||
boolean done = false;
|
||||
if (EQ(report, 1)) {
|
||||
done = true;
|
||||
write(1, $PROGRAM(), X(5), cp("GELOESCHTE UMKONTIERUNGEN STICHTAG:"), xWN_DATN, EM("9999-99-99"), T(80), "DATUM", $DATN(), AD("L"), T(100), "SEITE", $PAGE_NUMBER(1), AD("L"), newline(), newline());
|
||||
}
|
||||
if (done == false) {
|
||||
super.atWriteTitle(report);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package SYSTEM;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
import database.FDT.*;
|
||||
|
||||
@Natural(moduleName="MDUMKONT")
|
||||
public class MDUMKONT_DDM extends NaturalDdm {
|
||||
public static final DbA SATZART = reference(FDT0022.AA, "SATZART");
|
||||
public static final DbA V_FREIGABE = reference(FDT0022.CB, "V-FREIGABE");
|
||||
public static final DbA ANLAGE_VON = reference(FDT0022.AC, "ANLAGE-VON");
|
||||
public static final DbN ANLAGE_AM = reference(FDT0022.AD, "ANLAGE-AM");
|
||||
public static final DbN AUSLEER_DATUM = reference(FDT0022.AF, "AUSLEER-DATUM");
|
||||
public static final DbN VR_KEY = reference(FDT0022.CC, "VR-KEY", NUMERIC, 3, 0);
|
||||
public static final DbN VKTO = reference(FDT0022.AY, "VKTO");
|
||||
public static final DbN VKTO_ZIEL = reference(FDT0022.BC, "VKTO-ZIEL");
|
||||
public static final DbA UMKONT_ART = reference(FDT0022.BD, "UMKONT-ART", 2);
|
||||
public static final DbN UMKONT_PER = reference(FDT0022.BG, "UMKONT-PER");
|
||||
public static final DbN OBNR = reference(FDT0022.AB, "OBNR");
|
||||
public static final DbGroup SPARTEN_PE = reference(FDT0022.BH, "SPARTEN-PE");
|
||||
public static final DbA SPARTEN = reference(FDT0022.BI, "SPARTEN");
|
||||
public static final DbGroup PRV_ART_PE = reference(FDT0022.BJ, "PRV-ART-PE");
|
||||
public static final DbA PRV_ART = reference(FDT0022.BK, "PRV-ART");
|
||||
public static final DbGroup OVKEY_PE = reference(FDT0022.BU, "OVKEY-PE");
|
||||
public static final DbA OVKEY = reference(FDT0022.BV, "OVKEY", 15);
|
||||
public static final DbA KZ_PR_UMK_ABWEICH_KVA = reference(FDT0022.BZ, "KZ-PR-UMK-ABWEICH-KVA");
|
||||
public static final DbA KZ_PR_UMK_AN_TL = reference(FDT0022.CA, "KZ-PR-UMK-AN-TL");
|
||||
public static final DbN GT_ZO_VKTO_VON = reference(FDT0022.DA, "GT-ZO-VKTO-VON");
|
||||
public static final DbB SPUMZIEL = reference(FDT0022.CX, "SPUMZIEL", BINARY, 19);
|
||||
public static final DbB SPUMVKTO = reference(FDT0022.CY, "SPUMVKTO", BINARY, 19);
|
||||
public static final DbA SPUMVA = reference(FDT0022.CZ, "SPUMVA");
|
||||
public static final DbA UMKONT_MENGE = reference(FDT0022.AZ, "UMKONT-MENGE");
|
||||
public static final DbB TIMESTMP = reference(FDT0022.BW, "TIMESTMP");
|
||||
|
||||
{
|
||||
setDbId(0);
|
||||
setFileName(22);
|
||||
setFdt(FDT0022.class);
|
||||
addSuppression(SPUMZIEL, Suppression.NullValueSuppression);
|
||||
addSuppression(SPUMVKTO, Suppression.NullValueSuppression);
|
||||
addSuppression(SPUMVA, Suppression.NullValueSuppression);
|
||||
addHeader(SATZART, "SA");
|
||||
addHeader(V_FREIGABE, "V-/Frei-/gabe");
|
||||
addHeader(ANLAGE_VON, "Anlage/Opid");
|
||||
addHeader(ANLAGE_AM, "Anlage/Datum");
|
||||
addHeader(AUSLEER_DATUM, "Ausleer-/datum");
|
||||
addHeader(UMKONT_ART, "Umkont-/Art");
|
||||
addHeader(UMKONT_PER, "Umkont-/per");
|
||||
addHeader(SPARTEN, "Umkont-/Sparten");
|
||||
addHeader(PRV_ART, "Umkont-/PRV-Art");
|
||||
addHeader(OVKEY, "Umkont-/OV-Key");
|
||||
addHeader(TIMESTMP, "Timestamp");
|
||||
}
|
||||
}
|
||||
113
ac-parser-natjav/src/test/resources/fixtures/natjav/V_MIT04.java
Normal file
113
ac-parser-natjav/src/test/resources/fixtures/natjav/V_MIT04.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* * header created during migration process *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION Rule : Vkto-Daten
|
||||
* * @OWNER P07
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* </pre>
|
||||
*/
|
||||
@Natural(moduleName="V-MIT04")
|
||||
public class V_MIT04 extends NaturalSubprogram {
|
||||
|
||||
//
|
||||
// VKTO-Daten zu Original od. Schwebe
|
||||
//
|
||||
@Using KORIN.Diverse.Framework.GWSTGP_DA GWSTGP_DA = parameterUsing(KORIN.Diverse.Framework.GWSTGP_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWSTGP_DA) */
|
||||
public final KORIN.Diverse.Framework.GWSTGP_DA.GWSTGP GWSTGP = GWSTGP_DA.GWSTGP;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWSTGP_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWPDA_M_DA GWPDA_M_DA = parameterUsing(KORIN.Diverse.Framework.GWPDA_M_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
public final KORIN.Diverse.Framework.GWPDA_M_DA.MSG_INFO MSG_INFO = GWPDA_M_DA.MSG_INFO;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWPDA_M_DA) */
|
||||
@Using V_MIT04P_DA V_MIT04P_DA = parameterUsing(V_MIT04P_DA.class);
|
||||
/* @@USING:BEGIN(V_MIT04P_DA) */
|
||||
public final V_MIT04P_DA.V_MIT04P V_MIT04P = V_MIT04P_DA.V_MIT04P;
|
||||
/* @@USING:END(V_MIT04P_DA) */
|
||||
@Using KORIN.Diverse.Framework.GWLOGNAM_DA GWLOGNAM_DA = localUsing(KORIN.Diverse.Framework.GWLOGNAM_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_RETCODE xSTD_RETCODE = GWLOGNAM_DA.xSTD_RETCODE;
|
||||
public final KORIN.Diverse.Framework.GWLOGNAM_DA.xSTD_LOOPCODE xSTD_LOOPCODE = GWLOGNAM_DA.xSTD_LOOPCODE;
|
||||
/* @@USING:END(KORIN.Diverse.Framework.GWLOGNAM_DA) */
|
||||
/* * */
|
||||
// Daten für: MITZ1R
|
||||
@Using MITZ1RP_DA MITZ1RP_DA = localUsing(MITZ1RP_DA.class);
|
||||
/* @@USING:BEGIN(MITZ1RP_DA) */
|
||||
public final MITZ1RP_DA.MITZ1RR MITZ1RR = MITZ1RP_DA.MITZ1RR;
|
||||
public final MITZ1RP_DA.MITZ1RP MITZ1RP = MITZ1RP_DA.MITZ1RP;
|
||||
/* @@USING:END(MITZ1RP_DA) */
|
||||
@Local
|
||||
@L1 Group xVON_MITZ1R = group("#VON-MITZ1R"); // <PRIME-KEY-VON>
|
||||
@L2 A xGESAMT = A(7 ,"#GESAMT");
|
||||
@L2 Redefine xGESAMT;
|
||||
@L3 A SVKTO = A(7 ,"SVKTO");
|
||||
@L3 Redefine SVKTO;
|
||||
@L4 P VR_KEY = P(3,0 ,"VR-KEY");
|
||||
@L4 P VKTO = P(7,0 ,"VKTO");
|
||||
@L4 A STATUS_BYTE = A(1 ,"STATUS-BYTE");
|
||||
/* * */
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
/* * */
|
||||
reset(V_MIT04P.OUTPUTS);
|
||||
/* * */
|
||||
if (((NE(V_MIT04P.xVR_KEY, 0)
|
||||
&& NE(V_MIT04P.xVKTO, 0))
|
||||
&& NE(V_MIT04P.xSTATUS_BYTE, " "))) {
|
||||
assign(xVON_MITZ1R.VR_KEY, V_MIT04P.xVR_KEY);
|
||||
assign(xVON_MITZ1R.VKTO, V_MIT04P.xVKTO);
|
||||
assign(xVON_MITZ1R.STATUS_BYTE, V_MIT04P.xSTATUS_BYTE);
|
||||
//
|
||||
//
|
||||
assign(MITZ1RR.xVON_KEY, xVON_MITZ1R.xGESAMT);
|
||||
assign(MITZ1RR.xFUNCTION, "Read");
|
||||
callnat("MITZ1R", GWSTGP, MSG_INFO, MITZ1RR, MITZ1RP);
|
||||
if (NE(MSG_INFO.xxRETURN_CODE, xSTD_RETCODE.OK)) {
|
||||
escapeRoutine();
|
||||
}
|
||||
if ((NE(MITZ1RR.xSW_FOUND, "J")
|
||||
&& EQ(V_MIT04P.xSCHWEBE_GILT, "J"))) {
|
||||
assign(xVON_MITZ1R.STATUS_BYTE, "S");
|
||||
assign(MITZ1RR.xVON_KEY, xVON_MITZ1R.xGESAMT);
|
||||
assign(MITZ1RR.xFUNCTION, "Read");
|
||||
callnat("MITZ1R", GWSTGP, MSG_INFO, MITZ1RR, MITZ1RP);
|
||||
if (NE(MSG_INFO.xxRETURN_CODE, xSTD_RETCODE.OK)) {
|
||||
escapeRoutine();
|
||||
}
|
||||
}
|
||||
if (EQ(MITZ1RR.xSW_FOUND, "J")) {
|
||||
assign(V_MIT04P.xZS_NAME, MITZ1RP.ZS_NAME.get(1));
|
||||
assign(V_MIT04P.xVKTO_ORGBEZEICHNUNG, MITZ1RP.VKTO_ORGBEZEICHNUNG);
|
||||
assign(V_MIT04P.xVKTO_STATUS_TEXT, MITZ1RP.VKTO_STATUS_TEXT);
|
||||
assign(V_MIT04P.xVKTO_VERWENDUNG, MITZ1RP.VKTO_VERWENDUNG);
|
||||
assign(V_MIT04P.xVKTO_VERWENDUNG_TEXT, MITZ1RP.VKTO_VERWENDUNG_TEXT);
|
||||
if (GT(V_MIT04P.xVKTO_ORGBEZEICHNUNG, " ")) {
|
||||
assign(V_MIT04P.xMAP_NAME, V_MIT04P.xVKTO_ORGBEZEICHNUNG);
|
||||
} else {
|
||||
assign(V_MIT04P.xMAP_NAME, V_MIT04P.xZS_NAME);
|
||||
}
|
||||
} else {
|
||||
// KONTONUMMER :1: NICHT IN MITARBEITER-DATEI GESPEICHERT
|
||||
assign(MSG_INFO.xxMSG_NR, 684);
|
||||
assign(MSG_INFO.xxMSG_DATA.get(1), V_MIT04P.xVKTO);
|
||||
escapeRoutine();
|
||||
}
|
||||
}
|
||||
/* * */
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Diverse.Framework;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* * header created during migration process *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION Zentrale Abbruch-Routinen
|
||||
* * @OWNER P05
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ****** $$LCMHEADER:20140410-V00.01**********
|
||||
* </pre>
|
||||
*/
|
||||
@Natural(moduleName="#ABBRUCH")
|
||||
public class XABBRUCH extends NaturalProgram {
|
||||
|
||||
//
|
||||
// Programm-Abbruch-Routinen
|
||||
//
|
||||
// Schritt 1 bei Webservices:
|
||||
//
|
||||
// Protkollieren Msg
|
||||
// Wegsichern Msg in AIV-Variablen
|
||||
// Simulieren Fehler 954
|
||||
// damit dieser in einer ON-ERROR-ROUTINE (z.B. Aussenfassade)
|
||||
// gecatched werden kann, um die gesicherten Infos auszuwerten
|
||||
//
|
||||
@Using KORIN.Diverse.Webservices.WSAIVP_DA WSAIVP_DA = localUsing(KORIN.Diverse.Webservices.WSAIVP_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Webservices.WSAIVP_DA) */
|
||||
public final KORIN.Diverse.Webservices.WSAIVP_DA.WSAIVP WSAIVP = WSAIVP_DA.WSAIVP;
|
||||
/* @@USING:END(KORIN.Diverse.Webservices.WSAIVP_DA) */
|
||||
@Using KORIN.Diverse.Webservices.WSAIVL_DA WSAIVL_DA = localUsing(KORIN.Diverse.Webservices.WSAIVL_DA.class);
|
||||
/* @@USING:BEGIN(KORIN.Diverse.Webservices.WSAIVL_DA) */
|
||||
public final KORIN.Diverse.Webservices.WSAIVL_DA.WSAIVL WSAIVL = WSAIVL_DA.WSAIVL;
|
||||
/* @@USING:END(KORIN.Diverse.Webservices.WSAIVL_DA) */
|
||||
@Using GWPDA_M_DA GWPDA_M_DA = localUsing(GWPDA_M_DA.class);
|
||||
/* @@USING:BEGIN(GWPDA_M_DA) */
|
||||
public final GWPDA_M_DA.MSG_INFO MSG_INFO = GWPDA_M_DA.MSG_INFO;
|
||||
/* @@USING:END(GWPDA_M_DA) */
|
||||
@Using GWLOGNAM_DA GWLOGNAM_DA = localUsing(GWLOGNAM_DA.class);
|
||||
/* @@USING:BEGIN(GWLOGNAM_DA) */
|
||||
public final GWLOGNAM_DA.xSTD_RETCODE xSTD_RETCODE = GWLOGNAM_DA.xSTD_RETCODE;
|
||||
public final GWLOGNAM_DA.xSTD_LOOPCODE xSTD_LOOPCODE = GWLOGNAM_DA.xSTD_LOOPCODE;
|
||||
/* @@USING:END(GWLOGNAM_DA) */
|
||||
@Using CSTENTW.GWPRTSTP_DA GWPRTSTP_DA = localUsing(CSTENTW.GWPRTSTP_DA.class);
|
||||
/* @@USING:BEGIN(CSTENTW.GWPRTSTP_DA) */
|
||||
public final CSTENTW.GWPRTSTP_DA.GWPRTSTP GWPRTSTP = GWPRTSTP_DA.GWPRTSTP;
|
||||
/* @@USING:END(CSTENTW.GWPRTSTP_DA) */
|
||||
@Using CSTENTW.XTERM_A_DA XTERM_A_DA = localUsing(CSTENTW.XTERM_A_DA.class);
|
||||
/* @@USING:BEGIN(CSTENTW.XTERM_A_DA) */
|
||||
public final CSTENTW.XTERM_A_DA.xTERM_A xTERM_A = XTERM_A_DA.xTERM_A;
|
||||
/* @@USING:END(CSTENTW.XTERM_A_DA) */
|
||||
@Local
|
||||
//
|
||||
@L1 A xWA_MELDUNG = A(80 ,"#WA-MELDUNG").defineArray(range(1, 30));
|
||||
@L1 A xWA_MESSAGE = A(72 ,"#WA-MESSAGE");
|
||||
//
|
||||
@L1 P xWN_DATA = P(3,0 ,"#WN-DATA");
|
||||
//
|
||||
@L1 A xERR_954 = A(2 ,"#ERR-954");
|
||||
@L1 Redefine xERR_954;
|
||||
@L2 P FORMAT_P = P(2,0 ,"FORMAT-P");
|
||||
@L1 A xRUNTIME = A(32 ,"#RUNTIME");
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//
|
||||
// Umwandeln Stack in eine String
|
||||
format(0, LS(81), IP(false));
|
||||
//
|
||||
// Kein Webservice:
|
||||
// --> Aufruf Abbruch-Routine für 'Nicht-Webservices'
|
||||
if (NE($APPLIC_ID(), "SYSRPC")) {
|
||||
fetchReturn("ABBRUCH1");
|
||||
/* @INCLUDE XTERM_C "0" */
|
||||
/* @@INCLUDE:BEGIN(XTERM_C) */
|
||||
/* *LCM-HEADER BEGIN ******************************************** */
|
||||
/* * *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION CC für Kapsel Aufruf TERMINATE
|
||||
* * @OWNER P01
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ******************************************** */
|
||||
reset(xTERM_A);
|
||||
assign(xTERM_A.xTERM_CODE, 0);
|
||||
callnat("#TERM-N", xTERM_A);
|
||||
/* @@INCLUDE:END(XTERM_C) */
|
||||
}
|
||||
assign(xWN_DATA, $DATA());
|
||||
if (NE(xWN_DATA, 0)) {
|
||||
input(PS(200), xWA_MELDUNG.get($));
|
||||
compress(xWA_MELDUNG.get($), into(xWA_MESSAGE));
|
||||
} else {
|
||||
assign(xWA_MESSAGE, cp("*** Programm - Abbruch ***"));
|
||||
}
|
||||
//
|
||||
// Protokoll
|
||||
//
|
||||
releaseStack();
|
||||
backoutTransaction();
|
||||
//
|
||||
// Ermitteln runtime
|
||||
reset(WSAIVP);
|
||||
assign(WSAIVP.AUFRUFCODE, WSAIVL.xC_GET_ITEM);
|
||||
assign(WSAIVP.BEZEICHNER, WSAIVL.xB_RUNTIME);
|
||||
callnat("WSAIV", WSAIVP, MSG_INFO);
|
||||
assign(xRUNTIME, WSAIVP.INHALT);
|
||||
//
|
||||
// Merken Infos in AIV
|
||||
if (EQ(xRUNTIME, WSAIVL.xR_WS_CALL)) { // Aufruf aus Aussenfassade
|
||||
reset(MSG_INFO);
|
||||
assign(WSAIVP.AUFRUFCODE, WSAIVL.xC_PUT_ITEM);
|
||||
assign(WSAIVP.BEZEICHNER, WSAIVL.xB_REINPUT_FNR);
|
||||
assign(WSAIVP.INHALT, 1381);
|
||||
callnat("WSAIV", WSAIVP, MSG_INFO);
|
||||
assign(WSAIVP.AUFRUFCODE, WSAIVL.xC_PUT_ITEM);
|
||||
assign(WSAIVP.BEZEICHNER, WSAIVL.xB_REINPUT_FVAR);
|
||||
assign(WSAIVP.INHALT, xWA_MELDUNG.get(1));
|
||||
callnat("WSAIV", WSAIVP, MSG_INFO);
|
||||
assign(WSAIVP.AUFRUFCODE, WSAIVL.xC_PUT_ITEM);
|
||||
assign(WSAIVP.BEZEICHNER, WSAIVL.xB_REINPUT_PGM);
|
||||
assign(WSAIVP.INHALT, $PROGRAM());
|
||||
callnat("WSAIV", WSAIVP, MSG_INFO);
|
||||
//
|
||||
assign(GWPRTSTP.AUFRUFENDES_PROGRAMM, $PROGRAM());
|
||||
callnat("GWPRTST", GWPRTSTP);
|
||||
//
|
||||
// Produktion Fehler 954
|
||||
addTo(xERR_954.FORMAT_P, 1);
|
||||
//
|
||||
} else {
|
||||
// Sonstiger RPC (Host-Host, ...)
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="YPMA71VK")
|
||||
public class YPMA71VK_DA extends NaturalData {
|
||||
|
||||
@Parameter
|
||||
@L1 Group YPMA71VK = group("YPMA71VK");
|
||||
@L2 A MARKIERUNG = A(1 ,"MARKIERUNG");
|
||||
@L2 A MENU_TASTE = A(4 ,"MENU-TASTE");
|
||||
@L2 N VR = N(3,0 ,"VR");
|
||||
@L2 N VKTO = N(7,0 ,"VKTO");
|
||||
@L2 N DATUM = N(6,0 ,"DATUM");
|
||||
@L2 Redefine DATUM;
|
||||
@L3 N JAHR = N(4,0 ,"JAHR");
|
||||
@L3 N MONAT = N(2,0 ,"MONAT");
|
||||
@L2 A SA = A(2 ,"SA");
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* *LCM-HEADER BEGIN ********************************************
|
||||
* * *
|
||||
* **************************************************************
|
||||
* * @MERGE-SOURCE N
|
||||
* * @NO-MERGE N
|
||||
* * @FUNCTION Parameter YNUMKLOG
|
||||
* * @OWNER P36
|
||||
* **************************************************************
|
||||
* **************************************************************
|
||||
* *LCM-HEADER END ********************************************
|
||||
* </pre>
|
||||
*/
|
||||
@Natural(moduleName="YPUMKLOG")
|
||||
public class YPUMKLOG_DA extends NaturalData {
|
||||
@Parameter
|
||||
@L1 Group YPUMKLOG = group("YPUMKLOG");
|
||||
@L2 Group IN = group("IN");
|
||||
@L3 A OPID = A("OPID");
|
||||
@L3 B TIMESTMP = B(8 ,"TIMESTMP");
|
||||
@L3 N VR_KEY = N(3,0 ,"VR-KEY");
|
||||
@L3 A DATA = A(200 ,"DATA").defineArray(range(1, 9999));
|
||||
@L3 A TRACE_JN = A(1);
|
||||
@L2 Group OUT = group("OUT");
|
||||
@L3 A JOBNAME = A(8 ,"JOBNAME");
|
||||
@L3 A PAR_ASYNID = A("PAR-ASYNID");
|
||||
@L3 A PAR_UNIQUE_ID = A("PAR-UNIQUE-ID");
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Generated by innoWake maxenso enterprise-engine.
|
||||
*/
|
||||
package KORIN.Mitarbeiter.Verwaltung;
|
||||
|
||||
import innowake.mee.natural.api.*;
|
||||
|
||||
@Natural(moduleName="YPUMPRUE")
|
||||
public class YPUMPRUE_DA extends NaturalData {
|
||||
|
||||
@Parameter
|
||||
@L1 Group YPUMPRUE = group("YPUMPRUE");
|
||||
@L2 Group INPUTS = group("INPUTS");
|
||||
@L3 N VR_KEY = N(3,0 ,"VR-KEY");
|
||||
@L3 N VKTO = N(7,0 ,"VKTO");
|
||||
@L3 N VKTO_ZIEL = N(7,0 ,"VKTO-ZIEL");
|
||||
@L3 A KZ_PR_UMK_ABWEICH_KVA = A(1 ,"KZ-PR-UMK-ABWEICH-KVA");
|
||||
@L2 Group OUTPUTS = group("OUTPUTS");
|
||||
@L3 A VKTO_NAME = A(31 ,"VKTO-NAME");
|
||||
@L3 N FNUM = N(4,0 ,"FNUM");
|
||||
@L3 A FEHLER = A(78 ,"FEHLER");
|
||||
|
||||
|
||||
}
|
||||
@@ -11,7 +11,13 @@ services:
|
||||
# (~7.8 GiB here) and never gives it back: measured 6.17 GiB still held one hour after a
|
||||
# deep refresh of upms had finished, at <1% CPU. The cap is what forces the JVM to
|
||||
# collect rather than commit.
|
||||
NEO4J_server_memory_heap_max__size: "2G"
|
||||
# 2G -> 4G. The 2G was measured against `upms` alone; the database now also holds the NatJav
|
||||
# reference corpus (2.64 M nodes) and K3 (531 k), i.e. 3.19 M nodes total. Neo4j derives the
|
||||
# per-transaction memory limit from the heap, and at 2G that limit is 1.4 GiB — which the
|
||||
# generic placeholder resolution exceeded on K3, failing the refresh at step 2 of 38 with
|
||||
# "allocation of an extra 2.0 MiB would use more than the limit 1.4 GiB". The parse and persist
|
||||
# phases were unaffected; it is one enrichment transaction that outgrew the cap.
|
||||
NEO4J_server_memory_heap_max__size: "4G"
|
||||
NEO4J_server_memory_heap_initial__size: "512M"
|
||||
# 512M -> 1G -> 3G -> 2G. The store has grown to 2.4 GB (1.2 GB of it range indexes), so 1G
|
||||
# covered only ~42% of it. 2G covers nearly all of it; the 2.5 GB of transaction logs are
|
||||
@@ -37,7 +43,10 @@ services:
|
||||
# spare GB. This follows the page cache: Neo4j's own rule of thumb is heap + page cache + ~1G, so
|
||||
# 2G of cache means 6g here — 1G of reserve, not generosity. Do not raise the page cache without
|
||||
# raising this too.
|
||||
mem_limit: 6g
|
||||
# 6g -> 8g, following the heap: Neo4j's rule of thumb is heap + page cache + ~1G, so 4G + 2G + 1G
|
||||
# with a gigabyte of reserve. An OOM-kill mid-refresh leaves the graph half-updated, which is far
|
||||
# worse than a spare GB. Do not raise the heap or the page cache without raising this too.
|
||||
mem_limit: 8g
|
||||
volumes:
|
||||
- neo4j-data:/data
|
||||
healthcheck:
|
||||
@@ -81,7 +90,11 @@ services:
|
||||
# the API (which store absolute host paths) resolve inside the container too.
|
||||
# One entry per registered project root (GET /api/projects) — add a line
|
||||
# here whenever a new project root is registered outside this repo.
|
||||
- /home/ingo/deve/agenticCode:/home/ingo/deve/agenticCode:ro
|
||||
- /home/ingo/deve/agenticcode:/home/ingo/deve/agenticcode:ro
|
||||
# NatJav reference corpus (innoWake-transpiled Natural), reached via the
|
||||
# repo-local symlink korin-natjav -> /home/ingo/deve/korin-natjav.
|
||||
- /home/ingo/deve/korin-natjav:/home/ingo/deve/korin-natjav:ro
|
||||
- /home/ingo/deve/K3:/home/ingo/deve/K3:ro
|
||||
- /home/ingo/deve/uniqa/uniqa-upms-app:/home/ingo/deve/uniqa/uniqa-upms-app:ro
|
||||
- /home/ingo/deve/uniqa/pur-sources/backend:/home/ingo/deve/uniqa/pur-sources/backend:ro
|
||||
- /home/ingo/deve/tools/conqat/system/src/250401_UMPS/src/pur-analysis/pur-legacy:/home/ingo/deve/tools/conqat/system/src/250401_UMPS/src/pur-analysis/pur-legacy:ro
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -18,6 +18,7 @@
|
||||
<module>ac-parser-core</module>
|
||||
<module>ac-parser-natural</module>
|
||||
<module>ac-parser-java</module>
|
||||
<module>ac-parser-natjav</module>
|
||||
<module>ac-neo4j-store</module>
|
||||
<module>ac-code-server</module>
|
||||
<module>ac-cli</module>
|
||||
@@ -74,6 +75,11 @@
|
||||
<artifactId>ac-parser-java</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-parser-natjav</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agenticcode</groupId>
|
||||
<artifactId>ac-neo4j-store</artifactId>
|
||||
|
||||
@@ -897,6 +897,230 @@ or `CALLNAT` that only exists in a `.cpy` shows up in the host's `db-accesses`/`
|
||||
<gda>` resolves to them (the `INCLUDE`/`USING` recogniser now accepts `GLOBAL`, not just
|
||||
`PARAMETER`/`LOCAL`).
|
||||
|
||||
## NatJav: Natural transpiled to Java (2026-09-07)
|
||||
|
||||
A project may declare `language: "natjav"` alongside `natural` and `java`. NatJav is Software AG
|
||||
Natural transpiled to Java by the innoWake maxenso engine: the files are `.java`, so the **project's**
|
||||
language — not the extension — selects the parser. Consequence: a `natjav` project reads *every*
|
||||
`.java` file under its root as transpiled Natural, so point its `root` at the generated tree
|
||||
(`src-natural-java`), not at a directory that also holds hand-written Java.
|
||||
|
||||
Create one exactly like any other project:
|
||||
|
||||
```bash
|
||||
ac project create korin -r /path/to/src-natural-java -l natjav
|
||||
curl -X POST localhost:8787/api/projects/korin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"root":"/path/to/src-natural-java","language":"natjav"}'
|
||||
```
|
||||
|
||||
**Query it with Natural names, not Java class names.** A module's identity in the graph is its
|
||||
Natural object name, recovered from the generated `@Natural(moduleName=...)`:
|
||||
|
||||
| Ask for | Not | File on disk |
|
||||
|---|---|---|
|
||||
| `V-MIT04` | `V_MIT04` | `V_MIT04.java` |
|
||||
| `#ABBRUCH` | `XABBRUCH` | `XABBRUCH.java` (a leading `#` transpiles to `X`) |
|
||||
| `COMMON` (a GDA) | `COMMON_DA` | `COMMON_DA.java` |
|
||||
| `BANKEZ` (a DDM) | `BANKEZ_DDM` | `BANKEZ_DDM.java` |
|
||||
|
||||
That is the whole point: a query written against the Natural original also answers against its
|
||||
migration. The Java class name is kept as the `className` property, and the Java package (the
|
||||
transpiled library path) as `package`.
|
||||
|
||||
`moduleKind` is **exact** here, not the PROGRAM/SUBPROGRAM heuristic a flat `.nat` file forces —
|
||||
the transpiler kept the catalog type in the base class:
|
||||
|
||||
| `moduleKind` | base class | node type |
|
||||
|---|---|---|
|
||||
| `PROGRAM` | `NaturalProgram` | `MODULE` |
|
||||
| `SUBPROGRAM` | `NaturalSubprogram` | `MODULE` |
|
||||
| `DATA_AREA` | `NaturalData` | `DATA_STRUCTURE` |
|
||||
| `MAP` / `FORM` / `SUBROUTINE` / `DDM` / `HELP` / `FDT` | `NaturalMap` / `NaturalForm` / `NaturalSubroutine` / `NaturalDdm` / `NaturalHelp` / `NaturalFdt` | `MODULE` |
|
||||
|
||||
`GET /loc` reports `natjav` as its own language row, so a mixed estate can separate transpiled
|
||||
Natural from hand-written Java.
|
||||
|
||||
### Fields and data areas
|
||||
|
||||
`GET /data-structures/{name}/fields` and the identifier search answer for `natjav`. The field tree is
|
||||
built from the `@L1`..`@L7` **annotations**, not from Java nesting, and looks like the Natural one:
|
||||
`Group`/`Redefine`/view → `DATA_STRUCTURE`, `.constant(...)` → `CONSTANT`, the rest → `VARIABLE`,
|
||||
linked by `CONTAINS`. A program's inline local data is parsed too, not just `*_DA.java` files.
|
||||
|
||||
`GET /modules/{name}/data-structures` reports `area` for a NatJav `USING` from the edge's `scope`
|
||||
(see the `INCLUDES` edge below): `PDA`/`LDA`/`GDA` for `parameterUsing`/`localUsing`/`globalUsing`.
|
||||
It is a property of the *use*, not of the area — `YPKUNWE` is a PDA to the subprogram that takes it
|
||||
and an LDA to the program that declares it locally — which is why it lives on the edge and why this
|
||||
per-module endpoint can state it. An area whose file is not in the project stays `area: UNKNOWN`
|
||||
with `sourceFile: null` (item 186).
|
||||
|
||||
`dataType` uses the Natural spelling, so it compares directly with a Natural ingest:
|
||||
|
||||
| NatJav declaration | `dataType` | Natural |
|
||||
|---|---|---|
|
||||
| `A(8 ,"#WA-SPARTEN")` | `A8` | `(A8)` |
|
||||
| `N(9,0 ,"+BBST-POLNR")` | `N9` — the `,0` is dropped | `(N9)` |
|
||||
| `N(15,3 ,"BETRAG")` | `N15,3` | `(N15,3)` |
|
||||
| `A(200,"DATA").defineArray(range(1, 9999))` | `A200/1:9999` | `(A200/1:9999)` |
|
||||
| `A("OPID")` | `A` | `(A) DYNAMIC` |
|
||||
|
||||
`scope` (`PARAMETER`/`LOCAL`/`GLOBAL`) comes from the class's `@Parameter`/`@Local`/`@Global`
|
||||
annotation — the equivalent of Natural's `.pda`/`.lda`/`.gda` extension, which a `_DA.java` filename
|
||||
cannot express.
|
||||
|
||||
**Trust `naturalNameKnown`.** A field node's name is the Natural name whenever the source states it
|
||||
(63.5 % of declarations) or it can be resolved from the class's own symbol table (every `Redefine`).
|
||||
Otherwise the node keeps the **Java** field name and carries `naturalNameKnown=false`; nothing is
|
||||
derived, because the obvious transformation is wrong for 4.81 % of the corpus's named fields. When a
|
||||
node's name and Java name differ, the latter is in `javaName`.
|
||||
|
||||
`USING` shows up as `INCLUDES` to the data area, resolved by class name rather than by the (underivable)
|
||||
Natural name.
|
||||
|
||||
### When two files claim one module name
|
||||
|
||||
Natural module names are unique **per library**, not per estate, and the migration occasionally
|
||||
copied a header without updating it. Both show up as a NatJav name claimed twice, and the ingest
|
||||
resolves them differently:
|
||||
|
||||
- **Both files legitimately own the name** (the same module name in two libraries): each is qualified
|
||||
with its library — `KORIN.ACOMMON`, `ABFRAGE.ACOMMON`. Only the colliding names are qualified, so
|
||||
everything else stays addressable by its bare Natural name. The library is also on every node as
|
||||
`package`.
|
||||
- **One file's declaration is wrong** (`ZB80BM6.java` declaring `moduleName="ZB80BM5"`): the file whose
|
||||
class name matches keeps the name; the other is ingested under its own class name and carries
|
||||
`naturalNameConflict` naming the declaration it lost. Six files in the reference corpus are like
|
||||
this — treat `naturalNameConflict` as a defect report about the migrated source.
|
||||
- **Nothing distinguishes them**: neither file is ingested, and the endpoints answer
|
||||
`409 DUPLICATE_IDENTITY` with both paths, as for any other language.
|
||||
|
||||
### Call graph
|
||||
|
||||
`callers`, `callees`, `call-tree` and `ego-graph` answer for `natjav`. A `CALLS` edge's `callKind` is
|
||||
`PERFORM`, `CALLNAT` (also used for `fetch`/`fetchReturn`) or `CALLNAT_DYNAMIC` for a target held in a
|
||||
variable, which additionally carries `dynamicVar`.
|
||||
|
||||
**Subroutines are named by their Java method name** (`FX_LEEREN`, not `FX-LEEREN`) — unlike modules,
|
||||
whose Natural name the transpiler preserved in an annotation. Nothing states a subroutine's Natural
|
||||
name: the comment that trails the method looks like one but is a remark in 1,852 cases
|
||||
(`GWFMCHK // FORMAT-CHECK`) and is absent from half of them. `perform("FX_LEEREN")` names the same
|
||||
identifier, so the graph is internally exact.
|
||||
|
||||
`run()` and the event blocks (`atTopOfPage`, `atWriteTitle`, `onError`, …) are **not** functions —
|
||||
they are Natural's inline event blocks, so their calls are attributed to the module, exactly as in a
|
||||
Natural ingest.
|
||||
|
||||
**Copycode: origin only.** A call inside an expanded copycode carries `viaCopycode` and `includedAt`,
|
||||
so `callers` reports which copycode a call came from. It does **not** carry `originFile`, and there is
|
||||
no node for the copycode itself: the transpiler inlined every expansion into its host file, so unlike
|
||||
a Natural ingest there is no shared copycode node and no separate `.cpy` to point at. Treat
|
||||
`viaCopycode` as the answer to "where did this call come from", not as a file you can read.
|
||||
|
||||
### DB access, views and DDMs
|
||||
|
||||
`db-accesses`, `module-tables` and `workfile-accesses` answer for `natjav`, in the same shape as for
|
||||
Natural: a `DB_ACCESS` whose `dataType` is `READ`/`WRITE`/`DELETE`, `READS`/`WRITES` from the caller
|
||||
to a `DB_TABLE`, and `USES_TYPE` from the access to the table and the view.
|
||||
|
||||
A `DB_TABLE` is **file-less**, as in every other language here — that is what makes all references to
|
||||
a table converge on one node. The DDM file that describes it is a separate `MODULE`
|
||||
(`moduleKind=DDM`) holding the columns, joined by `MODULE -[MAPS_TO]-> DB_TABLE`, exactly as a Java
|
||||
JPA entity is joined to its table. So "which file defines this table" is
|
||||
`MATCH (m)-[:MAPS_TO]->(t:DB_TABLE {name: …})`, not a `sourceFile` on the table.
|
||||
|
||||
DDM column nodes carry `adabasShortName` (the two-character physical field key, e.g. `AF`), which no
|
||||
Natural source shows — it exists only in the DDM.
|
||||
|
||||
**A statement's operand is often not the view.** `read(VTO, …)` where `VVSTICH.VT VTO = VT;`, or
|
||||
`find(SWPOS, …)` where `SWPOS` is a view of a `USING`'d data area — the field's declared type names
|
||||
the origin (`<owner>.<view>`), and the ingest follows it, across files where the view belongs to a
|
||||
data area. So `db-accesses` reports the table even when the statement never mentions a view by name.
|
||||
|
||||
**A write's table comes from its read.** `UPDATE`/`DELETE` name the loop object the read positioned
|
||||
(`delete(getDEL)`), not the view, and the parser resolves that file-wide. Where an object was
|
||||
assigned from two different views the access carries `ambiguousLoopTarget=true` and points at both
|
||||
candidates; treat those 18 corpus cases as "one of these", not as two writes.
|
||||
|
||||
### Dataflow
|
||||
|
||||
`flow-forward`, `flow-backward` and `field-flow` answer for `natjav`: every assignment statement emits
|
||||
`READS`/`WRITES` from the enclosing function (or the module) to the field.
|
||||
|
||||
Accumulating statements read their target as well as writing it — `addTo(x, 1)` is `ADD 1 TO x`, whose
|
||||
old value is an input — matching how the Natural parser treats `TO` versus `GIVING`. A backward flow
|
||||
therefore runs through a counter rather than stopping at it.
|
||||
|
||||
A field of a `USING`'d data area resolves across files: the operand names Java identifiers
|
||||
(`WSAIVP.INHALT`) while the node carries the Natural name, and the ingest bridges the two through the
|
||||
data areas' own sources. An operand that resolves to neither the file's own fields nor a known area
|
||||
yields **no** edge — nothing is guessed.
|
||||
|
||||
### `USING` scope
|
||||
|
||||
An `INCLUDES` edge carries `scope` (`PARAMETER`/`LOCAL`/`GLOBAL`) and, for a parameter area,
|
||||
`paramPosition` — its 0-based slot in the subprogram's argument list. That is a subprogram's
|
||||
interface: `MATCH (m)-[i:INCLUDES]->(a) WHERE i.scope = 'PARAMETER' RETURN a.name ORDER BY i.paramPosition`.
|
||||
|
||||
**Read those from the edge, not the node.** A data area is shared, and the same one is parameter 0 of
|
||||
one subprogram and parameter 2 of another.
|
||||
|
||||
There are no `ARG_TO_PARAM` edges for `natjav`, by design: the transpiler passes shared data-area
|
||||
objects rather than the caller's own variables, so caller and callee reference the *same* node and the
|
||||
edge would be a self-loop. The relationship it would express is already there — both modules
|
||||
`INCLUDE` the same area.
|
||||
|
||||
### XML payload
|
||||
|
||||
`/modules/{name}/payload` answers, and an emitting module carries `xmlWrapper=true`. A
|
||||
`PAYLOAD_FIELD` has `tag`, `field`, `direction` and (NatJav-only) `lineType`.
|
||||
|
||||
The idiom differs from Natural's: this estate assigns `TAG`/`INHALT` into a framework parameter area
|
||||
and calls `XMLLIN` or `XMLDYN`, rather than using `ADD-XML-LINE`/`COMPRESS '<' tag '>'`, none of which
|
||||
occur here. `direction` is always `REQUEST` — not a default but a measurement: the read counterpart
|
||||
`XMLREAD` exists and is called zero times in the corpus.
|
||||
|
||||
### Control flow
|
||||
|
||||
`CONTROL_FLOW` nodes nest by containment, as for Natural. A block's `name` and `dataType` are the
|
||||
Natural construct — `IF`, `FOR`, `REPEAT` or `DECIDE` — and its `value` is the condition text.
|
||||
|
||||
Two rules are worth knowing before reading the numbers:
|
||||
|
||||
- **A database loop is not a control-flow block.** `while (readUMK.hasNext())` is a transpiled
|
||||
`READ`/`FIND` loop and yields no node, exactly as `READ` yields none in a Natural ingest. 20,749 of
|
||||
the corpus's `while` statements are of that kind; the `REPEAT` blocks you see are Natural `REPEAT`
|
||||
loops (`hasNextWhile`).
|
||||
- **A `DECIDE` is one block, not a chain of ifs.** The transpiler lowers Natural's `DECIDE` into
|
||||
`if`/`else if` around a counter local; the ingest folds the chain back into a single `DECIDE`, whose
|
||||
`decideValues` lists the arms' literal values (comma-separated). A dispatch on numbers has no such
|
||||
values and is a `DECIDE` all the same — do not use `decideValues IS NOT NULL` as the test for "is
|
||||
this a dispatch".
|
||||
|
||||
A `switch` statement is also a `DECIDE`, without `decideValues`.
|
||||
|
||||
### Comments
|
||||
|
||||
`/modules/{name}/comments` answers, and `search_by_value` with `includeComments` searches them.
|
||||
Blocks, kinds and the `DOCUMENTS` attachment are the Java parser's — NatJav is Java syntax — so a
|
||||
comment's shape is the same as in a Java project.
|
||||
|
||||
**Transpiler bookkeeping is not stored.** A generated file carries roughly a quarter of its comment
|
||||
lines as machine output — `@@USING`/`@@INCLUDE` expansion markers, the generation banner, the
|
||||
migration header's frame and its per-line generator stamps. All of it is already in the graph as
|
||||
structured data (`viaCopycode` on a call, `description`/`owner` on the module), and keeping it as text
|
||||
as well would make a full-text search answer a quarter markers.
|
||||
|
||||
What is *not* dropped is the prose inside the migration header: 71 % of the files carrying one have a
|
||||
real description there, often fuller than the `description` property taken from its `@FUNCTION` line.
|
||||
Filtering happens per line, so that text survives while the frame around it does not.
|
||||
|
||||
Comment text carries neither the Javadoc `*` nor Natural's own full-line comment marker — the
|
||||
sentence is stored, not the comment syntax, as for a Natural ingest.
|
||||
|
||||
`agenticcode.ingest.comments.enabled=false` drops comment nodes at the persist seam for every
|
||||
language, NatJav included.
|
||||
|
||||
## Deep-ingest: now automatic (lazy Tier-2)
|
||||
|
||||
Field-level endpoints (`flow-forward`, `flow-backward`, `field-flow`) and
|
||||
@@ -1009,7 +1233,7 @@ origins (`http://localhost:5173`, `http://localhost:4173`) — extend the
|
||||
| `GET /modules/{name}/graph?direction=&depth=&limit=` | Ego graph (item 49): bounded module-level call neighbourhood as **nodes + edges** (unlike call-tree). `direction` = `out`/`in`/`both`; `limit` caps nodes (BFS order) and sets `truncated`; unresolved targets carry `unresolved=true` + empty `sourceFile`. CLI `ac ego-graph` |
|
||||
| `GET /modules/{name}/db-accesses` \| `/sql-statements` | DB tables + mode, raw statement text (pass `?depth=` for Natural). **`db-accesses`/`workfile-accesses` return every row when no `limit` is given (item 103)** — they used to default to 50, and since the response is a bare array with no total and no `truncated` flag the cut was invisible: `WGEAGB0S?depth=10` returned 50 of 64 rows and hid 7 tables outright. An explicit `limit` is still honoured exactly. `db-accesses` items carry **`sites: [{lineNo, sourceFile, viaCopycode, includedAt}]`** (+ kept `lineNos`); `sql-statements` items carry **`sourceFile`** + **`viaCopycode`** — so a copycode-sourced access (e.g. `SELECT … FROM SYSIBM-SYSDUMMY1` in `USIX043C.cpy`) reports the `.cpy` line, not a bare number that reads as a host-file line |
|
||||
| `GET /modules/{name}/workfile-accesses` | Natural **work files** (sequential/flat-file I/O — `READ`/`WRITE WORK FILE n`), the work-file analogue of `db-accesses` (item 84): `[{workFile, physicalName, mode: READS\|WRITES, recordBuffers, lineNos, sites}]`, aggregated per work-file number + mode. `sites: [{lineNo, sourceFile, viaCopycode, includedAt}]` gives each access its file context (copycode-aware), like `db-accesses`. `physicalName` comes from a `DEFINE WORK FILE n '<name>'`, else `null`. **Kept separate from `db-accesses`** — a work file is not an ADABAS/SQL table (fixes a former bug where `READ WORK FILE` created a phantom `DB_TABLE 'WORK'`). CLI `ac workfile-accesses <module>` |
|
||||
| `GET /modules/{name}/data-structures` | Which copybooks/inline groups a module uses. A `USING <member>` binds by **member (file) name**, never by a level-1 record inside the file (item 100) — before that, `WGEAGB0S USING W-WIF-A2` reported `old/W-WIF-A7.pda` (whose level-1 record is a copy-pasted `1W-WIF-A2`), and a data area with several level-1 records and none named after the member (`VLAYERLA.lda`, `USIX020L.lda`) resolved to nothing at all (`sourceFile: null`, `area: UNKNOWN`, `fieldCount: 0`) although the file was ingested. One row per resolved definition, `(name, sourceFile)` (item 102) — never one row blending an arbitrary file with another definition's `fieldCount` |
|
||||
| `GET /modules/{name}/data-structures` | Which copybooks/inline groups a module uses. A `USING <member>` binds by **member (file) name**, never by a level-1 record inside the file (item 100) — before that, `WGEAGB0S USING W-WIF-A2` reported `old/W-WIF-A7.pda` (whose level-1 record is a copy-pasted `1W-WIF-A2`), and a data area with several level-1 records and none named after the member (`VLAYERLA.lda`, `USIX020L.lda`) resolved to nothing at all (`sourceFile: null`, `area: UNKNOWN`, `fieldCount: 0`) although the file was ingested. Item 186: for NatJav, `area` comes from the `INCLUDES` edge's `scope` (`PARAMETER`→`PDA`, `LOCAL`→`LDA`, `GLOBAL`→`GDA`), not from the file extension — a `.java` area has none, and the same area is a PDA for one module and an LDA for another (3,112 of korin's 5,818), so the value is per module by nature. The scope is applied only to resolved rows, so `UNKNOWN` again means exactly one thing: not resolved. One row per resolved definition, `(name, sourceFile)` (item 102) — never one row blending an arbitrary file with another definition's `fieldCount` |
|
||||
| `GET /modules/{name}/payload` | Natural XML wire-payload contract: `{tag, field, direction, source, lineNo, sourceFile}` — static `ADD-XML-LINE` idiom (`source=IDIOM`, item 45) or derived from the wrapper's interface PDA (`source=PDA`, item 46b). `sourceFile` is the file `lineNo` refers to (module for IDIOM, PDA for PDA) |
|
||||
| `GET /modules/{name}/comments?kind=&limit=&offset=` (`ac comments`) | **Item 141:** the module's **comment blocks** — `{text, kind, sourceFile, startLine, endLine, target, targetType, truncated}`, one row per contiguous block, ordered by line. `target`/`targetType` name the declaration the block documents: the declaration immediately below it, else the one enclosing it (so a file header banner documents the `MODULE`, a `/*` comment on a field's own line documents that field). `kind` is `JAVADOC`\|`LINE`\|`BLOCK` (Java) or `NATURAL_BANNER`\|`NATURAL_INLINE`\|`SAG` (Natural); `?kind=` filters to one. **`SAG` is excluded by default** — `**SAG` directives are generator metadata, not human notes, and would otherwise be most of the answer for every generated Natural module. Text is cut at 4 000 chars (`truncated:true`); read the file for the rest. Natural copycode comments belong to the **copycode's own module**, not to each includer. **Deep-gated:** comments come from the full parse, not the Tier-1 coarse scan, so the module is deep-ingested on demand and a still-shallow module answers `409 NOT_DEEPLY_INGESTED` rather than a misleading `[]` |
|
||||
| `GET /modules/{name}/dispatch-table` | Natural `DECIDE ON VALUE OF` routing table |
|
||||
|
||||
@@ -104,6 +104,343 @@ Item **141** — the one that made the API return a *wrong* answer rather than a
|
||||
"no `pur` module claims this program" from "a module claims it in a form the parser did not
|
||||
recognise", or the backlog list silently pads itself with the parser's own gaps.
|
||||
|
||||
## Language support — NatJav (2026-09-07)
|
||||
|
||||
**NatJav** is Software AG Natural transpiled to Java by the innoWake maxenso enterprise-engine. The
|
||||
files are syntactically Java (`@Natural class X extends NaturalProgram`, `perform("SUB")`,
|
||||
`callnat("Y")`, `read(VIEW)`), but the constructs are Natural's, so they map onto the *same* unified
|
||||
AST as `.nat`/`.lda` sources. Goal: a NatJav project answers the **identical API** as its Natural
|
||||
original. Reference corpus: `korin-natjav` (22,808 files, ingested as project `korin`).
|
||||
|
||||
- [x] **A. Foundation: module, language wiring, module shells** (2026-09-07)
|
||||
|
||||
New Maven module `ac-parser-natjav` (`NatJavParser`/`NatJavCoarseScanner`/`NatJavLineCounter`,
|
||||
JavaParser as the syntax layer), `SourceFiles.Language.NATJAV` selected by the *project's*
|
||||
`language` (the extension cannot decide — NatJav files are `.java`), and `natjav` accepted by
|
||||
`POST /api/projects/{name}` and `ac project create -l`.
|
||||
|
||||
A module's identity is its **Natural** name, read from `@Natural(moduleName=...)`: `V-MIT04` not
|
||||
`V_MIT04`, the GDA `COMMON` not `COMMON_DA`, `#ABBRUCH` not `XABBRUCH`. Deriving it from the class
|
||||
name by string surgery was measured and rejected — the rule "strip `_DA`/`_DDM`, `_` → `-`" is
|
||||
wrong for **72 of 7,093** annotated modules, because Natural names may themselves contain `_`
|
||||
(`V_MIT11`) and a leading `#` transpiles to `X`.
|
||||
|
||||
`moduleKind` comes from the base class and is therefore **exact**, where the Natural parser can
|
||||
only guess PROGRAM vs SUBPROGRAM from a `PARAMETER` section: PROGRAM 8,391 / SUBPROGRAM 5,856 /
|
||||
MAP 1,539 / FORM 354 / SUBROUTINE 325 / DDM 243 / HELP 142 / FDT 132, plus 5,816 `DATA_STRUCTURE`
|
||||
data areas — matching a base-class census of the corpus.
|
||||
|
||||
Verified end-to-end: 22,798 of 22,808 files ingested, 0 failures, 5 duplicate names reported. Two
|
||||
of those are genuine cross-library collisions (`ACOMMON`, `COMMON`); the other three are **defects
|
||||
in the migrated source** — `ZB80BM6.java` declares `moduleName="ZB80BM5"`, likewise
|
||||
`YNSCK3D`/`YNSCK3C` and `DRFDK412`/`DRFDK312` — i.e. copy-pasted headers, correctly surfaced
|
||||
rather than silently merged.
|
||||
|
||||
- [x] **B. Data structures** (2026-09-07)
|
||||
|
||||
The `DEFINE DATA` field tree — for data areas *and* for the local data a program declares inline
|
||||
(15,775 non-`_DA` files do). Levels come from the `@L1`..`@L7` annotations, not from Java nesting:
|
||||
`Group`/`Redefine`/view → `DATA_STRUCTURE`, `.constant(...)` → `CONSTANT`, everything else →
|
||||
`VARIABLE`, wired by `CONTAINS` exactly as the Natural parser does. `scope` comes from
|
||||
`@Parameter`/`@Local`/`@Global` (one per data area: 4,654 + 1,157 + 7 = all 5,818), which is what
|
||||
the `.pda`/`.lda`/`.gda` extension says for Natural.
|
||||
|
||||
`dataType` is normalised to the Natural spelling: a zero scale is dropped (`N(9,0)` → `N9`, since
|
||||
Natural writes `N9` and a mismatch would make every integer field of the migration unfindable by
|
||||
type), array bounds are appended with `/` (`A(200).defineArray(range(1, 9999))` → `A200/1:9999`),
|
||||
`range(1, $)` is Natural's `1:V`, and `A()` stays bare `A` (Natural `A DYNAMIC`).
|
||||
|
||||
**Names are never derived.** 63.5 % of the corpus's 1,163,879 declarations state the Natural name
|
||||
as a literal; a `Redefine` names a Java field whose Natural name is already known from the class's
|
||||
own symbol table (78,030 of them). For the rest the Java name stands, flagged
|
||||
`naturalNameKnown=false`. The tempting rule (`x` → `#`, `g` → `+`, `_` → `-`) was measured against
|
||||
the 709,376 named fields and is **wrong for 4.81 %** — Natural names may mix `_` and `-`
|
||||
(`#IP_EL_STV-CODE`), the same trap as item A's module names.
|
||||
|
||||
Two structural rules that are not obvious from the source:
|
||||
- A top-level group named like the data area **is** the area, not a member of it (3,773 of 5,818
|
||||
areas open that way); emitting both would put two identically named `DATA_STRUCTURE`s in one
|
||||
file. Same purpose as item 100's wrapper rule on the Natural side.
|
||||
- `@Using` names the *Java class* while the data area's node carries its *Natural* name, and no
|
||||
string rule bridges them (stripping `_DA` is right for only 90.46 % of areas). The placeholder
|
||||
therefore carries `className`, and a new NatJav-only enrichment step
|
||||
(`resolve-natjav-placeholder-names`) re-keys it before the existing name-joining resolution runs
|
||||
— a no-op for Natural and Java projects.
|
||||
|
||||
Deferred on purpose: a view's columns (name and length live in the DDM, another file — they come
|
||||
with D), and `paramPosition` (it feeds `ARG_TO_PARAM`, and how NatJav maps `callnat` arguments onto
|
||||
parameters is C's question; Natural's own data-area path sets neither).
|
||||
- [x] **B2. Two files claiming one module name** (2026-09-07)
|
||||
|
||||
Five names were claimed twice in the corpus, and three more collided *silently* — found only by
|
||||
querying the graph, because the duplicate guard keyed a data-area file on its filename stem
|
||||
(`WSK386P_DA`) while the graph names it by its Natural name (`WSK385P`), so `WSK385P`, `WSSC31P`
|
||||
and `YLRY401A` each sat in the graph as one ambiguous pair. The guard now uses the shell node's own
|
||||
name for NatJav.
|
||||
|
||||
The eight split into two causes, told apart by which file *owns* the name (its class name, less the
|
||||
`_DA`/`_DDM` suffix, is the declared name):
|
||||
|
||||
- **Library homonyms** — Natural names are unique per library, not globally, so `KORIN` and
|
||||
`ABFRAGE` each hold an `ACOMMON` and each its own GDA `COMMON`. Both are real: they are qualified
|
||||
as `KORIN.ACOMMON` / `ABFRAGE.ACOMMON`, and *only where the collision occurs*, so the other
|
||||
22,790 modules keep answering to their bare Natural name.
|
||||
- **A copied header the migration never updated** — `ZB80BM6.java` declares `moduleName="ZB80BM5"`
|
||||
though `ZB80BM5.java` exists and the two differ in 178 of ~570 lines; likewise `YNSCK3D`→
|
||||
`YNSCK3C`, `DRFDK412`→`DRFDK312`, and the three data-area pairs. The owner keeps the name, the
|
||||
impostor is ingested under its own class name and carries `naturalNameConflict` with the
|
||||
declaration it lost, so the defect stays visible instead of being quietly corrected.
|
||||
|
||||
Same-library claims with no owner are still left to the duplicate detection: nothing distinguishes
|
||||
them, and inventing a distinction would be worse than reporting the collision.
|
||||
|
||||
Cross-file by necessity (one file cannot know its name is contested), so it runs once over the
|
||||
whole walk before parsing, reading only each file's head. A no-op for Natural and Java projects.
|
||||
|
||||
- [x] **C. Call graph** (2026-09-08)
|
||||
|
||||
`FUNCTION` nodes from the parameterless, non-overriding methods; `CALLS` edges for
|
||||
`perform` (129,590), `callnat` (66,687), `fetchReturn` (14,617) and `fetch` (1,839), plus 3,106
|
||||
non-literal targets as `CALLNAT_DYNAMIC` placeholders carrying `dynamicVar`, the same shape the
|
||||
Natural parser uses so constant-propagation enrichment can recover them later.
|
||||
|
||||
Resolution needs no derived names: both sides of a call use the same Java identifier.
|
||||
`perform("FX_LEEREN")` names the method `void FX_LEEREN()` — **93.8 %** of targets resolve inside
|
||||
their own file, exactly and locally — and `callnat("V_MIT04")` names the class, resolved by the
|
||||
className step built in B.
|
||||
|
||||
**Better than the Natural side here:** the 6.2 % of `perform`s that call an *external* subroutine
|
||||
resolve too, because the transpiler made each one a class of its own (`NaturalSubroutine`) and
|
||||
**98.4 %** of those targets exist as such. The roadmap's item-71 retraction records this as an
|
||||
open gap for Natural ("a `PERFORM` to another module's subroutine stays an unresolved placeholder").
|
||||
|
||||
`run()` and the event blocks (`atTopOfPage`, `atWriteTitle`, `onError`, `atEndOfPage`, `doBreak` —
|
||||
~5,300 methods) are **not** functions. They are Natural's inline event blocks, which the Natural
|
||||
parser attributes to the module; making them functions would invent subroutines and add a module
|
||||
hop to every call inside one.
|
||||
|
||||
**Copycode is attribution, not equivalence — and cannot be more.** A Natural copycode is a separate
|
||||
`.cpy` file whose nodes are *shared* by every including module (`L4N-ENTER` is contained by 137).
|
||||
The transpiler baked each expansion into its host file: no second file, no shared node, the same
|
||||
routine duplicated 137 times. What survives is where a statement came from, and the 50,336 calls
|
||||
(23.3 % of all) inside an expansion carry `viaCopycode` + `includedAt`. No `originFile` (there is no
|
||||
file to point at) and no `includePath` (it identifies one expansion site among several sharing a
|
||||
node — here every node lives in the host file and takes the ordinary merge path). Markers are
|
||||
reliable: 47,313 blocks in 6,124 files, nesting at most 3, zero unbalanced, zero name mismatches.
|
||||
|
||||
- [x] **D. DB access** (2026-09-08)
|
||||
|
||||
`DB_ACCESS` nodes with `READS`/`WRITES` to a `DB_TABLE`, in the same shape the Natural parser
|
||||
produces: 27,602 reads (`read` 9,939, `get` 9,904, `find` 6,481, `histogram` 1,278), 7,409 writes
|
||||
(`update` 4,462, `delete` 1,710, `store` 1,237) and 9,294 work-file statements as
|
||||
`WORKFILE`/`WORKFILE_ACCESS`.
|
||||
|
||||
**Only a call with no receiver is a statement.** `UMK.SPARTEN.get(range(1, 4))` is an array
|
||||
subscript and occurs 435,129 times against 9,904 real `GET`s — reading `get(` without that
|
||||
distinction would have reported a fortyfold phantom. This was caught while measuring, not after.
|
||||
|
||||
**Writes name the loop, not the view.** `getDEL = get(MDUMKONT_DEL, withHold(), isn); … delete(getDEL);`
|
||||
— the table is known only at the read. The loop objects are class fields, so a file-wide map
|
||||
resolves them across method boundaries: of 5,576 `update`/`delete` statements **98.7 %** resolve,
|
||||
1.3 % have no visible assignment, and 18 (0.3 %) name an object assigned from two views and get an
|
||||
edge to each plus `ambiguousLoopTarget` — for a write, a reported possibility beats a silent
|
||||
omission.
|
||||
|
||||
**DDM handling revised a decision from item A.** A DDM was going to become the `DB_TABLE` itself,
|
||||
until measurement showed that *every* `DB_TABLE` in this system is file-less — the Natural parser,
|
||||
the Java parser and all 72 tables of the `ac` project — because an empty `sourceFile` is what makes
|
||||
the merge key converge every reference to a table onto one node. So the DDM file stays a `MODULE`
|
||||
with its columns, and `MODULE -[MAPS_TO]-> DB_TABLE` joins the two, exactly as the Java parser
|
||||
joins a JPA entity to its table. Column nodes carry the ADABAS short name, which the Natural source
|
||||
never shows.
|
||||
|
||||
The view columns deferred in B arrive here: a view declares them by Java name only, and
|
||||
`NatJavDdmLibrary` — a pre-parse index over the 243 DDM files, the same split as
|
||||
`CopycodeResolver`/`CopycodeLibrary` on the Natural side — supplies the Natural name always and the
|
||||
format for the 1,834 of 17,982 references that state one. The remaining 16,148 keep their length in
|
||||
the FDT classes; it is left absent rather than guessed.
|
||||
|
||||
- [x] **D2. Statements whose operand is not the view** (2026-09-08)
|
||||
|
||||
1,390 accesses (4.1 %) reached no table after D, because a statement rarely names the `@ViewOf`
|
||||
field itself. Two forms, both stating their origin in the field's **declared type**:
|
||||
|
||||
- `VVSTICH.VT VTO = VT;` — an alias of a local view, resolved file-locally, transitively (an alias
|
||||
may point at another).
|
||||
- `public final KORIN.Programmierer.P40.YLPOSLV_DA.SWPOS SWPOS = YLPOSLV_DA.SWPOS;` — a view
|
||||
declared in a `USING`'d data area, whose DDM only that other file knows. The pre-parse index from
|
||||
D now also carries the data areas' `@ViewOf` declarations: **243 DDMs and 173 data-area views**.
|
||||
|
||||
The declared type is a statement of fact, not a heuristic, so the resolution guesses nothing.
|
||||
|
||||
*A note on estimating this before implementing it:* the line-regex measurement I used to size the
|
||||
work was wrong twice — it missed fully qualified types (`KORIN.…P40.YLPOSLV_DA.SWPOS`) and counted
|
||||
locally declared views as unresolved — and moved from 47 % to 82 % as I fixed my own pattern. The
|
||||
implementation works on the JavaParser AST, where a field's type is read rather than matched; the
|
||||
coverage that matters was measured on the graph afterwards, not predicted.
|
||||
|
||||
- [x] **D3. A 30-minute refresh was two unindexed joins** (2026-09-08)
|
||||
|
||||
`korin` took 30:00 to refresh; it now takes **4:36**. Both causes were the same shape — a join the
|
||||
planner could not support with an index — and both only became expensive once the graph passed a
|
||||
million nodes.
|
||||
|
||||
- **`reap-table-access-edges` (item 86, existing code): 97 % of every persist batch, deleting 0
|
||||
rows.** The planner started at the target, because `sourceFile: ""` looks selective and is not
|
||||
(`korin` holds 23,012 file-less nodes), and expanded *every* `READS`/`WRITES` edge in the project
|
||||
(35,103) for *each* unwound file before filtering on the file at all — ~11.6 M expansions per
|
||||
200-file batch. A `USING INDEX src:AstNode(project, sourceFile)` applies the file filter first:
|
||||
**13.1 s → 0.3 s** per batch, persist **28.4 min → 2.5 min**. Measured against a Natural project
|
||||
(`ac`) as well: no regression.
|
||||
- **`RESOLVE_NATJAV_PLACEHOLDER_NAMES` (item B, mine): 24 minutes.** It joins a placeholder to the
|
||||
real module on `className` — the one join in the pipeline that is on neither `name` nor
|
||||
`sourceFile` — and there was no index for it: 22,769 placeholders against 45,577 className-bearing
|
||||
nodes, as a nested loop. New index `ast_node_project_classname`: **24 min → 1.3 s**. Narrow like
|
||||
item 164's, and empty for Natural and Java projects, which never write `className`.
|
||||
|
||||
A correctness gap came out of the same query: item 86 and item 106 both filtered
|
||||
`language: 'natural'`, so a NatJav file's `READS`/`WRITES` and `INCLUDES` edges were **never**
|
||||
reaped on re-ingest — the parser re-emits them exactly as the Natural parser does, so an edited
|
||||
access target would have left its old edge behind forever. Both now match `natural` *and* `natjav`.
|
||||
|
||||
Fixing that is also what exposed the second slow query: while natjav placeholders were never reaped,
|
||||
they survived from the first ingest and there was almost nothing left to re-key.
|
||||
|
||||
- [x] **E1. Statement-level dataflow** (2026-09-09)
|
||||
|
||||
`READS`/`WRITES` from the enclosing function (or the module) to the field, for the corpus's
|
||||
1,414,872 assignment statements — what makes `flow-forward`, `flow-backward` and `field-flow`
|
||||
answerable. Result: 1,258,601 `WRITES` and 607,822 `READS`.
|
||||
|
||||
**Operands name Java identifiers, field nodes carry Natural names**, and 77.6 % of assignment
|
||||
targets are qualified (`WSAIVP.INHALT`). File-local operands resolve through the symbol table from
|
||||
item B, exactly. For a field of a `USING`'d data area the pre-parse index (extended here to the
|
||||
areas' 739,346 named fields) supplies the Natural name, and the placeholder is emitted **under that
|
||||
name** — so the existing placeholder-field resolution, which joins on `name`, redirects it. No new
|
||||
query, and deliberately no join on a Java name: that is the shape that cost 24 minutes in D3.
|
||||
|
||||
Two defects the verification run caught, neither visible in unit tests alone:
|
||||
|
||||
- **A `REDEFINE` re-declares its field's Java identifier.** `@L1 N xWN_DATN = N(8,0,"#WN-DATN")`
|
||||
followed by `@L1 Redefine xWN_DATN` overwrote the variable in the symbol table with the
|
||||
redefinition group, so assignments wrote to the wrong node type. The Natural parser keeps
|
||||
`variables` and `dataStructures` separate for exactly this reason.
|
||||
- **Accumulators read their target.** `addTo(x, 1)` is `ADD 1 TO x`: the old value is an input. The
|
||||
Natural parser distinguishes `TO` (accumulate) from `GIVING` (plain write); modelling these
|
||||
write-only broke every backward flow through a counter. Visible in the ratio — 1.26 M writes
|
||||
against 540 k reads before, +45,390 reads after.
|
||||
|
||||
- [x] **E1b. `stamp-unresolved-placeholders`: 274 s → 7.6 s** (2026-09-09)
|
||||
|
||||
E1 raised `korin`'s placeholder count to 340,667, and the step that flags them became the single
|
||||
most expensive of the refresh. It asked "does a real twin exist?" as
|
||||
`OPTIONAL MATCH … count(real) = 0`, enumerating **every** node sharing the placeholder's
|
||||
`(type, name)` — thousands of rows per placeholder for a field name that recurs across files — only
|
||||
to compare against zero. `NOT EXISTS { … }` short-circuits at the first match and computes the
|
||||
identical flag (same 6,973 unresolved). The whole refresh went 13:16 → **6:47**.
|
||||
|
||||
Third finding of the same family after D3, and the first that helps all three languages rather than
|
||||
NatJav alone.
|
||||
|
||||
- [x] **E2. `USING` scope and parameter slots** (2026-09-09)
|
||||
|
||||
The transpiler distinguishes `parameterUsing` (13,696), `localUsing` (59,895) and `globalUsing`
|
||||
(1,698) — Natural's `PARAMETER`/`LOCAL`/`GLOBAL USING`. Item B treated all three alike, so a
|
||||
subprogram's interface was indistinguishable from its scratch data. Now recorded as `scope` on the
|
||||
`INCLUDES` edge, with `paramPosition` for the parameter slots in declaration order.
|
||||
|
||||
**On the edge, never on the node.** A data area is shared: the same one is parameter 0 of one
|
||||
subprogram and parameter 2 of another, so stamping the node would let whichever module was ingested
|
||||
last decide the answer for every caller.
|
||||
|
||||
*`ARG_TO_PARAM` itself is not built, and should not be.* NatJav passes shared data-area objects, not
|
||||
the caller's own variables: `V_MIT04` declares `parameterUsing(V_MIT04P_DA)` while its caller
|
||||
declares `localUsing(V_MIT04P_DA)` — the same area, hence the same node, hence a self-loop. Natural
|
||||
has the same property for `PARAMETER USING`, which is why it assigns `paramPosition` only to
|
||||
*inline* parameters; NatJav has none — all 13,696 parameter bindings go through `parameterUsing`.
|
||||
What the edge would express is already in the graph: both modules `INCLUDE` the same area.
|
||||
|
||||
- [x] **E3. XML wire payload** (2026-09-09)
|
||||
|
||||
`PAYLOAD_FIELD` nodes and the `xmlWrapper` module marker, so `/modules/{name}/payload` answers.
|
||||
|
||||
**None of item 45's Natural idioms exist here**: `COMPRESS '<' tag '>' value`,
|
||||
`DEFINE SUBROUTINE ADD-XML-LINE` and `CALLNAT 'YFRAMN07'` occur **zero** times. (An early estimate
|
||||
of "195 files with XML emit" was a grep artefact — the pattern matched `XMLLINP`, an unrelated data
|
||||
area.) The estate drives a framework subprogram through a parameter area instead:
|
||||
`assign(XMLLINP.TAG, "ADRZEILE1"); assign(XMLLINP.INHALT, …); callnat("XMLLIN", …)`. Two emitters
|
||||
share the shape — `XMLLIN` (7,086 calls) and `XMLDYN` (1,818) — for 10,238 tags in 176 files.
|
||||
|
||||
**The direction is measured, not defaulted.** Natural derives `REQUEST`/`RESPONSE` from
|
||||
`ADD-XML-LINE` vs `GET-XML-LINE`. Here the read counterpart `XMLREAD` exists, carries the
|
||||
mirror-image `#TAG`/`#INHALT` outputs — and is called **zero** times in the corpus. So every tag is
|
||||
an emit and `REQUEST` is a fact.
|
||||
|
||||
- [x] **E4a. Control flow** (2026-09-09)
|
||||
|
||||
`CONTROL_FLOW` blocks nested by containment, the same shape the Natural parser builds:
|
||||
`IF` 461,646 / `DECIDE` 28,589 / `FOR` 26,396 / `REPEAT` 10,257. The graph grew from 1.40 M to
|
||||
1.96 M nodes and the refresh from 6:19 to **7:58** — +558,000 nodes for +99 s, with none of the
|
||||
index surprises that D3 and E1b had at a third of the size.
|
||||
|
||||
Two things it deliberately does **not** emit:
|
||||
|
||||
- **A database loop is not control flow.** 20,749 of the corpus's `while` statements are
|
||||
`while (readUMK.hasNext())` — a transpiled `READ`/`FIND` loop. Natural models `IF`/`FOR`/`REPEAT`/
|
||||
`DECIDE` and never a `READ` loop. The 10,257 `REPEAT`s that remain are `hasNextWhile` loops,
|
||||
matching the corpus's 10,240 `RepeatLoop` declarations.
|
||||
- **A `DECIDE` is one construct, not a chain of ifs.** The transpiler lowers it into `if`/`else if`
|
||||
around a counter local, and the counter is what makes the chain recognisable.
|
||||
|
||||
*Three rounds, each fault hidden by the previous one, and all three found by comparing two
|
||||
independent numbers — never by the node count, which looked plausible every time:*
|
||||
|
||||
1. Recursing into the chain's `else` branch found the same pattern again → every dispatch doubled.
|
||||
2. Searching the arm's whole subtree for the counter matched it through a nested `if` → a dispatch
|
||||
re-recognised at each nesting level (1,917 DECIDEs inside their own DECIDE).
|
||||
3. Deciding "is this a dispatch?" by "does it have literal values?" classified every dispatch on
|
||||
numbers as a plain `if`.
|
||||
|
||||
*A residual that is not pinned down:* 17,607 lowered dispatches against 15,361 `decideCounter`
|
||||
declarations. Double emission is ruled out (no two nodes share a position) and nesting was verified
|
||||
against the source; a counter reused across chains explains the rest plausibly but is not proven.
|
||||
|
||||
- [x] **E4b. Comments** (2026-09-09)
|
||||
|
||||
666,637 `COMMENT` nodes with 670,619 `DOCUMENTS` edges. The graph went from 1.96 M to 2.63 M nodes
|
||||
and the refresh from 7:58 to **9:39** — the last and largest step, and still no index surprise.
|
||||
|
||||
**The grouping is now shared, not copied.** NatJav *is* Java syntax, so the block folding and the
|
||||
`DOCUMENTS` attachment were already in `JavaParser`, privately. They moved to a public
|
||||
`JavaComments` in `ac-parser-java`, parameterised by language and by a text filter; both parsers
|
||||
call it. The 24 `JavaParserTest` cases stayed green unchanged — that was the regression proof for
|
||||
touching tested foreign code.
|
||||
|
||||
**What differs is the text.** 26 % of the corpus's 2,473,041 comment lines are transpiler
|
||||
bookkeeping whose content is *already* structured in the graph: `@@INCLUDE` markers as a call's
|
||||
`viaCopycode`, the header's `@FUNCTION`/`@OWNER` as the module's `description`/`owner`. Stored
|
||||
again as comment text it would bury the real comments — `search_by_value` with `includeComments`
|
||||
would answer a quarter transpiler markers.
|
||||
|
||||
**Filtered per line, not per block**, and that distinction was the finding of the validation pass:
|
||||
the migration header looks like pure framing but of the 14,749 files carrying one, **10,466 (71 %)**
|
||||
have real prose inside it, often a fuller description than the single `@FUNCTION` line. Dropping the
|
||||
block would have deleted the documentation of 10,466 modules.
|
||||
|
||||
Three things only the corpus run showed, none of them visible in unit tests:
|
||||
|
||||
- **Two comment markers sit on top of each other.** The Java layer strips the Javadoc `*`; beneath
|
||||
it is the `*` with which Natural marked a full-line comment. A Natural ingest stores neither.
|
||||
- **The generator stamps per-line metadata** (`*innoWake versionRep2Str:`, `datumUmsetzung`,
|
||||
`originalObjectName`, ~5,400 each) that the `Generated by innoWake` pattern did not match.
|
||||
- **The corpus spells "migration" two ways:** 57,484 `migration process` and 1,347
|
||||
`migraion process` — a typo in the 2014 migration template, inherited by 1,347 files. Matching
|
||||
only the correct spelling let 442 blocks of framing through.
|
||||
|
||||
Together those three were 5,434 blocks, 0.8 % — a share at which the node count still looks
|
||||
perfectly plausible. They surfaced only by searching the result for residues of the very patterns
|
||||
the filter is meant to remove.
|
||||
|
||||
## Known bugs
|
||||
|
||||
- [ ] **152. `NaturalLines.stripInlineComment` is not quote-aware — a `/*` inside a string literal
|
||||
@@ -415,6 +752,306 @@ after probing and are recorded at the end, so nobody re-files them.
|
||||
|
||||
## Ingest performance
|
||||
|
||||
- [x] **181. Six enrichment steps ran in one transaction each, sized by the project rather than by the query** (2026-09-09)
|
||||
|
||||
Found by ingesting `k3` (27,687 Java files) and then deep-refreshing `korin` (22,808 NatJav files).
|
||||
Both failed, four times between them, with `dbms.memory.transaction.total.max threshold reached`.
|
||||
The Neo4j heap was raised 2G -> 4G on the way, which lifted the per-transaction limit from 1.4 GiB
|
||||
to 2.8 GiB and bought exactly one more failing step. **Raising the heap only moves the ceiling.**
|
||||
|
||||
The pattern is one query shape: `MATCH ... MERGE ... DELETE r` (or a bare bulk `DELETE`) over every
|
||||
edge that points at a placeholder. The row count follows the corpus, not the query, and nothing
|
||||
bounded it. Measured on `korin`:
|
||||
|
||||
| step | work in one transaction |
|
||||
|---|---:|
|
||||
| `resolve-placeholder INCLUDES` | 73,519 edges redirected |
|
||||
| `resolve-field-placeholder READS` | 434,372 created / 279,746 deleted, 2.34 M properties |
|
||||
| `resolve-field-placeholder WRITES` | 1,054,093 created / 674,708 deleted, 5.68 M properties |
|
||||
| `delete-resolved-field-contains` | **4,780,020 edges deleted** |
|
||||
| `delete-resolved-placeholders` | 402,217 nodes deleted |
|
||||
|
||||
All six now run as `CALL { ... } IN TRANSACTIONS OF $batchSize ROWS`, which needs an auto-commit
|
||||
transaction — that is what `EnrichmentStep.batched` arranges. Batched, the 4.78 M-edge sweep takes
|
||||
**48.8 s at 383 MB of server heap**, against a step that previously could not finish at any heap
|
||||
size. Whole-root form only; the scoped form is bounded by the caller's `$names`.
|
||||
|
||||
**Batching is not a drop-in wrapper, and that is the part worth remembering.** Three of these
|
||||
queries fan out over several resolution candidates, and the fan-out sat *outside* the write, so the
|
||||
same edge appeared in several rows. In one transaction Cypher's eager evaluation hid it — every
|
||||
match ran before any delete. Across batch boundaries the second row reads an edge whose `DELETE`
|
||||
has already committed, and the step dies with `Relationship with id N has been deleted in this
|
||||
transaction`. That is a real regression this change first *introduced* and then fixed: on `korin`
|
||||
1,147 of 63,519 `INCLUDES` edges into placeholders have more than one candidate. The fix is to
|
||||
aggregate the candidates per edge and move the `UNWIND` inside the batch, ending with
|
||||
`WITH DISTINCT r DELETE r` so the edge is deleted once, after every target is merged. Verified with
|
||||
`EXPLAIN` that item 159's planner hoist survives the added aggregation.
|
||||
|
||||
Effect on the whole run, `korin` deep: **1,856 s, 52/52 steps, 0 failures**, and the placeholder
|
||||
population falls from 388,439 to 15,134 (27 % -> 96 % resolved). `k3` deep: **1,094 s, 27,687
|
||||
files, 0 failures**, adding 79,168 `ARG_TO_PARAM` edges the call-graph mode never built.
|
||||
|
||||
**Second run, and it changes what the first one means.** Repeated with no code change:
|
||||
`korin` **1,856 s -> 712 s**, `k3` **1,094 s -> 1,075 s**. `k3` is simply reproducible (every step
|
||||
within noise, node count identical at 643,936). `korin` is not, and the counters say why: step 29
|
||||
deleted **4,780,020** CONTAINS edges in run 1 against **342,132** in run 2, because run 1 was
|
||||
clearing a backlog the pre-fix 08:38 ingest had left in the graph. Steps 25/26 then did *identical*
|
||||
work in both runs (279,744 / 674,708 edges deleted) at **9x and 5x** the speed, because the
|
||||
`(real)-[:CONTAINS*1..10]->(realv)` expansion walks a graph carrying 3.4 M fewer CONTAINS edges.
|
||||
So **712 s is the steady-state cost of a `korin` deep refresh**; 1,856 s was a one-off catch-up.
|
||||
This also settles why the 08:38 run finished in 626 s: it did not resolve the fields, it deferred
|
||||
the bill.
|
||||
|
||||
**The second and third runs exposed a separate bug — see item 183.** `korin` gains exactly +34
|
||||
nodes per deep refresh (2,289,657 -> 2,289,691 -> 2,289,725), all placeholder `DATA_STRUCTURE`s.
|
||||
Not non-determinism and not an artefact of run 1's backlog, both of which this item claimed before
|
||||
the third run settled it: a monotonic leak. Timing itself is reproducible (712 s, 723 s).
|
||||
|
||||
- [x] **183. Ownerless placeholders are re-created every ingest and swept by nothing** (found 2026-09-09, fixed 2026-09-10)
|
||||
|
||||
A `korin` deep refresh adds **+34 nodes and +102 edges every time it runs**, on unchanged input and
|
||||
unchanged code. Found by running it three times: 477 -> 511 -> 545 placeholder `DATA_STRUCTURE`s,
|
||||
16 copies of each of 34 names, one per ingest since the project was created. Every other
|
||||
project/type combination in the graph is clean (`ac`, `k3`, and `korin`'s own MODULE, VARIABLE,
|
||||
CONSTANT, DB_TABLE placeholders all show zero duplicate identities), so this is specific to
|
||||
ownerless placeholders, which today means NatJav data areas.
|
||||
|
||||
**They fall through all three cleanup mechanisms**, and the third condition is the one that makes
|
||||
this invisible:
|
||||
|
||||
| sweep | why it misses them |
|
||||
|---|---|
|
||||
| `DELETE_STALE_FILE_NODES` | keys on `sourceFile`; a placeholder's is `""` |
|
||||
| `DELETE_STALE_PLACEHOLDER_NODES` | filters `(n:VARIABLE OR n:CONSTANT)` **and** `n.ownerModule IN $owners` - these are `DATA_STRUCTURE` **and** their `ownerModule` is the empty string, so they fail both halves |
|
||||
| `delete-resolved-placeholders` | needs an edgeless node; each copy carries `CONTAINS` edges to placeholder fields |
|
||||
|
||||
**Not just a node count.** Every copy, old and new, points at the *same current-generation* child
|
||||
fields - the name-driven field resolution attaches new children to all of them. So a traversal
|
||||
asking which data area contains a given field got **16 answers where one is true**. Query results
|
||||
were affected, not only `count(*)`.
|
||||
|
||||
`ownerModule` being empty is deliberate and correct: a data area is shared between modules, which
|
||||
is the same reason `SCOPE` and `PARAM_POSITION` live on the edge rather than the node. What follows
|
||||
from "shared" is that the placeholder must keep **one identity across ingests** instead of being
|
||||
minted fresh each time - so the fix is a deterministic `nid` derived from (type, name), letting the
|
||||
re-ingest MERGE onto the existing node. The alternative, a project-wide generation sweep for
|
||||
ownerless placeholders gated to whole-root refreshes, treats the symptom and leaves the identity
|
||||
question open. Either touches the persist path and wants its own proposal/validate pass.
|
||||
|
||||
**The 510 stale copies were deleted by hand on 2026-09-09**; the leak was closed on 2026-09-10.
|
||||
|
||||
**Root cause, found while fixing: it was not the sweeps, it was the rename.** The parser emits the
|
||||
`USING` placeholder under the Java class name (`K3VT000P_DA`), and `resolve-natjav-placeholder-names`
|
||||
— step 1 of the pipeline, added for NatJav — rewrites `name` to the Natural name (`K3VT000P`).
|
||||
`name` is part of the node merge key, so the next ingest's `MERGE` on `K3VT000P_DA` finds nothing
|
||||
and mints a second node, which step 1 renames again. Deterministic, one copy per ingest, exactly the
|
||||
34 areas that get renamed; the 35th, `COMMON_DA`, is never renamed (two real candidates, see 184)
|
||||
and is the one that stayed stable. The step's own javadoc claimed idempotency — true for the same
|
||||
node, false for the re-ingest.
|
||||
|
||||
**Fix:** the rename folds. If a same-type placeholder already carries the target name **and the
|
||||
same `className`**, it is deleted and the fresh one takes the name. Deleting the old copy loses
|
||||
nothing: a renamed placeholder has 0 incoming `INCLUDES` (all 1,678 in the graph sit on the
|
||||
unrenamed `COMMON_DA`) and its `CONTAINS` edges are re-created by every parse. The `className`
|
||||
guard makes a homonym unreachable. A `collect`/`UNWIND` barrier reads every stale node before any
|
||||
is deleted. Cypher only, no Java, no index, no batching (~34 rows). No test references the step;
|
||||
the verification is the test that found the bug — two consecutive deep refreshes with identical
|
||||
node counts.
|
||||
|
||||
**Verified 2026-09-10, two consecutive `korin` deep refreshes on the deployed fix.** Step 1 logged
|
||||
`nodes +0/-34, rels +0/-380, props 5855` in **both** runs — the fresh parse minted 34 class-name
|
||||
placeholders, the fold deleted the 34 previous-generation copies and their 380 `CONTAINS` edges,
|
||||
and renamed the fresh ones. All 13 node-type counts, all 4 placeholder-type counts and
|
||||
`dup-copies = 0` were identical before, after run A (738 s) and after run B (727 s). Before the
|
||||
fix the same comparison showed `DATA_STRUCTURE +34` three times in a row.
|
||||
|
||||
**Rejected alternative:** resolving the Natural name at parse time through the DDM library, which
|
||||
already reads every `_DA.java`. Right in principle — an index, not a string rule — but it walks into
|
||||
two traps the class-name join handles today: a copied header (item NatJav fix #8, `WSK386P_DA`
|
||||
declaring `WSK385P`) would yield the *wrong* name, and library homonyms (`COMMON_DA` in two
|
||||
libraries) need the referencing module's library to disambiguate. That is a feature (184), not a
|
||||
fix.
|
||||
|
||||
*Method note, because it nearly went unrecorded:* a single refresh looked clean, two looked like
|
||||
non-determinism worth tolerating, and only the third showed the constant +34 that names it a leak.
|
||||
Two of the three explanations offered along the way were wrong.
|
||||
|
||||
- [x] **184. Library-aware resolution of homonymous data areas** (found 2026-09-10, implemented 2026-09-10)
|
||||
|
||||
`COMMON_DA` exists in `KORIN` and `ABFRAGE`. Its placeholder is never renamed (two candidates) and
|
||||
so never resolved, and everything referenced through it stays unresolved: **1,678 `INCLUDES` and
|
||||
13,311 field placeholders** — 97 % of all unresolved field references in `korin` hang under this one
|
||||
name. The refusal is correct today: binding arbitrarily would invent a dependency. But the
|
||||
information to bind *correctly* exists — `NatJavIdentities` already qualifies the real nodes as
|
||||
`KORIN.COMMON` / `ABFRAGE.COMMON` by top-level directory, and a referencing module's own
|
||||
`sourceFile` names its library. Resolving a placeholder to the candidate in the module's library
|
||||
would close the single largest unresolved mass in the corpus. Natural-side check needed first:
|
||||
does `upms` have the same shape, and is "same top-level directory" the right notion of library there.
|
||||
|
||||
*Recorded because it was misread once:* a count of "unresolved children whose real area has a
|
||||
same-named field" reported 13,311 as a resolution bug. It matched `real` by class name and so hit
|
||||
both homonyms; the field exists in one of them. Every non-homonym area had 0 such children.
|
||||
|
||||
**Implemented.** The source of the library is not the including module's directory — 6 `CSTENTW`
|
||||
modules include `KORIN.COMMON_DA`, and their directory would have bound them wrong — but the
|
||||
`@Using` itself: 70 % (52,908) are qualified with the **full Java package**
|
||||
(`KORIN.Vertrag.Beteiligung.ABGB02P_DA`), which the transpiler wrote after resolving the steplib
|
||||
chain; the other 30 % (22,381) are unqualified and resolve by Java's same-package rule — 1,193 of
|
||||
1,193 checked name a class in the same directory, none an import. `NatJavUsings` records that
|
||||
package as `usingPackage` on the placeholder (node name and merge key unchanged; the placeholder
|
||||
map stays keyed by simple class name because `NatJavDbAccess.exposures()` looks areas up by it).
|
||||
`resolve-natjav-placeholder-names` applies it only where the class name alone yields more than one
|
||||
candidate: `c.package = ph.usingPackage`, a plain string equality — `package` is present on all
|
||||
5,818 real areas. One module including the same class name from two libraries (0 in the corpus)
|
||||
gets no `usingPackage` and is refused as before. Three tests in `NatJavFieldsTest`.
|
||||
|
||||
**Verified 2026-09-10, two consecutive `korin` deep refreshes (755 s, 742 s), all 18 count rows
|
||||
identical between them.** Against the pre-fix graph: `INCLUDES` on the `COMMON_DA` placeholder
|
||||
1,678 -> 0, on `KORIN.COMMON` 0 -> **1,678**, on `ABFRAGE.COMMON` 0 -> 0; placeholder `VARIABLE`
|
||||
13,691 -> **380** (exactly the fields the real area does not declare, counted before the fix was
|
||||
built); placeholder `DATA_STRUCTURE` 35 -> 34; no other row moved. Step counters against the
|
||||
pre-fix runs: step 1 `props` 5,855 -> 5,856 (the one added rename), step 3 `+1,678/-1,678`, step
|
||||
33 `-310,782 -> -324,094` (+13,312 = 13,311 fields + the area). Every `INCLUDES` edge in the
|
||||
project (78,533) now points at a real area. Through the API,
|
||||
`GET /korin/modules/AF000/data-structures` and `.../DISKRECH/data-structures` (the `CSTENTW`
|
||||
module) both list `KORIN.COMMON`, `fieldCount 616`, `sourceFile KORIN/COMMON_DA.java`.
|
||||
|
||||
*Not fixed here, same blindness:* `NatJavDdmLibrary` keys `areaFields` by simple class name too
|
||||
(`putIfAbsent` keeps whichever `COMMON_DA.java` the walk met first). Harmless in this corpus only
|
||||
because the two `COMMON` areas have identical field names (587 shared, 0 exclusive); a
|
||||
non-identical homonym pair would get one file's Natural field names for both. `usingPackage`
|
||||
is the key that would fix it.
|
||||
|
||||
- [ ] **185. No uniqueness constraint on the `AstNode` merge key** (found 2026-09-10)
|
||||
|
||||
`MERGE (node:AstNode {type, name, sourceFile, project})` runs with a range index and no constraint
|
||||
(`SHOW CONSTRAINTS`: only `Project.name`). Without a constraint `MERGE` is not atomic across
|
||||
concurrent transactions, and placeholders (`sourceFile = ""`, shared by every referencing file)
|
||||
are the keys most likely to be merged from two batches at once. Not the cause of 183 — that was
|
||||
deterministic — and no duplicate has been observed for any other type/project (0 across `ac`,
|
||||
`k3`, and `korin`'s MODULE/VARIABLE/CONSTANT/DB_TABLE). A four-property constraint over 2.9 M
|
||||
nodes has a write cost on every node merge; measure it against the persist phase before adding.
|
||||
|
||||
|
||||
- [x] **186. `data-structures` reports `area: UNKNOWN` for every resolved NatJav data area** (found 2026-09-10, implemented 2026-09-10)
|
||||
|
||||
Found by the API check that closed item 184: `GET /korin/modules/AF000/data-structures` answers
|
||||
`KORIN.COMMON, fieldCount 616, sourceFile KORIN/COMMON_DA.java, area: UNKNOWN`. Measured: **all
|
||||
78,533** NatJav `INCLUDES` edges point at `.java` areas, so every resolved `USING` row in every
|
||||
NatJav module says `UNKNOWN`. That collides with the documented meaning — the API guide defines
|
||||
`area: UNKNOWN` together with `sourceFile: null, fieldCount: 0` as "not resolved / file not
|
||||
ingested". An agent reading the doc draws the wrong conclusion from a fully resolved row.
|
||||
|
||||
**The query's own premise is out of date.** `MODULE_DATA_STRUCTURES` derives `area` from the file
|
||||
extension (`.pda/.lda/.gda`) and its javadoc says why: "the precise PARAMETER/LOCAL/GLOBAL USING
|
||||
scope is not stored on the edge, so area is the available proxy". Since item B (NatJav `@Using`)
|
||||
the scope **is** on the edge — on 100 % of the 78,533 (LOCAL 62,763, PARAMETER 14,072, GLOBAL
|
||||
1,698). The 196 Natural `INCLUDES` edges carry none, so the extension stays as the fallback.
|
||||
|
||||
**Why the edge, not the node, is the only correct source:** 3,112 of 5,818 NatJav areas are
|
||||
included under two different scopes by different modules — `YPKUNWE` *is* a PDA for one module and
|
||||
an LDA for another. That is the very reason `SCOPE` lives on the edge (item B). A per-node `area`
|
||||
would be wrong for 3,112 areas; per (module, area) it is single-valued — measured: 78,533 pairs,
|
||||
each with exactly one edge and one scope — and `data-structures` is a per-module endpoint.
|
||||
|
||||
**Fix:** carry `r.scope` through the aggregation and map `PARAMETER -> PDA`, `LOCAL -> LDA`,
|
||||
`GLOBAL -> GDA`, else the extension rule as today. One query, its javadoc, one paragraph in the API
|
||||
guide (a response field changes value for NatJav), verification through the API on a GLOBAL
|
||||
(`AF000`/`DISKRECH` -> `GDA`) and a PARAMETER include. No parser change, no refresh — the data is
|
||||
already in the graph.
|
||||
|
||||
**Implemented.** `scope` travels through the aggregation inside the per-definition map, so the
|
||||
item-102 grouping by `(name, sourceFile)` is untouched. **VALIDATE caught one thing the proposal
|
||||
had wrong:** the 34 permanently unresolved areas carry a scope on their edge too, and the naive
|
||||
CASE would have reported them as `LDA` with `sourceFile: null, fieldCount: 0` — destroying the
|
||||
documented "UNKNOWN = not resolved" signal from the other side. The scope is therefore applied only
|
||||
where `sourceFile IS NOT NULL`. Query javadoc corrected (it claimed the scope is not on the edge —
|
||||
true before NatJav), API guide updated in the `data-structures` row and the NatJav
|
||||
"Fields and data areas" section. CLI unchanged by construction (`ModuleDataStructuresCommand`
|
||||
prints the response as is).
|
||||
|
||||
**Verified 2026-09-10 on the deployed build, through the API:** `AF000 -> KORIN.COMMON: GDA, 616`;
|
||||
`DISKRECH -> KORIN.COMMON: GDA, 616` (the `CSTENTW` module — the case a directory heuristic would
|
||||
have bound wrong); `YSBIDATE -> YPBIDATE: PDA, 10`; `YTKUNWE -> YPKUNWE: LDA, 4`. `AF000`'s rows
|
||||
carry only `INLINE`/`GDA`/`LDA` — no `UNKNOWN` on a resolved row. Fallback path: `AnalysisResourceIT`
|
||||
85/85 green including `MSGAREA -> PDA` (Natural, extension rule). The `UNKNOWN` branch can no longer
|
||||
be shown on korin through the API — since item 184 no unresolved area has an includer left, the 34
|
||||
survivors hang only on 380 unresolvable fields — so it is covered by the IT and the query text.
|
||||
|
||||
*Gap noted while verifying, closed by item 187:* `ac-code-server` had **no NatJav integration test
|
||||
at all** — no `*IT` ingested a NatJav fixture through the API. Everything NatJav is covered by parser
|
||||
unit tests and the live corpus. The Natural `MSGAREA -> PDA` assertion in `AnalysisResourceIT`
|
||||
covers this item's fallback path only.
|
||||
|
||||
*Also seen, recorded so it is not chased twice:* `AnalysisResourceIT` failed **1 of 3** runs at
|
||||
class level — `Tests run: 1, Failures: 1` after 21.6 s, no report file written — while a
|
||||
`./manage-ac.sh deploy` docker build was running on the same host. The two runs on an idle machine
|
||||
(with and without `-q -DskipUTs=true`) passed 85/85 in ~60 s each. Not this item, not the flags: a
|
||||
boot-time flake under load (Quarkus + Testcontainers Neo4j starting next to a build). Cause not
|
||||
diagnosed.
|
||||
|
||||
- [x] **187. A NatJav integration test** (2026-09-10)
|
||||
|
||||
`ac-code-server` had 77 `*IT` classes and not one that ingests NatJav: a whole language verified only
|
||||
by parser unit tests and by the live reference corpus — every fix of 2026-09-09/10 included.
|
||||
`NatJavIngestIT` runs a six-file project through creation, deep refresh and the API. The root
|
||||
reproduces the corpus's shape where it matters: libraries are directories, `COMMON_DA.java` exists
|
||||
in `KORIN` and `ABFRAGE`, and `CSTENTW/DISKTEST` includes the KORIN one. Fixtures come from the
|
||||
parser module (`V_MIT04`, `YPUMPRUE_DA`, `COMMON_DA`) plus two ten-line programs and the ABFRAGE
|
||||
copy with its `package` line changed — which is what makes the item-184 join on `package`, not on
|
||||
directory, load-bearing.
|
||||
|
||||
One assertion per fix it protects: Natural-name identity and `moduleKind` (A/B); homonym by
|
||||
qualifier from a third library (184); homonym by the file's own package for an unqualified
|
||||
`@Using` — deliberately on the homonym, because with a single candidate the class name alone would
|
||||
pass (184); `area` from the edge scope on resolved rows and `UNKNOWN` on unresolved ones, including
|
||||
a PARAMETER include without a file (186); CALLNAT to an unresolved callee (B); and a second deep
|
||||
refresh that must leave `data-structures` byte-identical and the identifier search at exactly one
|
||||
hit per area (183).
|
||||
|
||||
**Green 2026-09-10: 6/6 in 41.9 s** (one Quarkus boot, two deep refreshes of the six-file root). The
|
||||
first run failed one assertion for a reason worth keeping: there is **no bare `GET /modules/{name}`
|
||||
route** — only sub-resources (`/context`, `/functions`, `/data-structures`, …). The identity check
|
||||
therefore goes through `GET /modules?sourceFile=…` (name, `moduleKind`) and then addresses the module
|
||||
by that name, the way every sub-resource call does. Five of six assertions — both item-184 forms,
|
||||
186, CALLNAT, and the 183 identity check — passed at the first attempt on a fresh, empty graph,
|
||||
which is the first time today's Cypher changes ran anywhere but on the accumulated `korin` store.
|
||||
|
||||
*Deliberately not in it:* a faithful reproduction of the 183 leak — a renamed area that survives
|
||||
because a child field is known to the DDM library but absent from the real tree. That condition
|
||||
arises only through the library's homonym-blind `putIfAbsent`, and which `COMMON_DA.java` wins is
|
||||
walk-order dependent. An order-dependent test is worse than none; the identity check above tests
|
||||
the property the leak violated.
|
||||
|
||||
- [ ] **188. The Neo4j service has no restart policy; the two services that depend on it do** (found 2026-09-24)
|
||||
|
||||
Found on resuming after 13 days: `ac-code-server` and `ac-ui` were "Up 5 minutes", the readiness
|
||||
check was DOWN with `UnknownHostException: neo4j`, and `agenticcode-neo4j` had exited with code
|
||||
255 at 14:11:03Z — `oom=false`, `restarts=0`, and not one log line since its start on 2026-09-09.
|
||||
Exit 255 with no log is the daemon or host going away, not the database failing. The compose file
|
||||
gives `restart: unless-stopped` to `ac-code-server` and `ac-ui` only; `neo4j` has none, so after a
|
||||
Docker or host restart the stack comes back without its database and every API call fails until
|
||||
someone notices. The fix is one line on the `neo4j` service, plus the question whether the server
|
||||
should keep retrying the connection rather than caching the failed DNS lookup — the
|
||||
`UnknownHostException` suggests it resolved once and did not try again. Config change; the human
|
||||
runs the stack (CLAUDE.md), so it is filed rather than applied.
|
||||
|
||||
- [ ] **182. A deep refresh outlives its own HTTP request** (found 2026-09-09)
|
||||
|
||||
Quarkus's default `quarkus.http.idle-timeout` is 30 minutes. The first `korin` deep refresh after
|
||||
item 181 took 30:56 (a one-off backlog run — see 181; steady state is 712-755 s), and the
|
||||
connection was dropped mid-run: the client got an empty `200` after exactly 1,800,102 ms with a
|
||||
Netty stack trace in the log, while **the refresh kept running server-side** and finished
|
||||
normally. Today's refreshes stay under the limit; the failure mode is intact for any project or
|
||||
backlog that crosses it. So the result is correct and the caller cannot see it — it has to be read
|
||||
back from `GET /api/projects`. Worse, a caller that retries on the empty response would start a
|
||||
second refresh over a running one.
|
||||
|
||||
Never surfaced before because no refresh had crossed 30 minutes. Two candidate fixes: raise the
|
||||
idle timeout, or — better, and what the CLI would want anyway — make refresh asynchronous, return a
|
||||
job id immediately and expose progress. The step-level progress is already logged, so there is
|
||||
something real to report.
|
||||
|
||||
- [ ] **180. The `DOCUMENTS` edge carries two strings and nothing else — 20.2 % of all edges**
|
||||
(found 2026-09-07 while measuring item 179)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user