forked from bridge-core/molang
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for if, elif, and else statements
- Loading branch information
Showing
6 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}` | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,4 +29,7 @@ export const KeywordTokens = new Set([ | |
'loop', | ||
'false', | ||
'true', | ||
'if', | ||
'elif', | ||
'else', | ||
]) |