diff options
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/parser/Parser.java')
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/parser/Parser.java | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/src/main/java/com/seaofnodes/simple/parser/Parser.java b/src/main/java/com/seaofnodes/simple/parser/Parser.java new file mode 100644 index 00000000..913fd2b2 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -0,0 +1,298 @@ +package com.seaofnodes.simple.parser; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.CompUnit; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Stack; + +/** Source-unit entry point and shared cursor, scope, and IR utilities. + * Grammar productions live in the package-local components below. */ +public class Parser { + final Statements statements = new Statements(this); + final ControlFlow controlflow = new ControlFlow(this); + final Expressions expressions = new Expressions(this); + final Declarations declarations = new Declarations(this); + final Types types = new Types(this); + final Functions functions = new Functions(this); + final Structs structs = new Structs(this); + final Allocations allocations = new Allocations(this); + final References references = new References(this); + final Conversions conversions = new Conversions(this); + + // Compile driver + public final CodeGen _code; + + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. + Lexer _lexer; + + // Class-object name, distinct from its instance type. + public static final String CLZ = "class:"; + + // Source file for compilation + public CompUnit _ref; + + // Current classname being parsed + String _nestedType; + /** + * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time + * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how + * we handle branching. See {@link Statements#parseIf()}. + * <p> + * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds + * variable names to Nodes. The top of this stack represents current scope. + * <p> + * We keep a list of all ScopeNodes so that we can show them in graphs. + * @see Statements#parseIf() + * @see #_xScopes + */ + public ScopeNode _scope; + + /** + * List of keywords disallowed as identifiers + */ + public static final HashSet<String> KEYWORDS = new HashSet<>(){{ + add("bool"); + add("break"); + add("byte"); + add("continue"); + add("else"); + add("f32"); + add("f64"); + add("false"); + add("flt"); + add("i16"); + add("i32"); + add("i64"); + add("i8"); + add("if"); + add("int"); + add("new"); + add("null"); + add("return"); + add("struct"); + add("true"); + add("u1"); + add("u16"); + add("u32"); + add("u8"); + add("while"); + }}; + + /** + * We clone ScopeNodes when control flows branch; it is useful to have + * a list of all active ScopeNodes for purposes of visualization of the SoN graph + */ + public final Stack<ScopeNode> _xScopes = new Stack<>(); + // Layouts needed by field accesses in this source file. Resolve after + // parsing, since a later local struct declaration can satisfy the use. + public final LinkedHashSet<String> _requiredTypes = new LinkedHashSet<>(); + + // Mapping from a type name to a Type. The string name matches + // `type.str()` call. + public static final HashMap<String, Type> TYPES = new HashMap<>(); + public static final HashMap<String, Type> INIT_TYPES = + new HashMap<>() {{ + put("bool",TypeInteger.U1 ); + put("byte",TypeInteger.U8 ); + put("f32" ,TypeFloat .F32); + put("f64" ,TypeFloat .F64); + put("flt" ,TypeFloat .F64); + put("i16" ,TypeInteger.I16); + put("i32" ,TypeInteger.I32); + put("i64" ,TypeInteger.BOT); + put("i8" ,TypeInteger.I8 ); + put("int" ,TypeInteger.BOT); + put("u1" ,TypeInteger.U1 ); + put("u16" ,TypeInteger.U16); + put("u32" ,TypeInteger.U32); + put("u8" ,TypeInteger.U8 ); + put("val" ,Type.TOP); // Marker type, indicates type inference + put("var" ,Type.BOTTOM); // Marker type, indicates type inference + }}; + + public static class ParseException extends RuntimeException { + public final Lexer _loc; + ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; } + } + + public Parser(CodeGen code ) { + _code = code; + } + + public Ary<FRefNode> parse( CompUnit ref ) { + assert _scope == null && statements._breakScope == null && statements._continueScope == null && functions._returnScope == null; + assert references._frefs == null; + _lexer = new Lexer(ref); + references._frefs = new Ary<>(FRefNode.class); + _requiredTypes.clear(); + // Starting Scope has control, memory, initial arguments + _scope = new ScopeNode(); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); + _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); + // Track active scopes for Graph display + _xScopes.push(_scope); + ctrl(_code.XCTRL); + mem(con(TypeMem.BOT)); + + // File-level struct declaration + _ref = ref; + _nestedType = ref._cname; + String typeName = addClzPrefix(ref._cname); + + // Parse whole file, which is a struct-level class initializer + structs.parseInitializer( true, typeName ); + + if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); + + Ary<FRefNode> frefs = references.resolve(); + + // The class being published + ref._clz = (TypeStruct)TYPES.get(typeName); + + // Clean up and reset + _xScopes.pop(); + _scope.kill(); + _scope = null; + references._frefs = null; + + return frefs; + } + + @Override + public String toString() { return _lexer==null ? "" : _lexer.toString(); } + + // Debugging utility to find a Node by index + public Node f(int nid) { return _code.f(nid); } + public static String addClzPrefix( String x ) { + assert !x.startsWith(CLZ); + return (CLZ+x).intern(); + } + public static boolean startsClzPrefix( String s ) { + return s.startsWith(CLZ); + } + public static boolean isPublicClassName( String name ) { + if( startsClzPrefix(name) ) name = name.substring(CLZ.length()); + return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1; + } + + // Read and set CTRL + Node ctrl() { return _scope.ctrl(); } + <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } + // Read and set Memory + Node mem() { return _scope.mem(1); } + void mem(Node mem) { _scope.mem(mem); } + // Structural label for precise memory Phis. The '$' prefix cannot be + // referenced in Simple source. + public static String memName(int alias) { return ("$"+alias).intern(); } + + // Ordinary parsing carries one whole-memory value. Alias partitioning is + // graph optimization, not parser state. + void storeMem( Node st, int alias, Node prior ) { + mem(alias==1 ? st : mergeAlias(prior,alias,st)); + } + + // A semantic graph partition representing the complete memory after a + // precise alias update. ScopeNode treats this as one opaque value. + Node mergeAlias(Node bulk, int alias, Node precise) { + assert bulk != null : "Missing bulk memory for alias #"+alias; + MemMergeNode merge = new MemMergeNode(null,bulk); + merge.alias(alias,precise); + return peep(merge); + } + ConstantNode con( long con ) { return _code.con(con); } + ConstantNode con( Type t ) { return _code.con(t ); } + void classInit(Lexer site, Node ptr) { + // Ordinary load machinery resolves the flag alias and offset when + // the receiver is still a forward reference. + Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null; + int alias = flag==null ? 1 : flag._alias; + Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole(); + CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep(); + ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole()); + Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep(); + flag = init.flag(); + mem(flag==null ? bulk : mergeAlias(bulk,flag._alias, + new ProjNode(init,2,memName(flag._alias)).peephole())); + bulk.unkeep(); + init.unkeep(); + } + + // Field offset for a known type (but unknown offset) + public static ConFldOffNode off( TypeStruct ts, String fname ) { + return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole()); + } + // Field offset for an unknown type + public Node fldoff( Node n, String name ) { + if( n._type instanceof TypeMemPtr ptr && ptr._obj._fref ) + _requiredTypes.add(ptr._obj._name); + return new FldOffNode(n,name,loc()).peephole(); + } + public Node peep( Node n ) { + // Peephole, then improve with lexically scoped guards + return _scope.upcastGuard(n.peephole()); + } + + // Parse a string or null + + ////////////////////////////////// + // Utilities for lexical analysis + + // Return true and skip if "syntax" is next in the stream. + boolean match (String syntax) { return _lexer.match (syntax); } + // Match must be "exact", not be followed by more id letters + boolean matchx(String syntax) { return _lexer.matchx(syntax); } + boolean matchOp(char c0 ) { return _lexer.matchOp(c0); } + boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } + // Return true and do NOT skip if 'ch' is next + boolean peek(char ch) { return _lexer.peek(ch); } + + // ch is +/- 1, means oper++ or oper-- means postfix + static boolean postfix(char ch) { + return (byte)ch== 1 || (byte)ch== -1; + } + + public int pos() { return _lexer._position; } + int pos(int pos) { + int old = _lexer._position; + _lexer._position = pos; + return old; + } + Type posT(int pos) { _lexer._position = pos; return null; } + // Source code location + Lexer loc() { return new Lexer(_lexer); } + + // Require and return an identifier + String requireId() { + String id = _lexer.matchId(); + if (id != null && !KEYWORDS.contains(id) ) return id.intern(); + throw error("Expected an identifier, found '"+id+"'"); + } + + String matchId() { + int old = pos(); + String id = _lexer.matchId(); + if( id==null ) return null; + if( !KEYWORDS.contains(id) ) return id; + pos(old); + return null; + } + + // Require an exact match + Parser require(String syntax) { require(null, syntax); return this; } + <N> N require(N n, String syntax) { + if (match(syntax)) return n; + throw errorSyntax(syntax); + } + + ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); } + private ParseException _errorSyntax(String msg) { + return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`"); + } + ParseException error(String msg) { return error(msg,_lexer); } + public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); } +} |
