Handle Parsing Issues
Real SAS codebases are large, macro-heavy, and occasionally syntactically invalid. A robust tool must inspect ParsingResult.issues, decide what severity levels matter, and often continue with a partial AST rather than aborting.
Add Dependencies
- Kotlin
- Java
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/sas-parser-with-dependencies-1.6.5-all.jar"))
}
repositories {
mavenLocal()
mavenCentral()
flatDir {
dirs("deps")
}
}
dependencies {
implementation(files("deps/sas-parser-with-dependencies-1.6.5-all.jar"))
implementation "com.strumenta.kolasu:kolasu-javalib:1.5.96"
}
Report issues by severity
Every parse returns a ParsingResult with a isCorrect flag and a list of Issue objects. Print them grouped by severity so operators can tell errors from warnings.
- Kotlin
- Java
import com.strumenta.kolasu.model.IssueSeverity
import com.strumenta.kolasu.commercial.LicenseManager
import com.strumenta.sas.parser.SASLanguage
import java.io.File
fun parseWithReporting(sasFile: File, license: File) {
LicenseManager.registerLicense(license)
val sas = SASLanguage()
sas.parseNativeSQL = true
val result = sas.parse(sasFile)
if (result.isCorrect) {
println("Parsed ${sasFile.name} with no issues.")
} else {
println("Parsed ${sasFile.name} with ${result.issues.size} issue(s).")
}
result.issues.forEach { issue ->
val location = issue.position?.let { " @ $it" } ?: ""
when (issue.severity) {
IssueSeverity.INFO -> println("INFO: ${issue.message}$location")
IssueSeverity.WARNING -> System.err.println("WARNING: ${issue.message}$location")
IssueSeverity.ERROR -> System.err.println("ERROR: ${issue.message}$location")
}
}
val root = result.root
if (root == null) {
println("No AST produced — cannot continue analysis.")
return
}
println("AST root: ${root.statementsAndDeclarations.size} top-level element(s)")
}
import com.strumenta.kolasu.model.Issue;
import com.strumenta.kolasu.model.IssueSeverity;
import com.strumenta.kolasu.parsing.ParsingResult;
import com.strumenta.sas.ast.SourceFile;
import com.strumenta.kolasu.commercial.LicenseManager;
import com.strumenta.sas.parser.SASLanguage;
import java.io.File;
public class ParsingIssues {
public static void parseWithReporting(File sasFile, File license) {
LicenseManager.INSTANCE.registerLicense(license);
SASLanguage sas = new SASLanguage();
sas.setParseNativeSQL(true);
ParsingResult<SourceFile> result = sas.parse(sasFile);
if (result.getIsCorrect()) {
System.out.println("Parsed " + sasFile.getName() + " with no issues.");
} else {
System.out.println("Parsed " + sasFile.getName() + " with "
+ result.getIssues().size() + " issue(s).");
}
for (Issue issue : result.getIssues()) {
String location = issue.getPosition() != null
? " @ " + issue.getPosition()
: "";
switch (issue.getSeverity()) {
case INFO:
System.out.println("INFO: " + issue.getMessage() + location);
break;
case WARNING:
System.err.println("WARNING: " + issue.getMessage() + location);
break;
case ERROR:
System.err.println("ERROR: " + issue.getMessage() + location);
break;
}
}
SourceFile root = result.getRoot();
if (root == null) {
System.out.println("No AST produced — cannot continue analysis.");
return;
}
System.out.println("AST root: " + root.getStatementsAndDeclarations().size()
+ " top-level element(s)");
}
}
Continue with a partial AST
Warnings and even some errors do not always prevent building an AST. Batch analyzers typically:
- Log all issues.
- Skip the file only when
rootisnull. - Optionally ignore specific severities for downstream passes.
- Kotlin
- Java
fun shouldAnalyze(result: com.strumenta.kolasu.parsing.ParsingResult<*>): Boolean {
if (result.root == null) return false
val blocking = result.issues.any { it.severity == IssueSeverity.ERROR }
if (blocking) {
println("File has errors but AST is available — proceeding with best-effort analysis.")
}
return true
}
import com.strumenta.kolasu.model.IssueSeverity;
import com.strumenta.kolasu.parsing.ParsingResult;
public class PartialAst {
public static boolean shouldAnalyze(ParsingResult<?> result) {
if (result.getRoot() == null) {
return false;
}
boolean blocking = result.getIssues().stream()
.anyMatch(i -> i.getSeverity() == IssueSeverity.ERROR);
if (blocking) {
System.out.println(
"File has errors but AST is available — proceeding with best-effort analysis.");
}
return true;
}
}
Error nodes in the AST
When error reporting is enabled (the default), unparsed fragments may appear as ErrorNode instances inside the tree. Count them with a filtered walk — the same walkDescendants / walkDescendantsBreadthFirst utilities used elsewhere:
- Kotlin
- Java
import com.strumenta.kolasu.model.ErrorNode
import com.strumenta.kolasu.traversing.walkDescendants
fun countErrorNodes(root: com.strumenta.kolasu.model.Node): Int =
root.walkDescendants(ErrorNode::class).count()
import com.strumenta.kolasu.model.ErrorNode;
import com.strumenta.kolasu.model.Node;
import com.strumenta.kolasu.javalib.Traversing;
public class ErrorNodes {
public static int countErrorNodes(Node root) {
int[] count = {0};
Traversing.walkDescendantsBreadthFirst(root, ErrorNode.class, node -> count[0]++);
return count[0];
}
}
Treat ErrorNode counts as a quality signal: a high count often means lineage or inventory output will be incomplete for that region of the file.