package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; /** * 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 { /** * 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(); } String src() { return new String( _lexer._input ); } 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 Node parseReturn() {
var expr = require(parseExpression(), ";");
return new ReturnNode(START, expr).peephole();
}
/**
* Parse an expression of the form:
*
*
* expr : additiveExpr
*
* @return an expression {@link Node}, never {@code null}
*/
private Node parseExpression() { return parseAddition(); }
/**
* Parse an additive expression
*
*
* additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
*
* @return an add expression {@link Node}, never {@code null}
*/
private Node parseAddition() {
var lhs = parseMultiplication();
if (match("+")) return new AddNode(lhs, parseAddition()).peephole();
if (match("-")) return new SubNode(lhs, parseAddition()).peephole();
return lhs;
}
/**
* Parse an multiplicativeExpr expression
*
*
* multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
*
* @return a multiply expression {@link Node}, never {@code null}
*/
private Node parseMultiplication() {
var lhs = parseUnary();
if (match("*")) return new MulNode(lhs, parseMultiplication()).peephole();
if (match("/")) return new DivNode(lhs, parseMultiplication()).peephole();
return lhs;
}
/**
* Parse a unary minus expression.
*
*
* unaryExpr : ('-') unaryExpr | primaryExpr
*
* @return a unary expression {@link Node}, never {@code null}
*/
private Node parseUnary() {
if (match("-")) return new MinusNode(parseUnary()).peephole();
return parsePrimary();
}
/**
* Parse a primary expression:
*
*
* primaryExpr : integerLiteral | Identifier | '(' expression ')'
*
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber() ) return parseIntegerLiteral();
if( match("(") ) return require(parseExpression(), ")");
throw errorSyntax("integer literal");
}
/**
* Parse integer literal
*
*
* integerLiteral: [1-9][0-9]* | [0]
*
*/
private ConstantNode parseIntegerLiteral() {
return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole();
}
//////////////////////////////////
// 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;
}
// Very handy in the debugger, shows the unparsed program
@Override
public String toString() {
return new String(_input, _position, _input.length - _position);
}
// 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 Type parseNumber() {
String snum = parseNumberString();
if (snum.length() > 1 && snum.charAt(0) == '0')
throw error("Syntax error: integer values cannot start with '0'");
return TypeInteger.constant(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);
}
}
}