From ccd83187b172f8ba3cd7c49b59a080fb027a184b Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 29 Oct 2023 09:32:35 -0700 Subject: Fix bugs using keywords as ids Add an exact 'matchx' for keywords. Add a list of keywords. int true=0; return true; // Expected id, found 'true' int else=arg; if(else) else=2; else else=1; return else; // Expected id, found 'else' inta=1; return a; // undefined inta --- .../main/java/com/seaofnodes/simple/Parser.java | 57 +++++++++++++++------- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 31 +++++++++++- 3 files changed, 71 insertions(+), 19 deletions(-) (limited to 'chapter05') diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java index a23f8d70..7ea74306 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java @@ -48,6 +48,19 @@ public class Parser { */ public ScopeNode _scope; + /** + * List of keywords disallowed as identifiers + */ + private final HashSet KEYWORDS = new HashSet<>(){{ + add("else"); + add("false"); + add("if"); + add("return"); + add("then"); + add("true"); + }}; + + /** * 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 @@ -116,11 +129,11 @@ public class Parser { * */ private Node parseStatement() { - if (match("return") ) return parseReturn(); - else if (match("int")) return parseDecl(); - else if (match("{" )) return parseBlock(); - else if (match("if" )) return parseIf(); - else if (match("#showGraph")) return showGraph(); + if (matchx("return") ) return parseReturn(); + else if (matchx("int")) return parseDecl(); + else if (match ("{" )) return parseBlock(); + else if (matchx("if" )) return parseIf(); + else if (matchx("#showGraph")) return showGraph(); else return parseExpressionStatement(); } @@ -147,7 +160,7 @@ public class Parser { // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF); // Ctrl token is now set to ifFalse projection - if (match("else")) parseStatement(); + if (matchx("else")) parseStatement(); if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) throw error("Cannot define a new name on one arm of an if"); @@ -286,13 +299,13 @@ public class Parser { private Node parsePrimary() { if( _lexer.isNumber() ) return parseIntegerLiteral(); if( match("(") ) return require(parseExpression(), ")"); - if( match("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); - if( match("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); - String name = requireId(); - Node id = _scope.lookup(name); - if( id==null ) - throw error("Undefined name '" + name + "'"); - return id; + if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole(); + if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole(); + String name = _lexer.matchId(); + if( name == null) return null; + Node n = _scope.lookup(name); + if( n!=null ) return n; + throw error("Undefined name '" + name + "'"); } /** @@ -310,13 +323,15 @@ public class Parser { // Utilities for lexical analysis // Return true and skip if "syntax" is next in the stream. - private boolean match(String syntax) { return _lexer.match(syntax); } + private boolean match (String syntax) { return _lexer.match (syntax); } + // Match must not be followed by more id letters + private boolean matchx(String syntax) { return _lexer.matchx(syntax); } // Require and return an identifier private String requireId() { String id = _lexer.matchId(); - if (id != null) return id; - throw error("identifier"); + if (id != null && !KEYWORDS.contains(id) ) return id; + throw error("Expected an identifier, found '"+id+"'"); } // Require an exact match @@ -402,11 +417,19 @@ public class Parser { int len = syntax.length(); if (_position + len > _input.length) return false; for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) return false; + if ((char) _input[_position + i] != syntax.charAt(i)) + return false; _position += len; return true; } + boolean matchx(String syntax) { + if( !match(syntax) ) return false; + if( !isIdLetter(peek()) ) return true; + _position -= syntax.length(); + return false; + } + // Return an identifier or null String matchId() { skipWhiteSpace(); diff --git a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index e23840e0..f75a6788 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -25,7 +25,7 @@ public class TypeInteger extends Type { @Override public String toString() { - return _print(new StringBuilder()).toString(); + return _print(new StringBuilder()).toString(); } @Override diff --git a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 8f09620e..32ac5aac 100644 --- a/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter05/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -95,6 +95,26 @@ return c; StopNode stop = new Parser("return true;").parse(); assertEquals("return 1;",stop.toString()); } + + @Test + public void testChapter5Keyword1() { + try { + new Parser("int true=0; return true;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'true'",e.getMessage()); + } + } + + @Test + public void testChapter5Keyword2() { + try { + new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Expected an identifier, found 'else'",e.getMessage()); + } + } @Test public void testChapter4Peephole() { @@ -196,6 +216,16 @@ return c; assertEquals("return (arg+1);", ret.print()); } + @Test + public void testChapter4Bug3() { + try { + new Parser("inta=1; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'inta'",e.getMessage()); + } + } + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -242,7 +272,6 @@ return c; } } - @Test public void testChapter2ParseGrammar() { Node._disablePeephole = true; // disable peephole so we can observe full graph -- cgit v1.2.3