package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; /** * The Parser converts a Simple source program to the Sea of Nodes intermediate * representation directly in one pass. There is no intermediate Abstract * Syntax Tree structure. *
* This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { public int pos() { return _lexer._position; } /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode * through the entire parser and optimizer. *
* To make the compiler multithreaded, this field will have to move into a TLS. */ public static StartNode START; // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; public Parser(String source) { _lexer = new Lexer(source); Node.reset(); START = new StartNode(); } public ReturnNode parse() { var ret = (ReturnNode) parseStatement(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); return ret; } /** * Parses a statement * *
* returnStatement
*
* @return a {@link Node} or {@code null}
*/
private Node parseStatement() {
if (matchx("return")) return parseReturn();
throw errorSyntax("a statement");
}
/**
* Parses a return statement; "return" already parsed.
*
*
* 'return' expr ;
*
* @return an expression {@link Node}, never {@code null}
*/
private ReturnNode parseReturn() {
var expr = require(parseExpression(), ";");
return new ReturnNode(START, expr);
}
/**
* Parse an expression of the form:
*
*
* expr : primaryExpr
*
* @return an expression {@link Node}, never {@code null}
*/
private Node parseExpression() {
return parsePrimary();
}
/**
* Parse a primary expression:
*
*
* primaryExpr : integerLiteral
*
*/
private Node parsePrimary() {
_lexer.skipWhiteSpace();
if (_lexer.isNumber())
return parseIntegerLiteral();
throw error("Syntax error, expected integer literal");
}
/**
* Parse integer literal
*
*
* integerLiteral: [1-9][0-9]* | [0]
*
*/
private ConstantNode parseIntegerLiteral() {
return new ConstantNode(_lexer.parseNumber());
}
//////////////////////////////////
// Utilities for lexical analysis
// Return true and skip if "syntax" is next in the stream.
private boolean match (String syntax) { return _lexer.match (syntax); }
// Match must be "exact", not be followed by more id letters
private boolean matchx(String syntax) { return _lexer.matchx(syntax); }
// Require an exact match
private void require(String syntax) { require(null, syntax); }
private Node require(Node n, String syntax) {
if (match(syntax)) return n;
throw errorSyntax(syntax);
}
RuntimeException errorSyntax(String syntax) {
return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken());
}
static RuntimeException error(String errorMessage) {
return new RuntimeException(errorMessage);
}
////////////////////////////////////
// Lexer components
private static class Lexer {
// Input buffer; an array of text bytes read from a file or a string
private final byte[] _input;
// Tracks current position in input buffer
private int _position = 0;
/**
* Record the source text for lexing
*/
public Lexer(String source) {
this(source.getBytes());
}
/**
* Direct from disk file source
*/
public Lexer(byte[] buf) {
_input = buf;
}
// True if at EOF
private boolean isEOF() {
return _position >= _input.length;
}
// Peek next character, or report EOF
private char peek() {
return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
: (char) _input[_position];
}
private char nextChar() {
char ch = peek();
_position++;
return ch;
}
// True if a white space
private boolean isWhiteSpace() {
return peek() <= ' '; // Includes all the use space, tab, newline, CR
}
/**
* Return the next non-white-space character
*/
private void skipWhiteSpace() {
while (isWhiteSpace()) _position++;
}
// Return true, if we find "syntax" after skipping white space; also
// then advance the cursor past syntax.
// Return false otherwise, and do not advance the cursor.
boolean match(String syntax) {
skipWhiteSpace();
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;
_position += len;
return true;
}
boolean matchx(String syntax) {
if( !match(syntax) ) return false;
if( !isIdLetter(peek()) ) return true;
_position -= syntax.length();
return false;
}
// Used for errors
String getAnyNextToken() {
if (isEOF()) return "";
if (isIdStart(peek())) return parseId();
if (isNumber(peek())) return parseNumberString();
if (isPunctuation(peek())) return parsePunctuation();
return String.valueOf(peek());
}
boolean isNumber() {return isNumber(peek());}
boolean isNumber(char ch) {return Character.isDigit(ch);}
private long parseNumber() {
String snum = parseNumberString();
if (snum.length() > 1 && snum.charAt(0) == '0')
throw error("Syntax error: integer values cannot start with '0'");
return Long.parseLong(snum);
}
private String parseNumberString() {
int start = _position;
while (isNumber(nextChar())) ;
return new String(_input, start, --_position - start);
}
// First letter of an identifier
private boolean isIdStart(char ch) {
return Character.isAlphabetic(ch) || ch == '_';
}
// All characters of an identifier, e.g. "_x123"
private boolean isIdLetter(char ch) {
return Character.isLetterOrDigit(ch) || ch == '_';
}
private String parseId() {
int start = _position;
while (isIdLetter(nextChar())) ;
return new String(_input, start, --_position - start);
}
private boolean isPunctuation(char ch) {
return "=;[]<>(){}+-/*!".indexOf(ch) != -1;
}
private String parsePunctuation() {
int start = _position;
return new String(_input, start, 1);
}
}
}