diff options
Diffstat (limited to 'src/main/java/com/seaofnodes/simple/Parser.java')
| -rw-r--r-- | src/main/java/com/seaofnodes/simple/Parser.java | 233 |
1 files changed, 198 insertions, 35 deletions
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 3c1eab96..7734f4df 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -53,14 +53,17 @@ public class Parser { /** * List of keywords disallowed as identifiers */ - private final HashSet<String> KEYWORDS = new HashSet<>(){{ + private static final HashSet<String> KEYWORDS = new HashSet<>(){{ add("break"); add("continue"); add("else"); add("false"); add("if"); add("int"); + add("new"); + add("null"); add("return"); + add("struct"); add("true"); add("while"); }}; @@ -75,14 +78,18 @@ public class Parser { ScopeNode _continueScope; ScopeNode _breakScope; + // Mapping from a Struct name to a Struct. + public static Map<String, TypeStruct> OBJS = new HashMap<>(); + public Parser(String source, TypeInteger arg) { PARSER = this; Node.reset(); IterPeeps.reset(); + OBJS.clear(); _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, arg }); + START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); STOP = new StopNode(source); } @@ -106,8 +113,9 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - _scope.define(ScopeNode.CTRL, new ProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, new ProjNode(START, 1, ScopeNode.ARG0).peephole()); + _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); parseBlock(); _scope.pop(); _xScopes.pop(); @@ -144,18 +152,59 @@ public class Parser { * @return a {@link Node} or {@code null} */ private Node parseStatement() { - if (matchx("return") ) return parseReturn(); - else if (matchx("int")) return parseDecl(); - else if (match ("{" )) return require(parseBlock(),"}"); - else if (matchx("if" )) return parseIf(); - else if (matchx("while")) return parseWhile(); - else if (matchx("break")) return parseBreak(); + if( false ) return null; + else if (matchx("return") ) return parseReturn(); + else if (matchx("int") ) return parseDecl(TypeInteger.BOT); + else if (match ("{") ) return require(parseBlock(),"}"); + else if (matchx("if") ) return parseIf(); + else if (matchx("while") ) return parseWhile(); + else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); - else if (matchx(";")) return null; // Empty statement + else if (matchx("struct") ) return parseStruct(); + else if (matchx(";") ) return null; // Empty statement + // declarations of vars with struct type are handled in parseExpressionStatement due + // to ambiguity else return parseExpressionStatement(); } /** + * Parse a struct field. + * <pre> + * int IDENTIFIER ; + * </pre> + */ + private Field parseField(String sname) { + if( matchx("int") ) // Currently only parsing "int" type fields + return require(Field.make(requireId().intern(),TypeInteger.BOT),";"); + throw errorSyntax("A field type is expected, only type 'int' is supported at present"); + } + + /** + * Parse a struct declaration, and return the following statement. + * Only allowed in top level scope. + * Structs cannot be redefined. + * + * @return The statement following the struct + */ + private Node parseStruct() { + if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + String typeName = requireId(); + if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + ArrayList<Field> fields = new ArrayList<>(); + require("{"); + while (!peek('}') && !_lexer.isEOF()) { + Field field = parseField(typeName); + if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); + fields.add(field); + } + require("}"); + // Build and install the TypeStruct + TypeStruct ts = TypeStruct.make(typeName, fields); + OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names + return parseStatement(); + } + + /** * Parses a while statement * * <pre> @@ -247,7 +296,7 @@ public class Parser { cur.pop(); // If this is a continue then first time the target is null // So we just use the pruned current scope as the base for the - // continue + // "continue" if (toScope == null) return cur; // toScope is either the break scope, or a scope that was created here @@ -272,7 +321,7 @@ public class Parser { private Node parseIf() { require("("); // Parse predicate - var pred = require(parseExpression(), ")"); + var pred = require(parseExpression(), ")").keep(); // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes @@ -287,6 +336,7 @@ public class Parser { // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.upcast(ifT,pred,false); // Up-cast predicate parseStatement(); // Parse true-side ScopeNode tScope = _scope; @@ -294,9 +344,11 @@ public class Parser { _scope = fScope; // Restore scope, then parse else block if any ifF.unkeep(); // fScope already owns the false control if (matchx("else")) { + _scope.upcast(ifF,pred,true); // Up-cast predicate parseStatement(); fScope = _scope; } + pred.unkeep(); if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) throw error("Cannot define a new name on one arm of an if"); @@ -320,7 +372,7 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseExpression(), ";"); - Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr).peephole()); + Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr, _scope).peephole()); ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control return ret; } @@ -328,40 +380,86 @@ public class Parser { /** - * Parses an expression statement + * Parses an expression statement or a declaration statement where type is a struct * * <pre> - * name '=' expression ';' + * name; // Error + * type name; // Define name with default initial value + * type name = expr; // Define name with given initial value + * name = expr; // Reassign existing + * expr // Something else * </pre> * @return an expression {@link Node}, never {@code null} */ private Node parseExpressionStatement() { - var name = requireId(); - require("="); - var expr = require(parseExpression(), ";"); - if( _scope.update(name, expr)==null ) - throw error("Undefined name '" + name + "'"); + int old = _lexer._position; + Type t = type(); + String name = requireId(); + Node expr; + if( match(";") ) { // Assign a default value + // No type and no expr is an error + if( t==null ) throw errorSyntax("expression"); + expr = new ConstantNode(t.makeInit()).peephole(); + } else if( match("=") ) { // Assign "= expr;" + expr = require(parseExpression(), ";"); + } else { // Neither, so just a normal expression parse + _lexer._position = old; + return require(parseExpression(),";"); + } + + // Defining a new variable vs updating an old one + if( t != null ) { + if( _scope.define(name,t,expr) == null ) + throw error("Redefining name '" + name + "'"); + } else { + Node n = _scope.lookup(name); + t = _scope.lookupDeclaredType(name); + if( n==null ) + throw error("Undefined name '" + name + "'"); + _scope.update(name,expr); + } + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); return expr; } + // Parse a type-or-null + private Type type() { + int old = _lexer._position; + String tname = _lexer.matchId(); + if( tname==null ) return null; + if( tname.equals("int") ) return TypeInteger.BOT; + TypeStruct obj = OBJS.get(tname); + if( obj != null ) + return TypeMemPtr.make(obj,match("?")); + // Not a type; unwind the parse + _lexer._position = old; + return null; + } + /** * Parses a declStatement * * <pre> - * 'int' name = expression ';' + * type name = expression ';' * </pre> - * @return an expression {@link Node}, never {@code null} + * @return an expression {@link Node} */ - private Node parseDecl() { - // Type is 'int' for now + private Node parseDecl(Type t) { var name = requireId(); - require("="); - var expr = require(parseExpression(), ";"); - if( _scope.define(name,expr) == null ) + var expr = match(";") + // Assign a null value + ? new ConstantNode(t.makeInit()).peephole() + // Assign "= expr;" + : require(require("=").parseExpression(), ";"); + if( !expr._type.isa(t) ) + throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); + if( _scope.define(name,t,expr) == null ) throw error("Redefining name '" + name + "'"); return expr; } + /** * Parse an expression of the form: * @@ -448,20 +546,21 @@ public class Parser { * Parse a unary minus expression. * * <pre> - * unaryExpr : ('-') unaryExpr | primaryExpr + * unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr * </pre> * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { if (match("-")) return new MinusNode(parseUnary()).peephole(); - return parsePrimary(); + if (match("!")) return new NotNode(parseUnary()).peephole(); + return parsePostfix(parsePrimary()); } /** * Parse a primary expression: * * <pre> - * primaryExpr : integerLiteral | Identifier | true | false | '(' expression ')' + * primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' expression ')' * </pre> * @return a primary {@link Node}, never {@code null} */ @@ -470,6 +569,13 @@ public class Parser { if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); + if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); + if( matchx("new") ) { + String structName = requireId(); + TypeStruct obj = OBJS.get(structName); + if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'"); + return newStruct(obj); + } String name = _lexer.matchId(); if( name == null) throw errorSyntax("an identifier or expression"); Node n = _scope.lookup(name); @@ -478,6 +584,54 @@ public class Parser { } /** + * Return a NewNode but also generate instructions to initialize it. + */ + private Node newStruct( TypeStruct obj ) { + Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); + Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); + for( Field field : obj._fields ) { + mem(new StoreNode(field._fname, mem(), n, initValue).peephole()); + } + return n.unkeep(); + } + + // Memory is one hidden SSA variable, including across branches and loops. + private Node mem() { return _scope.lookup("$mem"); } + private Node mem(Node n) { return _scope.update("$mem",n); } + + /** + * Parse postfix expression. For now this is just a field + * expression, but in future could be array index too. + * + * <pre> + * expr ('.' IDENTIFIER)* [ = expr ] + * </pre> + */ + private Node parsePostfix(Node expr) { + if( !match(".") ) return expr; + + if( !(expr._type instanceof TypeMemPtr ptr) ) + throw error("Expected struct reference but got " + expr._type.str()); + + String name = requireId().intern(); + int idx = ptr._obj==null ? -1 : ptr._obj.find(name); + if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + + if( match("=") ) { + // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment + if( peek('=') ) _lexer._position--; + else { + Node val = parseExpression(); + mem(new StoreNode(name, mem(), expr, val).peephole()); + return expr; // "obj.a = expr" returns the expression while updating memory + } + } + + Type declaredType = ptr._obj._fields[idx]._type; + return parsePostfix(new LoadNode(name, declaredType, mem(), expr).peephole()); + } + + /** * Parse integer literal * * <pre> @@ -497,6 +651,7 @@ public class Parser { private boolean matchx(String syntax) { return _lexer.matchx(syntax); } // Return true and do NOT skip if 'ch' is next private boolean peek(char ch) { return _lexer.peek(ch); } + private boolean peekIsId() { return _lexer.peekIsId(); } // Require and return an identifier private String requireId() { @@ -505,9 +660,11 @@ public class Parser { throw error("Expected an identifier, found '"+id+"'"); } + + // Require an exact match - private void require(String syntax) { require(null, syntax); } - private <N extends Node> N require(N n, String syntax) { + private Parser require(String syntax) { require(null, syntax); return this; } + private <N> N require(N n, String syntax) { if (match(syntax)) return n; throw errorSyntax(syntax); } @@ -594,6 +751,8 @@ public class Parser { return true; } + // Match must be exact and not followed by more ID characters. + // Prevents identifier "ifxy" from matching an "if" statement. boolean matchx(String syntax) { if( !match(syntax) ) return false; if( !isIdLetter(peek()) ) return true; @@ -606,10 +765,14 @@ public class Parser { return peek()==ch; } + boolean peekIsId() { + skipWhiteSpace(); + return isIdStart(peek()); + } + // Return an identifier or null String matchId() { - skipWhiteSpace(); - return isIdStart(peek()) ? parseId() : null; + return peekIsId() ? parseId() : null; } // Used for errors |
