Skip to content

Commit

Permalink
feat: add support for if, elif, and else statements
Browse files Browse the repository at this point in the history
  • Loading branch information
lavyyy committed Aug 1, 2024
1 parent d359fb1 commit 130f0ed
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 0 deletions.
65 changes: 65 additions & 0 deletions __tests__/conditionals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { expect, test } from 'vitest'
import { Molang } from '../lib/Molang'

const molang = new Molang()

test('Basic if statement', () => {
const statement = `
if (true) {
return 1;
}`

expect(molang.execute(statement)).toBe(1)
})

test('Elif chain with variables', () => {
const statement = `
v.test = 10;
if (v.test == 0) {
return 1;
} elif (v.test == 5) {
return 2;
} elif (v.test == 10) {
return 3;
}`

expect(molang.execute(statement)).toBe(3)
})

test('Else statement', () => {
const statement = `
v.test = 50;
if (v.test == 0) {
return 1;
} elif (v.test == 5) {
return 2;
} elif (v.test == 10) {
return 3;
} else {
return 4;
}`

expect(molang.execute(statement)).toBe(4)
})

test('Nested conditionals', () => {
const statement = `
v.test = 10;
v.test2 = 5;
if (v.test == 0) {
return 1;
} elif (v.test == 5) {
return 2;
} elif (v.test == 10) {
if (v.test2 == 2) {
return 3;
} else {
return 4;
}
}`

expect(molang.execute(statement)).toBe(4)
})
80 changes: 80 additions & 0 deletions lib/parser/expressions/if.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Expression, IExpression } from '../expression'

export class IfExpression extends Expression {
type = 'IfExpression'

constructor(
protected test: IExpression,
protected consequent: IExpression,
protected elifClauses: IfExpression[] = [],
protected alternate?: IExpression
) {
super()
}

get isReturn(): boolean {
return (
this.consequent.isReturn ||
this.elifClauses.some((clause) => clause.isReturn)
)
}

get allExpressions(): IExpression[] {
return [
this.test,
this.consequent,
...this.elifClauses.flatMap((clause) => clause.allExpressions),
this.alternate,
].filter((expr) => expr !== undefined)
}
setExpressionAt(index: number, expr: IExpression) {
if (index === 0) this.test = expr
else if (index === 1) this.consequent = expr
else if (index > 1 && index < 2 + this.elifClauses.length) {
const clauseIndex = index - 2
this.elifClauses[clauseIndex].setExpressionAt(0, expr)
} else if (index === 2 + this.elifClauses.length) this.alternate = expr
}

isStatic(): boolean {
return (
this.test.isStatic() &&
this.consequent.isStatic() &&
this.elifClauses.every((clause) => clause.isStatic()) &&
(!this.alternate || this.alternate.isStatic())
)
}

eval() {
if (this.test.eval()) {
const val = this.consequent.eval()

return val
}

for (let i = 0; i < this.elifClauses.length; i++) {
if (this.elifClauses[i].test.eval()) {
return this.elifClauses[i].consequent.eval()
}
}

if (this.alternate) {
return this.alternate.eval()
}

return null
}

toString() {
const elifString = this.elifClauses
.map(
(clause) =>
`elif (${clause.test.toString()}) {${clause.consequent.toString()}}`
)
.join(' ')
const elseString = this.alternate
? `else {${this.alternate.toString()}}`
: ''
return `if (${this.test.toString()}) {${this.consequent.toString()}} ${elifString} ${elseString}`
}
}
1 change: 1 addition & 0 deletions lib/parser/expressions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { StaticExpression } from './static'
export { StringExpression } from './string'
export { TernaryExpression } from './ternary'
export { VoidExpression } from './void'
export { IfExpression } from './if'
2 changes: 2 additions & 0 deletions lib/parser/molang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { OrOperator } from './parselets/OrOperator'
import { SmallerOperator } from './parselets/SmallerOperator'
import { GreaterOperator } from './parselets/GreaterOperator'
import { QuestionOperator } from './parselets/QuestionOperator'
import { IfParselet } from './parselets/if'

export class MolangParser extends Parser {
constructor(config: Partial<IParserConfig>) {
Expand All @@ -40,6 +41,7 @@ export class MolangParser extends Parser {
this.registerPrefix('BREAK', new BreakParselet())
this.registerPrefix('LOOP', new LoopParselet())
this.registerPrefix('FOR_EACH', new ForEachParselet())
this.registerPrefix('IF', new IfParselet())
this.registerInfix(
'QUESTION',
new QuestionOperator(EPrecedence.CONDITIONAL)
Expand Down
41 changes: 41 additions & 0 deletions lib/parser/parselets/if.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Token } from '../../tokenizer/token'
import { IfExpression } from '../expressions'
import { Parser } from '../parse'
import { IPrefixParselet } from './prefix'

export class IfParselet implements IPrefixParselet {
constructor(public precedence = 0) {}

parse(parser: Parser, token: Token) {
parser.consume('LEFT_PARENT')
const condition = parser.parseExpression(this.precedence)
parser.consume('RIGHT_PARENT')

parser.consume('CURLY_LEFT')
const consequent = parser.parseExpression(this.precedence)
parser.consume('CURLY_RIGHT')

const elifClauses: IfExpression[] = []

while (parser.match('ELIF')) {
parser.consume('LEFT_PARENT')
const elifCondition = parser.parseExpression(this.precedence)
parser.consume('RIGHT_PARENT')

parser.consume('CURLY_LEFT')
const elifConsequent = parser.parseExpression(this.precedence)
parser.consume('CURLY_RIGHT')

elifClauses.push(new IfExpression(elifCondition, elifConsequent))
}

let alternate
if (parser.match('ELSE')) {
parser.consume('CURLY_LEFT')
alternate = parser.parseExpression(this.precedence)
parser.consume('CURLY_RIGHT')
}

return new IfExpression(condition, consequent, elifClauses, alternate)
}
}
3 changes: 3 additions & 0 deletions lib/tokenizer/tokenTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ export const KeywordTokens = new Set([
'loop',
'false',
'true',
'if',
'elif',
'else',
])

0 comments on commit 130f0ed

Please sign in to comment.