Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add enum support #19

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/server/ast/statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,82 @@ export class ASTFunction extends ASTNodeData implements ASTNode
}
}

export class ASTEnumMember extends ASTNodeData implements ASTNode
{
private _name: ASTIdent
private _value: ASTNode | undefined

constructor(enumMemberToken: Token, name: ASTIdent, value?: ASTNode)
{
super(enumMemberToken)
this._name = name
this._value = value
}

get semanticType() { return SemanticTokenTypes.type }
get name() { return this._name }
get value() { return this._value }
get type() { return ASTType.enumDef }

toString()
{
return `<EnumMember '${this.name.fullName}'>`
}

get valid()
{
return this.token.valid &&
this.name.valid
}

*semanticTokens(): Generator<SemanticToken, void, undefined>
{
yield* generateSemanticTokens(this)
yield this.buildSemanticToken(SemanticTokenTypes.type, this._name.token)
if (this._value)
yield this.buildSemanticToken(SemanticTokenTypes.type, this._value.token)
}
}

export class ASTEnum extends ASTNodeData implements ASTNode
{
private _name: ASTIdent
private _members: ASTEnumMember[]

constructor(enumToken: Token, name: ASTIdent, values: ASTEnumMember[])
{
super(enumToken)
this._name = name
this._members = values
}

get semanticType() { return SemanticTokenTypes.keyword }
get name() { return this._name.fullName }
get members() { return this._members }
get type() { return ASTType.enumDef }

toString()
{
return `<Enum: '${this.name}' Members: '${this.members.length}'>`
}

get valid()
{
const membersValid = this.members.every(member => member.valid)
return this.token.valid &&
this._name.valid &&
membersValid
}

*semanticTokens(): Generator<SemanticToken, void, undefined>
{
yield* generateSemanticTokens(this)
for (const value of this.members)
yield* value.semanticTokens()
yield this.buildSemanticToken(SemanticTokenTypes.type, this._name.token)
}
}

export class ASTOperator extends ASTNodeData implements ASTNode
{
private _operator: Token | ASTIdent
Expand Down
49 changes: 29 additions & 20 deletions src/server/ast/symbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,38 @@ import {Parser} from '../parser/parser'

export enum SymbolTypes
{
invalid = 0x0000,
integer = 0x0001,
signed = integer | 0x0000,
unsigned = 0x0002,
int8Bit = integer | 0x0000,
int16Bit = integer | 0x0004,
int32Bit = integer | 0x0008,
int64Bit = integer | 0x000C,
character = 0x0010,
list = 0x0020,
invalid = 0x000000,
integer = 0x000001,
signed = integer | 0x000000,
unsigned = 0x000002,
int8Bit = integer | 0x000000,
uint8Bit = integer | 0x000002,
int16Bit = integer | 0x000004,
uint16Bit = integer | 0x000006,
int32Bit = integer | 0x000008,
uint32Bit = integer | 0x00000A,
int64Bit = integer | 0x00000C,
uint64Bit = integer | 0x00000E,
float = 0x000010,
float32Bit = float | 0x000000,
float64Bit = float | 0x000004,
character = 0x000020,
list = 0x000040,
string = character | list,
struct = 0x0040,
struct = 0x000080,
dict = struct | list,
array = 0x0080,
array = 0x000100,
set = struct | array,
bool = 0x0100,
function = 0x0200,
reference = 0x0400,
pointer = 0x0800,
pack = 0x1000, // This is for template type/value packs
auto = 0x2000,
none = 0x4000,
type = 0x8000
bool = 0x000200,
enum = 0x000400,
class = 0x00800,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class is struct above as while the keyword is class, the language defines them as structures-with-members so internally we've been using struct

function = 0x001000,
reference = 0x002000,
pointer = 0x004000,
pack = 0x008000, // This is for template type/value packs
auto = 0x010000,
none = 0x020000,
type = 0x040000,
}

export class SymbolType
Expand Down
1 change: 1 addition & 0 deletions src/server/ast/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum ASTType
templateDef,
functionDef,
operatorDef,
enumDef,
visibility,
block
}
Expand Down
93 changes: 83 additions & 10 deletions src/server/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import
ASTVisibility,
ASTParams,
ASTReturnType,
ASTEnum,
ASTEnumMember,
ASTTemplate,
ASTFunction,
ASTOperator,
Expand All @@ -94,7 +96,7 @@ function isNodeRelation(node: ASTNode | ASTRel): node is ASTRel

type IdentAndComments = {token: Token, comments: ASTNode[]}
type IdentDef = {type?: ASTIdent, ident: ASTIdent}
type BlockConfig = {allowExtStmt: boolean}
type BlockConfig = {allowExtStmt: boolean, isEnum: boolean}

export class Parser
{
Expand Down Expand Up @@ -1288,7 +1290,7 @@ export class Parser
const cond = this.parseLogicExpr()
if (!cond.isValid())
return cond as Result<undefined, ParsingErrors>
const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
else if (block.isErr())
Expand All @@ -1309,7 +1311,7 @@ export class Parser
const cond = this.parseLogicExpr()
if (!cond.isValid())
return cond as Result<undefined, ParsingErrors>
const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
else if (block.isErr())
Expand All @@ -1327,7 +1329,7 @@ export class Parser
const match = this.match(TokenType.elseStmt)
if (!match)
return Err('UnreachableState')
const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
else if (block.isErr())
Expand Down Expand Up @@ -1411,7 +1413,7 @@ export class Parser
return Err('MissingRightBracket')

// Now match the loop body
const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
else if (block.isErr())
Expand All @@ -1438,7 +1440,7 @@ export class Parser
if (cond.isErr())
return cond
// Now parse the body of the loop
const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
else if (block.isErr())
Expand Down Expand Up @@ -1695,7 +1697,7 @@ export class Parser
if (templateParams.isErr())
return templateParams

const block = this.parseBlock({allowExtStmt: true})
const block = this.parseBlock({allowExtStmt: true, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
if (block.isErr())
Expand All @@ -1714,6 +1716,73 @@ export class Parser
return result
}

parseEnumDef(): Result<ASTNode, ParsingErrors>
{
const token = this.lexer.token.clone()
const match = this.match(TokenType.enumDef)
if (!match)
return Err('UnreachableState')

const ident = this.parseIdent()
if (!ident.isDefined())
return Err('InvalidTokenSequence')
if (ident.isErr() || ident.isInvalid())
return ident

const enumName = ident.val
// If the symbol's already in the table but is not a function symbol, that's an error
if (enumName.symbol && !enumName.symbol.type.mask(SymbolTypes.enum))
return Err('SymbolAlreadyDefined')
enumName.symbol = new MangroveSymbol(enumName.value, new SymbolType(SymbolTypes.enum | SymbolTypes.type))
this.symbolTable.insert(enumName.symbol)

const block = this.parseBlock({allowExtStmt: false, isEnum: true})
if (!block.isDefined())
return Err('InvalidTokenSequence')
if (block.isErr() || block.isInvalid())
return block
const astBlock = block.val as ASTBlock
const enumMembers = astBlock.statements
const valuesValid = enumMembers.every(value => value.valid)
if (enumMembers && !valuesValid)
this.symbolTable.pop(this)
return Ok(new ASTEnum(token, enumName, enumMembers as ASTEnumMember[]))
}

parseEnumMember(): Result<ASTEnumMember, ParsingErrors>
{
let value
const token = this.lexer.token
if (token.typeIsOneOf(TokenType.comma))
this.lexer.next()
this.skipWhite()
const ident = this.parseIdent()
if (!ident.isDefined())
return Err('InvalidTokenSequence')
if (ident.isErr() || ident.isInvalid())
return ident
// Parse the actual enum value
this.skipWhite()
if (token.typeIsOneOf(TokenType.assignOp))
{
this.lexer.next()
this.skipWhite()
value = this.parseInt()
}

if (value)
{
if (!value.isDefined)
return Err('InvalidTokenSequence')
if (value.isErr() || value.isInvalid())
return Err('InvalidAssignment')

return Ok(new ASTEnumMember(token, ident.val, value.val))
}

return Ok(new ASTEnumMember(token, ident.val))
}

parseFunctionDef(): Result<ASTNode, ParsingErrors>
{
const functionToken = this.lexer.token.clone()
Expand Down Expand Up @@ -1752,7 +1821,7 @@ export class Parser
if (returnType.isErr())
return returnType

const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
if (block.isErr())
Expand Down Expand Up @@ -1826,7 +1895,7 @@ export class Parser
if (returnType.isErr())
return returnType

const block = this.parseBlock({allowExtStmt: false})
const block = this.parseBlock({allowExtStmt: false, isEnum: false})
if (!block.isDefined())
return Err('MissingBlock')
if (block.isErr())
Expand Down Expand Up @@ -1854,6 +1923,8 @@ export class Parser
const token = this.lexer.token
if (config.allowExtStmt && token.typeIsOneOf(TokenType.visibility))
return this.parseVisibility()
if (config.isEnum)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably check && token.typeIsOneOf(TokenType.enumDef)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only token that's considered the enum type is the initial enum keyword, we also can't really force every token inside the enum to be of the enum type as when we parse the value assignments to the enum members they need to have their own typing, seperate to the enum.

Since the isEnum config key is set only once we've read the enum keyword and continue to parse the braces we thought this would be fine as no other routes could lead to this being reached and parsing anything else incorrectly, let us know if you have any thoughts on this one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It comes down to: Do we want to only allow enums to contain enumeration values, or do we want to allow them to contain conditional logic, visibility statements, functions, operator overloads, etc?

One of the biggest problems C++ has always had with its enumerations (after fixing the scope problem) is there's no way to directly attach functionality to the type such as helpers to string-ify the enumeration values, and operators to help build complex values from enumerated values. If you agree that can make sense here, and given this current setup does allow visibility statements already, then we think checking for TokenType.enumDef is correct, otherwise we agree with the restriction

return this.parseEnumMember()
if (token.typeIsOneOf(TokenType.fromStmt))
return this.parseImportStmt()
if (token.typeIsOneOf(TokenType.ifStmt))
Expand All @@ -1862,6 +1933,8 @@ export class Parser
return this.parseForStmt()
if (token.typeIsOneOf(TokenType.whileStmt))
return this.parseWhileStmt()
if (token.typeIsOneOf(TokenType.enumDef))
return this.parseEnumDef()

const stmt = this.parseDefine()
if (stmt.isInvalid())
Expand Down Expand Up @@ -1940,7 +2013,7 @@ export class Parser
const nodes = this.skipWhite()
while (!token.typeIsOneOf(TokenType.eof))
{
const stmt = this.parseStatement({allowExtStmt: true})
const stmt = this.parseStatement({allowExtStmt: true, isEnum: false})
if (!stmt.isValid() && this.haveIdent)
{
const ident = this.ident as ASTIdent
Expand Down