Permalink
Switch branches/tags
Nothing to show
Find file Copy path
96e6733 Mar 19, 2017
1 contributor

Users who have contributed to this file

1066 lines (935 sloc) 34 KB
namespace GLSLX.Parser {
var pratt Pratt = null
def typeParselet(type Type) fn(ParserContext, Token) Node {
return (context, token) => Node.createType(type).withRange(token.range)
}
def unaryPrefix(kind NodeKind) fn(ParserContext, Token, Node) Node {
assert(kind.isUnaryPrefix)
return (context, token, value) => Node.createUnary(kind, value).withRange(Range.span(token.range, value.range)).withInternalRange(token.range)
}
def unaryPostfix(kind NodeKind) fn(ParserContext, Node, Token) Node {
assert(kind.isUnaryPostfix)
return (context, value, token) => Node.createUnary(kind, value).withRange(Range.span(value.range, token.range)).withInternalRange(token.range)
}
def binaryParselet(kind NodeKind) fn(ParserContext, Node, Token, Node) Node {
assert(kind.isBinary)
return (context, left, token, right) => Node.createBinary(kind, left, right).withRange(Range.span(left.range, right.range)).withInternalRange(token.range)
}
def parseInt(text string) int {
if text.count > 1 && text[0] == '0' && (text[1] != 'x' && text[1] != 'X') {
return dynamic.parseInt(text, 8)
}
return (text as dynamic) | 0
}
def parseFloat(text string) double {
return +(text as dynamic)
}
def createExpressionParser Pratt {
var pratt = Pratt.new
var invalidUnaryOperator = (context ParserContext, token Token, value Node) Node => {
context.log.syntaxErrorInvalidOperator(token.range)
return Node.createUnknownConstant(.ERROR).withRange(Range.span(token.range, value.range))
}
var invalidBinaryOperator = (context ParserContext, left Node, token Token, right Node) Node => {
context.log.syntaxErrorInvalidOperator(token.range)
return Node.createUnknownConstant(.ERROR).withRange(Range.span(left.range, right.range))
}
pratt.literal(.TRUE, (context, token) => Node.createBool(true).withRange(token.range))
pratt.literal(.FALSE, (context, token) => Node.createBool(false).withRange(token.range))
pratt.literal(.INT_LITERAL, (context, token) => Node.createInt(parseInt(token.range.toString)).withRange(token.range))
pratt.literal(.FLOAT_LITERAL, (context, token) => Node.createFloat(parseFloat(token.range.toString)).withRange(token.range))
pratt.literal(.BOOL, typeParselet(.BOOL))
pratt.literal(.BVEC2, typeParselet(.BVEC2))
pratt.literal(.BVEC3, typeParselet(.BVEC3))
pratt.literal(.BVEC4, typeParselet(.BVEC4))
pratt.literal(.FLOAT, typeParselet(.FLOAT))
pratt.literal(.INT, typeParselet(.INT))
pratt.literal(.IVEC2, typeParselet(.IVEC2))
pratt.literal(.IVEC3, typeParselet(.IVEC3))
pratt.literal(.IVEC4, typeParselet(.IVEC4))
pratt.literal(.MAT2, typeParselet(.MAT2))
pratt.literal(.MAT3, typeParselet(.MAT3))
pratt.literal(.MAT4, typeParselet(.MAT4))
pratt.literal(.VEC2, typeParselet(.VEC2))
pratt.literal(.VEC3, typeParselet(.VEC3))
pratt.literal(.VEC4, typeParselet(.VEC4))
pratt.literal(.VOID, typeParselet(.VOID))
pratt.prefix(.COMPLEMENT, .UNARY_PREFIX, invalidUnaryOperator)
pratt.prefix(.DECREMENT, .UNARY_PREFIX, unaryPrefix(.PREFIX_DECREMENT))
pratt.prefix(.INCREMENT, .UNARY_PREFIX, unaryPrefix(.PREFIX_INCREMENT))
pratt.prefix(.MINUS, .UNARY_PREFIX, unaryPrefix(.NEGATIVE))
pratt.prefix(.NOT, .UNARY_PREFIX, unaryPrefix(.NOT))
pratt.prefix(.PLUS, .UNARY_PREFIX, unaryPrefix(.POSITIVE))
pratt.postfix(.DECREMENT, .UNARY_POSTFIX, unaryPostfix(.POSTFIX_DECREMENT))
pratt.postfix(.INCREMENT, .UNARY_POSTFIX, unaryPostfix(.POSTFIX_INCREMENT))
pratt.infix(.DIVIDE, .MULTIPLY, binaryParselet(.DIVIDE))
pratt.infix(.EQUAL, .COMPARE, binaryParselet(.EQUAL))
pratt.infix(.GREATER_THAN, .COMPARE, binaryParselet(.GREATER_THAN))
pratt.infix(.GREATER_THAN_OR_EQUAL, .COMPARE, binaryParselet(.GREATER_THAN_OR_EQUAL))
pratt.infix(.LESS_THAN, .COMPARE, binaryParselet(.LESS_THAN))
pratt.infix(.LESS_THAN_OR_EQUAL, .COMPARE, binaryParselet(.LESS_THAN_OR_EQUAL))
pratt.infix(.MINUS, .ADD, binaryParselet(.SUBTRACT))
pratt.infix(.MULTIPLY, .MULTIPLY, binaryParselet(.MULTIPLY))
pratt.infix(.NOT_EQUAL, .COMPARE, binaryParselet(.NOT_EQUAL))
pratt.infix(.PLUS, .ADD, binaryParselet(.ADD))
pratt.infix(.REMAINDER, .MULTIPLY, invalidBinaryOperator)
pratt.infix(.SHIFT_LEFT, .SHIFT, invalidBinaryOperator)
pratt.infix(.SHIFT_RIGHT, .SHIFT, invalidBinaryOperator)
pratt.infix(.LOGICAL_OR, .LOGICAL_OR, binaryParselet(.LOGICAL_OR))
pratt.infix(.LOGICAL_XOR, .LOGICAL_XOR, binaryParselet(.LOGICAL_XOR))
pratt.infix(.LOGICAL_AND, .LOGICAL_AND, binaryParselet(.LOGICAL_AND))
pratt.infix(.BITWISE_AND, .BITWISE_AND, invalidBinaryOperator)
pratt.infix(.BITWISE_OR, .BITWISE_OR, invalidBinaryOperator)
pratt.infix(.BITWISE_XOR, .BITWISE_XOR, invalidBinaryOperator)
pratt.infixRight(.ASSIGN, .ASSIGN, binaryParselet(.ASSIGN))
pratt.infixRight(.ASSIGN_ADD, .ASSIGN, binaryParselet(.ASSIGN_ADD))
pratt.infixRight(.ASSIGN_BITWISE_AND, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_BITWISE_OR, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_BITWISE_XOR, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_DIVIDE, .ASSIGN, binaryParselet(.ASSIGN_DIVIDE))
pratt.infixRight(.ASSIGN_MULTIPLY, .ASSIGN, binaryParselet(.ASSIGN_MULTIPLY))
pratt.infixRight(.ASSIGN_REMAINDER, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_SHIFT_LEFT, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_SHIFT_RIGHT, .ASSIGN, invalidBinaryOperator)
pratt.infixRight(.ASSIGN_SUBTRACT, .ASSIGN, binaryParselet(.ASSIGN_SUBTRACT))
# Name
pratt.literal(.IDENTIFIER, (context, token) => {
var name = token.range.toString
var symbol = context.scope.find(name)
if symbol == null {
context.log.syntaxErrorBadSymbolReference(token.range)
return Node.createParseError.withRange(token.range)
}
# Check extension usage
if symbol.requiredExtension != null && context.compilationData.extensionBehavior(symbol.requiredExtension) == .DISABLE {
context.log.syntaxErrorDisabledExtension(token.range, name, symbol.requiredExtension)
}
return (symbol.isStruct ? Node.createType(symbol.resolvedType) : Node.createName(symbol)).withRange(token.range)
})
# Sequence
pratt.infix(.COMMA, .COMMA, (context, left, token, right) => {
if left.kind != .SEQUENCE {
left = Node.createSequence.appendChild(left).withRange(left.range)
}
left.appendChild(right)
return left.withRange(context.spanSince(left.range))
})
# Dot
pratt.parselet(.DOT, .MEMBER).infix = (context, left) => {
context.next
var name = context.current.range
if !context.expect(.IDENTIFIER) {
return null
}
return Node.createDot(left, name.toString).withRange(context.spanSince(left.range)).withInternalRange(name)
}
# Group
pratt.parselet(.LEFT_PARENTHESIS, .LOWEST).prefix = (context) => {
var token = context.next
var value = pratt.parse(context, .LOWEST)
if value == null || !context.expect(.RIGHT_PARENTHESIS) {
return null
}
return value.withRange(context.spanSince(token.range))
}
# Call
pratt.parselet(.LEFT_PARENTHESIS, .UNARY_POSTFIX).infix = (context, left) => {
var token = context.next
var node = Node.createCall(left)
if !parseCommaSeparatedList(context, node, .RIGHT_PARENTHESIS) {
return null
}
return node.withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range))
}
# Index
pratt.parselet(.LEFT_BRACKET, .MEMBER).infix = (context, left) => {
var token = context.next
# The "[]" syntax isn't valid but skip over it and recover
if context.peek(.RIGHT_BRACKET) {
context.unexpectedToken
context.next
return Node.createParseError.withRange(context.spanSince(token.range))
}
var value = pratt.parse(context, .LOWEST)
if value == null || !context.expect(.RIGHT_BRACKET) {
return null
}
return Node.createBinary(.INDEX, left, value).withRange(context.spanSince(left.range)).withInternalRange(context.spanSince(token.range))
}
# Hook
pratt.parselet(.QUESTION, .ASSIGN).infix = (context, left) => {
context.next
var middle = pratt.parse(context, .COMMA)
if middle == null || !context.expect(.COLON) {
return null
}
var right = pratt.parse(context, .COMMA)
if right == null {
return null
}
return Node.createHook(left, middle, right).withRange(context.spanSince(left.range))
}
return pratt
}
def parseCommaSeparatedList(context ParserContext, parent Node, stop TokenKind) bool {
var isFirst = true
while !context.eat(stop) {
if !isFirst && !context.expect(.COMMA) {
return false
}
var value = pratt.parse(context, .COMMA)
if value == null {
return false
}
parent.appendChild(value)
isFirst = false
}
return true
}
def parseDoWhile(context ParserContext) Node {
var token = context.next
context.pushScope(Scope.new(.LOOP, context.scope))
var body = parseStatement(context, .LOCAL)
if body == null || !context.expect(.WHILE) || !context.expect(.LEFT_PARENTHESIS) {
return null
}
var test = pratt.parse(context, .LOWEST)
if test == null {
return null
}
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
context.popScope
return checkForSemicolon(context, token.range, Node.createDoWhile(body, test))
}
def parseExportOrImport(context ParserContext) Node {
var token = context.next
var old = context.flags
context.flags |= token.kind == .EXPORT ? .EXPORTED : .IMPORTED
# Parse a modifier block
if context.eat(.LEFT_BRACE) {
var node = Node.createModifierBlock
if !parseStatements(context, node, .GLOBAL) || !context.expect(.RIGHT_BRACE) {
return null
}
context.flags = old
return node.withRange(context.spanSince(token.range))
}
# Just parse a single statement
var statement = parseStatement(context, .GLOBAL)
if statement == null {
return null
}
context.flags = old
return statement
}
const _extensionBehaviors StringMap<ExtensionBehavior> = {
"disable": .DISABLE,
"enable": .ENABLE,
"require": .REQUIRE,
"warn": .WARN,
}
# From https://www.khronos.org/registry/webgl/extensions/
const _knownWebGLExtensions StringMap<int> = {
"GL_OES_standard_derivatives": 0,
"GL_EXT_frag_depth": 0,
"GL_EXT_draw_buffers": 0,
"GL_EXT_shader_texture_lod": 0,
}
def parseExtension(context ParserContext) Node {
var token = context.next
var range = context.current.range
if !context.expect(.IDENTIFIER) {
return null
}
var name = range.toString
# Parse an extension block (a non-standard addition)
if context.eat(.LEFT_BRACE) {
if !(name in context.compilationData.currentExtensions) {
context.compilationData.currentExtensions[name] = .DEFAULT # Silence warnings about this name
}
var block = Node.createModifierBlock
if !parseStatements(context, block, .GLOBAL) || !context.expect(.RIGHT_BRACE) {
return null
}
for child = block.firstChild; child != null; child = child.nextSibling {
if child.kind == .VARIABLES {
for variable = child.variablesType.nextSibling; variable != null; variable = variable.nextSibling {
variable.symbol.requiredExtension = name
}
} else if child.symbol != null {
child.symbol.requiredExtension = name
}
}
return block.withRange(context.spanSince(token.range))
}
# Warn about typos
if !(name in _knownWebGLExtensions) && !(name in context.compilationData.currentExtensions) {
context.log.syntaxWarningUnknownExtension(range, name)
}
# Parse a regular extension pragma
if !context.expect(.COLON) {
return null
}
var text = context.current.range.toString
if !(text in _extensionBehaviors) {
context.unexpectedToken
return null
}
context.next
# Activate or deactivate the extension
var behavior = _extensionBehaviors[text]
context.compilationData.currentExtensions[name] = behavior
return Node.createExtension(name, behavior).withRange(context.spanSince(token.range)).withInternalRange(range)
}
def parseFor(context ParserContext) Node {
var token = context.next
context.pushScope(Scope.new(.LOOP, context.scope))
if !context.expect(.LEFT_PARENTHESIS) {
return null
}
# Setup
var setup Node = null
if !context.eat(.SEMICOLON) {
# Check for a type
var flags = parseFlags(context, .LOCAL)
var type Node = null
if flags != 0 {
type = parseType(context, .REPORT_ERRORS)
if type == null {
return null
}
} else {
type = parseType(context, .IGNORE_ERRORS)
}
# Try to parse a variable
if type != null {
setup = parseAfterType(context, token.range, flags, type, .AVOID_FUNCTIONS)
if setup == null {
return null
}
} else {
setup = pratt.parse(context, .LOWEST)
if setup == null {
return null
}
if !context.expect(.SEMICOLON) {
return null
}
}
}
# Test
var test Node = null
if !context.eat(.SEMICOLON) {
test = pratt.parse(context, .LOWEST)
if test == null {
return null
}
if !context.expect(.SEMICOLON) {
return null
}
}
# Update
var update Node = null
if !context.eat(.RIGHT_PARENTHESIS) {
update = pratt.parse(context, .LOWEST)
if update == null {
return null
}
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
}
# Body
var body = parseStatement(context, .LOCAL)
if body == null {
return null
}
context.popScope
return Node.createFor(setup, test, update, body).withRange(context.spanSince(token.range))
}
def parseIf(context ParserContext) Node {
var token = context.next
if !context.expect(.LEFT_PARENTHESIS) {
return null
}
var test = pratt.parse(context, .LOWEST)
if test == null {
return null
}
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
var yes = parseStatement(context, .LOCAL)
if yes == null {
return null
}
var no Node = null
if context.eat(.ELSE) {
no = parseStatement(context, .LOCAL)
if no == null {
return null
}
}
return Node.createIf(test, yes, no).withRange(context.spanSince(token.range))
}
def parseVersion(context ParserContext) Node {
var token = context.next
var range = context.current.range
if !context.expect(.INT_LITERAL) {
return null
}
return Node.createVersion((range.toString as dynamic) | 0).withRange(context.spanSince(token.range))
}
def parseWhile(context ParserContext) Node {
var token = context.next
context.pushScope(Scope.new(.LOOP, context.scope))
if !context.expect(.LEFT_PARENTHESIS) {
return null
}
var test = pratt.parse(context, .LOWEST)
if test == null {
return null
}
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
var body = parseStatement(context, .LOCAL)
if body == null {
return null
}
context.popScope
return Node.createWhile(test, body).withRange(context.spanSince(token.range))
}
def parseReturn(context ParserContext) Node {
var token = context.next
var value Node = null
if !context.eat(.SEMICOLON) {
value = pratt.parse(context, .LOWEST)
if value == null {
return null
}
context.expect(.SEMICOLON)
}
return Node.createReturn(value).withRange(context.spanSince(token.range))
}
def parsePrecision(context ParserContext) Node {
var token = context.next
var flag SymbolFlags = 0
switch context.current.kind {
case .LOWP { flag = .LOWP }
case .MEDIUMP { flag = .MEDIUMP }
case .HIGHP { flag = .HIGHP }
default {
context.unexpectedToken
return null
}
}
context.next
var type = parseType(context, .REPORT_ERRORS)
if type == null {
return null
}
return checkForSemicolon(context, token.range, Node.createPrecision(flag, type))
}
def parseStruct(context ParserContext, flags int) Node {
var name = context.current.range
if !context.expect(.IDENTIFIER) {
return null
}
var symbol = StructSymbol.new(context.compilationData.nextSymbolID, name, name.toString, Scope.new(.STRUCT, context.scope))
symbol.flags |= context.flags | flags
if !tryToDefineUniquelyInScope(context, symbol) {
return null
}
var range = context.current.range
var block = Node.createStructBlock
var variables Node = null
if !context.expect(.LEFT_BRACE) {
return null
}
context.pushScope(symbol.scope)
while !context.peek(.RIGHT_BRACE) && !context.peek(.END_OF_FILE) {
var statement = parseStatement(context, .STRUCT)
if statement == null {
return null
}
if statement.kind != .VARIABLES {
context.log.syntaxErrorInsideStruct(statement.range)
continue
}
block.appendChild(statement)
for child = statement.variablesType.nextSibling; child != null; child = child.nextSibling {
var variable = child.symbol.asVariable
symbol.variables.append(variable)
if variable.value != null {
context.log.syntaxErrorStructVariableInitializer(variable.value.range)
}
}
}
context.popScope
if !context.expect(.RIGHT_BRACE) {
return null
}
block.withRange(context.spanSince(range))
# Parse weird struct-variable hybrid things
#
# struct S { int x; } y, z[2];
#
if context.peek(.IDENTIFIER) {
variables = parseVariables(0, Node.createType(symbol.resolvedType), context.next.range, context)
if variables == null {
return null
}
}
else {
context.expect(.SEMICOLON)
}
return Node.createStruct(symbol, block, variables)
}
def checkForLoopAndSemicolon(context ParserContext, range Range, node Node) Node {
var found = false
for scope = context.scope; scope != null; scope = scope.parent {
if scope.kind == .LOOP {
found = true
break
}
}
if !found {
context.log.syntaxErrorOutsideLoop(range)
}
return checkForSemicolon(context, range, node)
}
def checkForSemicolon(context ParserContext, range Range, node Node) Node {
context.expect(.SEMICOLON)
return node.withRange(context.spanSince(range))
}
enum Allow {
AVOID_FUNCTIONS
ALLOW_FUNCTIONS
}
def parseAfterType(context ParserContext, range Range, flags SymbolFlags, type Node, allow Allow) Node {
var name = context.current.range
if flags == 0 && !context.peek(.IDENTIFIER) {
var value = pratt.resume(context, .LOWEST, type)
if value == null {
return null
}
return checkForSemicolon(context, range, Node.createExpression(value))
}
if !context.expect(.IDENTIFIER) {
return null
}
if context.eat(.LEFT_PARENTHESIS) {
return parseFunction(flags, type, name, context)
}
var variables = parseVariables(flags, type, name, context)
if variables == null {
return null
}
return variables.withRange(context.spanSince(range))
}
def parseStatement(context ParserContext, mode VariableKind) Node {
var token = context.current
switch token.kind {
case .BREAK { return checkForLoopAndSemicolon(context, context.next.range, Node.createBreak) }
case .CONTINUE { return checkForLoopAndSemicolon(context, context.next.range, Node.createContinue) }
case .DISCARD { return checkForSemicolon(context, context.next.range, Node.createDiscard) }
case .DO { return parseDoWhile(context) }
case .EXPORT, .IMPORT { return parseExportOrImport(context) }
case .EXTENSION { return parseExtension(context) }
case .FOR { return parseFor(context) }
case .IF { return parseIf(context) }
case .LEFT_BRACE { return parseBlock(context) }
case .PRECISION { return parsePrecision(context) }
case .RETURN { return parseReturn(context) }
case .SEMICOLON { return Node.createBlock.withRange(context.next.range) }
case .VERSION { return parseVersion(context) }
case .WHILE { return parseWhile(context) }
}
# Try to parse a variable or function
var flags = parseFlags(context, mode)
var type Node = null
if context.eat(.STRUCT) {
var struct = parseStruct(context, flags)
if struct == null {
return null
}
return struct.withRange(context.spanSince(token.range))
}
if flags != 0 {
type = parseType(context, .REPORT_ERRORS)
if type == null {
return null
}
} else {
type = parseType(context, .IGNORE_ERRORS)
}
if type != null {
return parseAfterType(context, token.range, flags, type, .ALLOW_FUNCTIONS)
}
# Parse an expression
var value = pratt.parse(context, .LOWEST)
if value == null {
return null
}
return checkForSemicolon(context, token.range, Node.createExpression(value))
}
def checkStatementLocation(context ParserContext, node Node) {
if node.kind == .VARIABLES || node.kind == .STRUCT {
return
}
var isOutsideFunction =
context.scope.kind == .GLOBAL ||
context.scope.kind == .STRUCT
var shouldBeOutsideFunction =
node.kind == .EXTENSION ||
node.kind == .FUNCTION ||
node.kind == .PRECISION ||
node.kind == .VERSION
if shouldBeOutsideFunction && !isOutsideFunction {
context.log.syntaxErrorInsideFunction(node.range)
} else if !shouldBeOutsideFunction && isOutsideFunction {
context.log.syntaxErrorOutsideFunction(node.range)
}
}
def parseInclude(context ParserContext, parent Node) bool {
# See if there is a string literal
var range = context.current.range
if !context.expect(.STRING_LITERAL) {
return false
}
# Decode the escapes
var path string
try {
path = dynamic.JSON.parse(range.toString)
} catch {
context.log.syntaxErrorInvalidString(range)
return false
}
# Must have access to the file system
var fileAccess = context.compilationData.fileAccess
if fileAccess == null {
context.log.semanticErrorIncludeWithoutFileAccess(range)
return false
}
# Must be able to read the file
var source = fileAccess(path, range.source.name)
if source == null {
context.log.semanticErrorIncludeBadPath(range, path)
return false
}
# Parse the file and insert it into the parent
var tokens = Tokenizer.tokenize(context.log, source)
var nestedContext = ParserContext.new(context.log, tokens, context.compilationData, context.resolver)
nestedContext.pushScope(context.scope)
if !parseStatements(nestedContext, parent, .GLOBAL) || !nestedContext.expect(.END_OF_FILE) {
return false
}
return true
}
def parseBlock(context ParserContext) Node {
var token = context.current
var block = Node.createBlock
context.pushScope(Scope.new(.LOCAL, context.scope))
if !context.expect(.LEFT_BRACE) || !parseStatements(context, block, .LOCAL) || !context.expect(.RIGHT_BRACE) {
return null
}
context.popScope
return block.withRange(context.spanSince(token.range))
}
def parseFlags(context ParserContext, mode VariableKind) SymbolFlags {
var flags SymbolFlags = 0
while true {
var kind = context.current.kind
switch kind {
case .ATTRIBUTE { flags |= .ATTRIBUTE }
case .CONST { flags |= .CONST }
case .HIGHP { flags |= .HIGHP }
case .IN { flags |= .IN }
case .INOUT { flags |= .INOUT }
case .LOWP { flags |= .LOWP }
case .MEDIUMP { flags |= .MEDIUMP }
case .OUT { flags |= .OUT }
case .UNIFORM { flags |= .UNIFORM }
case .VARYING { flags |= .VARYING }
default { return flags }
}
if mode == .ARGUMENT && (kind == .ATTRIBUTE || kind == .UNIFORM || kind == .VARYING) ||
mode == .STRUCT && kind != .LOWP && kind != .MEDIUMP && kind != .HIGHP ||
mode != .ARGUMENT && (kind == .IN || kind == .OUT || kind == .INOUT) {
context.log.syntaxErrorBadQualifier(context.current.range)
}
context.next
}
}
enum ParseTypeMode {
IGNORE_ERRORS
REPORT_ERRORS
}
def parseType(context ParserContext, mode ParseTypeMode) Node {
var token = context.current
var type Type = null
switch token.kind {
case .BOOL { type = .BOOL }
case .BVEC2 { type = .BVEC2 }
case .BVEC3 { type = .BVEC3 }
case .BVEC4 { type = .BVEC4 }
case .FLOAT { type = .FLOAT }
case .INT { type = .INT }
case .IVEC2 { type = .IVEC2 }
case .IVEC3 { type = .IVEC3 }
case .IVEC4 { type = .IVEC4 }
case .MAT2 { type = .MAT2 }
case .MAT3 { type = .MAT3 }
case .MAT4 { type = .MAT4 }
case .SAMPLER2D { type = .SAMPLER2D }
case .SAMPLERCUBE { type = .SAMPLERCUBE }
case .VEC2 { type = .VEC2 }
case .VEC3 { type = .VEC3 }
case .VEC4 { type = .VEC4 }
case .VOID { type = .VOID }
case .IDENTIFIER {
var symbol = context.scope.find(token.range.toString)
if symbol == null || !symbol.isStruct {
if mode == .REPORT_ERRORS {
context.unexpectedToken
}
return null
}
type = symbol.resolvedType
}
default {
if mode == .REPORT_ERRORS {
context.unexpectedToken
}
return null
}
}
context.next
return Node.createType(type).withRange(context.spanSince(token.range))
}
def parseFunction(flags SymbolFlags, type Node, name Range, context ParserContext) Node {
var originalScope = context.scope
var function = FunctionSymbol.new(context.compilationData.nextSymbolID, name, name.toString, Scope.new(.FUNCTION, originalScope))
function.flags |= context.flags | flags | (function.name == "main" ? .EXPORTED : 0)
function.returnType = type
context.pushScope(function.scope)
# Takes no arguments
if context.eat(.VOID) {
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
}
# Takes arguments
else if !context.eat(.RIGHT_PARENTHESIS) {
while true {
# Parse leading flags
var argumentFlags = parseFlags(context, .ARGUMENT)
# Parse the type
var argumentType = parseType(context, .REPORT_ERRORS)
if argumentType == null {
return null
}
# Parse the identifier
var argumentName = context.current.range
if !context.expect(.IDENTIFIER) {
return null
}
# Create the argument
var argument = VariableSymbol.new(context.compilationData.nextSymbolID, argumentName, argumentName.toString, context.scope, .ARGUMENT)
argument.flags |= argumentFlags
argument.type = argumentType
function.arguments.append(argument)
tryToDefineUniquelyInScope(context, argument)
# Array size
if !parseArraySize(context, argument) {
return null
}
# Parse another argument?
if !context.eat(.COMMA) {
break
}
}
if !context.expect(.RIGHT_PARENTHESIS) {
return null
}
}
var previous = originalScope.symbols.get(name.toString, null)
var hasBlock = !context.eat(.SEMICOLON)
# Merge adjacent function symbols to support overloading
if previous == null {
originalScope.define(function)
} else if previous.isFunction {
for link = previous.asFunction; link != null; link = link.previousOverload {
if !link.hasSameArgumentTypesAs(function) {
continue
}
# Overloading by return type is not allowed
if link.returnType.resolvedType != function.returnType.resolvedType {
context.log.syntaxErrorDifferentReturnType(function.returnType.range, function.name,
function.returnType.resolvedType, link.returnType.resolvedType, link.returnType.range)
}
# Defining a function more than once is not allowed
else if link.block != null || !hasBlock {
context.log.syntaxErrorDuplicateSymbolDefinition(function.range, link.range)
}
# Merge the function with its forward declaration
else {
assert(link.sibling == null)
assert(function.sibling == null)
link.sibling = function
function.sibling = link
function.flags |= link.flags
link.flags = function.flags
}
break
}
# Use a singly-linked list to store the function overloads
function.previousOverload = previous.asFunction
originalScope.redefine(function)
} else {
context.log.syntaxErrorDuplicateSymbolDefinition(name, previous.range)
return null
}
if hasBlock {
var old = context.flags
context.flags &= ~(.EXPORTED | .IMPORTED)
function.block = parseBlock(context)
context.flags &= old
if function.block == null {
return null
}
}
context.popScope
return Node.createFunction(function).withRange(context.spanSince(type.range))
}
def parseArraySize(context ParserContext, variable VariableSymbol) bool {
var token = context.current
if context.eat(.LEFT_BRACKET) {
# The "[]" syntax isn't valid but skip over it and recover
if context.eat(.RIGHT_BRACKET) {
context.log.syntaxErrorMissingArraySize(context.spanSince(token.range))
return true
}
variable.arrayCount = pratt.parse(context, .LOWEST)
if variable.arrayCount == null || !context.expect(.RIGHT_BRACKET) {
return false
}
# The array size must be resolved immediately
var count = 0
context.resolver.resolveNode(variable.arrayCount)
context.resolver.checkConversion(variable.arrayCount, .INT)
if variable.arrayCount.resolvedType != .ERROR {
var folded = Folder.fold(variable.arrayCount)
if folded == null {
context.log.syntaxErrorConstantRequired(variable.arrayCount.range)
} else if folded.kind == .INT {
var value = folded.asInt
if value < 1 {
context.log.syntaxErrorInvalidArraySize(variable.arrayCount.range, value)
} else {
count = value
}
}
}
# Multidimensional arrays are not supported
while context.peek(.LEFT_BRACKET) {
token = context.next
if !context.peek(.RIGHT_BRACKET) && pratt.parse(context, .LOWEST) == null || !context.expect(.RIGHT_BRACKET) {
return false
}
context.log.syntaxErrorMultidimensionalArray(context.spanSince(token.range))
}
variable.type = Node.createType(variable.type.resolvedType.arrayType(count)).withRange(variable.type.range)
}
return true
}
def parseVariables(flags int, type Node, name Range, context ParserContext) Node {
var variables = Node.createVariables(context.flags | flags, type)
while true {
var symbol = VariableSymbol.new(context.compilationData.nextSymbolID, name, name.toString, context.scope,
context.scope.kind == .GLOBAL ? .GLOBAL :
context.scope.kind == .STRUCT ? .STRUCT :
.LOCAL)
symbol.flags |= context.flags | flags
symbol.type = type
# Array size
if !parseArraySize(context, symbol) {
return null
}
# Initial value
var assign = context.current.range
if context.eat(.ASSIGN) {
symbol.value = pratt.parse(context, .COMMA)
if symbol.value == null {
return null
}
} else {
assign = null
}
# Constants must be resolved immediately
var variable = Node.createVariable(symbol).withRange(context.spanSince(symbol.range)).withInternalRange(assign)
if symbol.isConst {
context.resolver.resolveNode(variable)
}
variables.appendChild(variable)
tryToDefineUniquelyInScope(context, symbol)
# Are there more variables in this statement?
if !context.eat(.COMMA) {
context.expect(.SEMICOLON)
return variables
}
name = context.current.range
if !context.expect(.IDENTIFIER) {
return null
}
}
}
def tryToDefineUniquelyInScope(context ParserContext, symbol Symbol) bool {
var previous = context.scope.symbols.get(symbol.name, null)
if previous != null {
context.log.syntaxErrorDuplicateSymbolDefinition(symbol.range, previous.range)
return false
}
context.scope.define(symbol)
return true
}
def parseStatements(context ParserContext, parent Node, mode VariableKind) bool {
while !context.peek(.END_OF_FILE) && !context.peek(.RIGHT_BRACE) {
if context.eat(.INCLUDE) {
if !parseInclude(context, parent) {
return false
}
continue
}
var statement = parseStatement(context, mode)
if statement == null {
return false
}
# Extension blocks are temporary and don't exist in the parsed result
if statement.kind == .MODIFIER_BLOCK {
while statement.hasChildren {
var child = statement.firstChild.remove
checkStatementLocation(context, child)
parent.appendChild(child)
}
} else {
checkStatementLocation(context, statement)
parent.appendChild(statement)
}
}
return true
}
def parse(log Log, tokens List<Token>, global Node, data CompilerData, scope Scope, resolver Resolver) {
if pratt == null {
pratt = createExpressionParser
}
var context = ParserContext.new(log, tokens, data, resolver)
context.pushScope(scope)
if parseStatements(context, global, .GLOBAL) {
context.expect(.END_OF_FILE)
}
}
}