Common type and character positions

- Shader source abstractions now inherit from AbstractShaderElement
 - Spans keep track of line and column positions
This commit is contained in:
Jozufozu 2021-07-06 12:38:32 -07:00
parent a5d282a0ef
commit 865926e783
15 changed files with 327 additions and 107 deletions

View file

@ -26,7 +26,7 @@ public class TypeHelper {
return 1;
}
public static int getAttributeCount(String type) {
public static int getAttributeCount(CharSequence type) {
Matcher mat = matType.matcher(type);
if (mat.find()) {
return Integer.parseInt(mat.group(1));

View file

@ -8,6 +8,7 @@ import java.util.Stack;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.pipeline.parse.Include;
import net.minecraft.util.ResourceLocation;
@ -28,12 +29,12 @@ public class Includer {
}
private static void process(ShaderSources sources, Set<ResourceLocation> seen, List<SourceFile> out, SourceFile source) {
ImmutableList<ResourceLocation> includes = source.getIncludes();
ImmutableList<Include> includes = source.getIncludes();
for (ResourceLocation include : includes) {
for (Include include : includes) {
if (seen.add(include)) {
SourceFile file = sources.source(include);
if (seen.add(include.getFile())) {
SourceFile file = sources.source(include.getFile());
process(sources, seen, out, file);

View file

@ -1,40 +1,47 @@
package com.jozufozu.flywheel.backend.pipeline;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.pipeline.parse.Include;
import com.jozufozu.flywheel.backend.pipeline.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.pipeline.span.CharPos;
import com.jozufozu.flywheel.backend.pipeline.span.ErrorSpan;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
import com.jozufozu.flywheel.backend.pipeline.span.StringSpan;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.util.ResourceLocation;
public class SourceFile {
// #use "valid_namespace:valid/path_to_file.glsl"
private static final Pattern includePattern = Pattern.compile("#use \"(\\w+:[\\w./]+)\"");
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
// https://regexr.com/60n3d
public static final Pattern functionDeclaration = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public final ResourceLocation name;
private final String source;
private final ShaderSources parent;
// function name -> function object
private final ShaderSources parent;
private final String source;
private final IntList lineStarts;
// Function name -> Function object
private final ImmutableMap<String, ShaderFunction> functions;
private final ImmutableList<ResourceLocation> includes;
// Includes ordered as defined in the source
private final ImmutableList<Include> includes;
// Sections of the source that must be trimmed for compilation.
private final List<Span> elisions = new ArrayList<>();
@ -44,8 +51,10 @@ public class SourceFile {
this.name = name;
this.source = source;
functions = parseFunctions();
includes = parseIncludes();
this.lineStarts = getLinePositions();
this.functions = parseFunctions();
this.includes = parseIncludes();
}
public String getSource() {
@ -60,28 +69,81 @@ public class SourceFile {
return functions;
}
public ImmutableList<ResourceLocation> getIncludes() {
public ImmutableList<Include> getIncludes() {
return includes;
}
private ImmutableList<ResourceLocation> parseIncludes() {
Matcher uses = includePattern.matcher(source);
public CharPos getCharPos(int charPos) {
int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) {
int ls = lineStarts.getInt(lineNo);
List<ResourceLocation> includes = new ArrayList<>();
while (uses.find()) {
Span use = Span.fromMatcher(this, uses);
elisions.add(use); // we have to trim that later
ResourceLocation loc = new ResourceLocation(uses.group(1)); // TODO: error gracefully
includes.add(loc);
if (charPos < ls) {
break;
}
}
return ImmutableList.copyOf(includes);
int lineStart = lineStarts.getInt(lineNo - 1);
return new CharPos(charPos, lineNo, charPos - lineStart);
}
public String printSource() {
StringBuilder builder = new StringBuilder();
builder.append("Source for shader '")
.append(name)
.append("':\n");
int i = 1;
for (String s : source.split("\n")) {
builder.append(String.format("%1$4s: ", i++))
.append(s)
.append('\n');
}
return builder.toString();
}
private CharSequence elided = null;
public CharSequence getElidedSource() {
if (elided == null) {
StringBuilder out = new StringBuilder();
int lastEnd = 0;
for (Span elision : elisions) {
out.append(source, lastEnd, elision.getStart());
lastEnd = elision.getEnd();
}
out.append(source, lastEnd, source.length());
elided = out.toString();
}
return elided;
}
/**
* Scan the source for line breaks, recording the position of the first character of each line.
*/
private IntList getLinePositions() {
IntList l = new IntArrayList();
l.add(0); // first line is always at position 0
Matcher matcher = newLine.matcher(source);
while (matcher.find()) {
l.add(matcher.end());
}
return l;
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
private ImmutableMap<String, ShaderFunction> parseFunctions() {
Matcher matcher = functionDeclaration.matcher(source);
@ -113,59 +175,43 @@ public class SourceFile {
return ImmutableMap.copyOf(functions);
}
private int findEndOfBlock(int end) {
char[] rest = source.substring(end)
.toCharArray();
/**
* Scan the source for <code>#use "..."</code> directives.
* Records the contents of the directive into an {@link Include} object, and marks the directive for elision.
*/
private ImmutableList<Include> parseIncludes() {
Matcher uses = includePattern.matcher(source);
List<Include> includes = new ArrayList<>();
while (uses.find()) {
Span use = Span.fromMatcher(this, uses);
Span file = Span.fromMatcher(this, uses, 1);
includes.add(new Include(parent, use, file));
elisions.add(use); // we have to trim that later
}
return ImmutableList.copyOf(includes);
}
/**
* Given the position of an opening brace, scans through the source for a paired closing brace.
*/
private int findEndOfBlock(int start) {
int blockDepth = 0;
for (int i = 0; i < rest.length; i++) {
char ch = rest[i];
for (int i = start + 1; i < source.length(); i++) {
char ch = source.charAt(i);
if (ch == '{') blockDepth++;
if (ch == '}') blockDepth--;
else if (ch == '}') blockDepth--;
if (blockDepth < 0) {
return end + i;
return i;
}
}
return -1;
}
public String printSource() {
StringBuilder builder = new StringBuilder();
builder.append("Source for shader '")
.append(name)
.append("':\n");
int i = 1;
for (String s : source.split("\n")) {
builder.append(String.format("%1$4s: ", i++))
.append(s)
.append('\n');
}
return builder.toString();
}
private CharSequence elided = null;
public CharSequence getElidedSource() {
if (elided == null) {
StringBuilder out = new StringBuilder();
int lastEnd = 0;
for (Span elision : elisions) {
out.append(source, lastEnd, elision.getStart());
lastEnd = elision.getEnd();
}
out.append(source, lastEnd, source.length());
elided = out.toString();
}
return elided;
}
}

View file

@ -0,0 +1,14 @@
package com.jozufozu.flywheel.backend.pipeline.parse;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public abstract class AbstractShaderElement {
public final Span self;
public AbstractShaderElement(Span self) {
this.self = self;
}
public abstract void checkErrors(ErrorReporter e);
}

View file

@ -0,0 +1,14 @@
package com.jozufozu.flywheel.backend.pipeline.parse;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ErrorReporter {
public String generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
return "";
}
}

View file

@ -0,0 +1,49 @@
package com.jozufozu.flywheel.backend.pipeline.parse;
import java.util.Optional;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
import net.minecraft.util.ResourceLocation;
public class Include extends AbstractShaderElement {
private final ShaderSources sources;
private Span file;
private SourceFile resolution;
public Include(ShaderSources sources, Span self, Span file) {
super(self);
this.sources = sources;
this.file = file;
}
public boolean isResolved() {
return resolution != null;
}
public Optional<SourceFile> getTarget() {
return Optional.ofNullable(resolution);
}
public ResourceLocation getFile() {
return new ResourceLocation(file.get());
}
@Override
public void checkErrors(ErrorReporter e) {
String name = file.get();
try {
ResourceLocation loc = new ResourceLocation(name);
resolution = sources.source(loc);
} catch (RuntimeException error) {
}
}
}

View file

@ -8,7 +8,7 @@ import java.util.stream.Collectors;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ShaderFunction {
public class ShaderFunction extends AbstractShaderElement {
public static final Pattern argument = Pattern.compile("(\\w+)\\s+(\\w+)");
public static final Pattern assignment = Pattern.compile("(\\w+)\\s*=");
@ -17,16 +17,15 @@ public class ShaderFunction {
private final Span name;
private final Span args;
private final Span body;
private final Span self;
private final List<Variable> parameters;
public ShaderFunction(Span self, Span type, Span name, Span args, Span body) {
super(self);
this.type = type;
this.name = name;
this.args = args;
this.body = body;
this.self = self;
this.parameters = new ArrayList<>();
@ -59,6 +58,11 @@ public class ShaderFunction {
.map(Span::get)
.collect(Collectors.joining(","));
return name + "(" + p + ")";
return type + " " + name + "(" + p + ")";
}
@Override
public void checkErrors(ErrorReporter e) {
}
}

View file

@ -7,36 +7,56 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.backend.loading.TypeHelper;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class ShaderStruct {
public class ShaderStruct extends AbstractShaderElement {
// https://regexr.com/5t207
public static final Pattern struct = Pattern.compile("struct\\s+([\\w\\d]*)\\s*\\{([\\w\\d \\t#\\[\\](),;\\n]*)}\\s*;");
public final Span self;
public final Span name;
public final Span body;
List<StructField> fields = new ArrayList<>(4);
Map<String, String> fields2Types = new HashMap<>();
private final ImmutableList<StructField> fields;
private final ImmutableMap<String, Span> fields2Types;
public ShaderStruct(Span self, Span name, Span body) {
this.self = self;
super(self);
this.name = name;
this.body = body;
parseFields();
this.fields = parseFields();
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
for (StructField field : fields) {
lookup.put(field.name.get(), field.type);
}
this.fields2Types = lookup.build();
}
private void parseFields() {
Matcher fielder = StructField.fieldPattern.matcher(body.get());
@Override
public void checkErrors(ErrorReporter e) {
while (fielder.find()) {
fields.add(new StructField(fielder));
fields2Types.put(fielder.group(2), fielder.group(1));
}
private ImmutableList<StructField> parseFields() {
Matcher matcher = StructField.fieldPattern.matcher(body);
ImmutableList.Builder<StructField> fields = ImmutableList.builder();
while (matcher.find()) {
Span field = Span.fromMatcher(body, matcher);
Span type = Span.fromMatcher(body, matcher, 1);
Span name = Span.fromMatcher(body, matcher, 2);
fields.add(new StructField(field, type, name));
}
return fields.build();
}
public void addPrefixedAttributes(Program builder, String prefix) {

View file

@ -4,24 +4,25 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jozufozu.flywheel.backend.loading.LayoutTag;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class StructField {
public class StructField extends AbstractShaderElement {
public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);");
public String name;
public String type;
public LayoutTag layout;
public Span name;
public Span type;
public StructField(Matcher fieldMatcher) {
type = fieldMatcher.group(2);
name = fieldMatcher.group(3);
public StructField(Span self, Span name, Span type) {
super(self);
this.name = name;
this.type = type;
}
public String getName() {
public Span getName() {
return name;
}
public String getType() {
public Span getType() {
return type;
}
@ -29,4 +30,9 @@ public class StructField {
public String toString() {
return "TaggedField{" + "name='" + name + '\'' + ", type='" + type + '\'' + '}';
}
@Override
public void checkErrors(ErrorReporter e) {
}
}

View file

@ -2,14 +2,13 @@ package com.jozufozu.flywheel.backend.pipeline.parse;
import com.jozufozu.flywheel.backend.pipeline.span.Span;
public class Variable {
public class Variable extends AbstractShaderElement {
private final Span self;
private final Span type;
private final Span name;
public Variable(Span self, Span type, Span name) {
this.self = self;
super(self);
this.type = type;
this.name = name;
}
@ -21,4 +20,9 @@ public class Variable {
public Span getName() {
return name;
}
@Override
public void checkErrors(ErrorReporter e) {
}
}

View file

@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.pipeline.parse;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.backend.pipeline.span;
import com.jozufozu.flywheel.backend.pipeline.SourceFile;
public class CharPos {
private final int idx;
private final int line;
private final int col;
public CharPos(int idx, int line, int col) {
this.idx = idx;
this.line = line;
this.col = col;
}
public int getPos() {
return idx;
}
public int getLine() {
return line;
}
public int getCol() {
return col;
}
}

View file

@ -11,6 +11,10 @@ public class ErrorSpan extends Span {
super(in, start, end);
}
public ErrorSpan(SourceFile in, CharPos start, CharPos end) {
super(in, start, end);
}
@Override
public Span subSpan(int from, int to) {
return new ErrorSpan(in, start, end); // the sub-span of an error is an error in the same location

View file

@ -7,13 +7,17 @@ import com.jozufozu.flywheel.backend.pipeline.SourceFile;
/**
* A span of code in a {@link SourceFile}.
*/
public abstract class Span {
public abstract class Span implements CharSequence {
protected final SourceFile in;
protected final int start;
protected final int end;
protected final CharPos start;
protected final CharPos end;
public Span(SourceFile in, int start, int end) {
this(in, in.getCharPos(start), in.getCharPos(end));
}
public Span(SourceFile in, CharPos start, CharPos end) {
this.in = in;
this.start = start;
this.end = end;
@ -24,11 +28,11 @@ public abstract class Span {
}
public int getStart() {
return start;
return start.getPos();
}
public int getEnd() {
return end;
return end.getPos();
}
public boolean isEmpty() {
@ -41,6 +45,21 @@ public abstract class Span {
public abstract boolean isErr();
@Override
public int length() {
return end.getPos() - start.getPos();
}
@Override
public char charAt(int index) {
return in.getSource().charAt(start.getPos() + index);
}
@Override
public CharSequence subSequence(int start, int end) {
return subSpan(start, end);
}
@Override
public String toString() {
return get();

View file

@ -8,15 +8,19 @@ public class StringSpan extends Span {
super(in, start, end);
}
public StringSpan(SourceFile in, CharPos start, CharPos end) {
super(in, start, end);
}
@Override
public Span subSpan(int from, int to) {
return new StringSpan(in, start + from, start + to);
return new StringSpan(in, start.getPos() + from, start.getPos() + to);
}
@Override
public String get() {
return in.getSource()
.substring(start, end);
.substring(start.getPos(), end.getPos());
}
@Override