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()}. *

* 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. *

* 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 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 _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 _requiredTypes = new LinkedHashSet<>(); // Mapping from a type name to a Type. The string name matches // `type.str()` call. public static final HashMap TYPES = new HashMap<>(); public static final HashMap 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 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 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 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 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); } }