save work before rewriting in rust
This commit is contained in:
parent
5062b3aebe
commit
7c8194633b
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -1,5 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
[[package]]
|
|
||||||
name = "pivot"
|
|
||||||
version = "0.1.0"
|
|
@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "pivot"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["ElementG9 <garentyler@gmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
0
errors.txt
Normal file
0
errors.txt
Normal file
10
output.s
Normal file
10
output.s
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.global main
|
||||||
|
main:
|
||||||
|
push {fp, lr}
|
||||||
|
ldr r0, =1
|
||||||
|
cmp r0, #1
|
||||||
|
moveq r0, #'.'
|
||||||
|
movne r0, #'F'
|
||||||
|
bl putchar
|
||||||
|
mov r0, #0
|
||||||
|
pop {fp, pc}
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "pivot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A programming language",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"run": "deno run -c tsconfig.json --allow-read --allow-write --unstable src/index.ts"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ElementG9/pivot.git"
|
||||||
|
},
|
||||||
|
"author": "Garen Tyler",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ElementG9/pivot/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ElementG9/pivot#readme"
|
||||||
|
}
|
273
src/ast.ts
Normal file
273
src/ast.ts
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import { emit } from './codegen.ts';
|
||||||
|
|
||||||
|
export interface AST {
|
||||||
|
emit(): void;
|
||||||
|
equals(other: AST): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number node.
|
||||||
|
export class Num implements AST {
|
||||||
|
constructor(public value: number) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
emit(` ldr r0, =${this.value}`);
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Num
|
||||||
|
&& this.value === other.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Identifier node.
|
||||||
|
export class Id implements AST {
|
||||||
|
constructor(public value: string) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Id
|
||||||
|
&& this.value === other.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Operator nodes.
|
||||||
|
export class Not implements AST {
|
||||||
|
constructor(public operand: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
this.operand.emit();
|
||||||
|
emit(' cmp r0, #0');
|
||||||
|
emit(' moveq r0, #1');
|
||||||
|
emit(' movne r0, #0');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Not
|
||||||
|
&& this.operand.equals(other.operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export abstract class Infix {
|
||||||
|
constructor(public left: AST, public right: AST) {}
|
||||||
|
emit() {
|
||||||
|
this.left.emit();
|
||||||
|
emit(' push {r0, ip}');
|
||||||
|
this.right.emit();
|
||||||
|
emit(' pop {r1, ip}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Equal extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' cmp r0, r1');
|
||||||
|
emit(' moveq r0, #1');
|
||||||
|
emit(' movne r0, #0');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Equal
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class NotEqual extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' cmp r0, r1');
|
||||||
|
emit(' moveq r0, #0');
|
||||||
|
emit(' movne r0, #1');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof NotEqual
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Add extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' add r0, r0, r1');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Add
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Subtract extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' sub r0, r0, r1');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Subtract
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Multiply extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' mul r0, r0, r1');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Multiply
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Divide extends Infix implements AST {
|
||||||
|
constructor(public left: AST, public right: AST) {
|
||||||
|
super(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
super.emit();
|
||||||
|
emit(' udiv r0, r0, r1');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Divide
|
||||||
|
&& this.left.equals(other.left)
|
||||||
|
&& this.right.equals(other.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Function call node.
|
||||||
|
export class FunctionCall implements AST {
|
||||||
|
constructor(public callee: string, public args: Array<AST>) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof FunctionCall
|
||||||
|
&& this.callee === other.callee
|
||||||
|
&& this.args.length === other.args.length
|
||||||
|
&& this.args.every((arg: AST, i: number) => arg.equals(other.args[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return node.
|
||||||
|
export class Return implements AST {
|
||||||
|
constructor(public operand: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Return
|
||||||
|
&& this.operand.equals(other.operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Block node.
|
||||||
|
export class Block implements AST {
|
||||||
|
constructor(public statements: Array<AST>) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
this.statements.forEach((statement: any) => statement.emit());
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Block
|
||||||
|
&& this.statements.length === other.statements.length
|
||||||
|
&& this.statements.every((arg: AST, i: number) => arg.equals(other.statements[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If node.
|
||||||
|
export class If implements AST {
|
||||||
|
constructor(public conditional: AST,
|
||||||
|
public consequence: AST,
|
||||||
|
public alternative: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof If
|
||||||
|
&& this.conditional.equals(other.conditional)
|
||||||
|
&& this.consequence.equals(other.consequence)
|
||||||
|
&& this.alternative.equals(other.alternative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Function definition node.
|
||||||
|
export class FunctionDefinition implements AST {
|
||||||
|
constructor(public name: string,
|
||||||
|
public parameters: Array<string>,
|
||||||
|
public body: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error(`Not implemented yet (${this.name})`);
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof FunctionDefinition
|
||||||
|
&& this.name === other.name
|
||||||
|
&& this.parameters.length === other.parameters.length
|
||||||
|
&& this.parameters.every((arg: string, i: number) => arg === other.parameters[i])
|
||||||
|
&& this.body.equals(other.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Variable declaration node.
|
||||||
|
export class VariableDeclaration implements AST {
|
||||||
|
constructor(public name: string, public value: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof VariableDeclaration
|
||||||
|
&& this.name === other.name
|
||||||
|
&& this.value.equals(other.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assignment node.
|
||||||
|
export class Assign implements AST {
|
||||||
|
constructor(public name: string, public value: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Assign
|
||||||
|
&& this.name === other.name
|
||||||
|
&& this.value.equals(other.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// While loop node.
|
||||||
|
export class While implements AST {
|
||||||
|
constructor(public conditional: AST, public body: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof While
|
||||||
|
&& this.conditional.equals(other.conditional)
|
||||||
|
&& this.body.equals(other.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Variable node.
|
||||||
|
export class Var implements AST {
|
||||||
|
constructor(public name: string, public value: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
throw Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Var
|
||||||
|
&& this.name === other.name
|
||||||
|
&& this.value.equals(other.value);
|
||||||
|
}
|
||||||
|
}
|
55
src/codegen.ts
Normal file
55
src/codegen.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { AST } from './ast.ts';
|
||||||
|
import { existsSync } from 'https://deno.land/std/fs/mod.ts';
|
||||||
|
|
||||||
|
const outputFileName = '../output.s';
|
||||||
|
|
||||||
|
const outputToFile = false;
|
||||||
|
export async function emit(input: string) {
|
||||||
|
if (outputToFile) {
|
||||||
|
if (existsSync(outputFileName)) {
|
||||||
|
await Deno.remove(outputFileName);
|
||||||
|
}
|
||||||
|
await Deno.create(outputFileName);
|
||||||
|
const outputFile = await Deno.open(outputFileName, {
|
||||||
|
write: true,
|
||||||
|
append: true,
|
||||||
|
});
|
||||||
|
await Deno.write(outputFile.rid, new TextEncoder().encode(input));
|
||||||
|
} else {
|
||||||
|
console.log(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Main implements AST {
|
||||||
|
constructor(public statements: Array<AST>) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
emit('.global main');
|
||||||
|
emit('main:');
|
||||||
|
emit(' push {fp, lr}');
|
||||||
|
this.statements.forEach((statement: any) => statement.emit());
|
||||||
|
emit(' mov r0, #0');
|
||||||
|
emit(' pop {fp, pc}');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Main
|
||||||
|
&& this.statements.length === other.statements.length
|
||||||
|
&& this.statements.every((arg: AST, i: number) => arg.equals(other.statements[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Assert implements AST {
|
||||||
|
constructor(public condition: AST) {}
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
this.condition.emit();
|
||||||
|
emit(' cmp r0, #1');
|
||||||
|
emit(` moveq r0, #'.'`);
|
||||||
|
emit(` movne r0, #'F'`);
|
||||||
|
emit(' bl putchar');
|
||||||
|
}
|
||||||
|
equals(other: AST): boolean {
|
||||||
|
return other instanceof Assert
|
||||||
|
&& this.condition.equals(other.condition);
|
||||||
|
}
|
||||||
|
}
|
10
src/index.ts
Normal file
10
src/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { parse } from './parser.ts';
|
||||||
|
|
||||||
|
let result = parse(`
|
||||||
|
function main() {
|
||||||
|
assert(1);
|
||||||
|
assert(!0);
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
// console.log(JSON.stringify(result, null, 2));
|
||||||
|
result.emit();
|
@ -1,42 +0,0 @@
|
|||||||
use super::parser::*;
|
|
||||||
|
|
||||||
pub fn interpret(ast: Program) {
|
|
||||||
for stmt in ast {
|
|
||||||
use Statement::*;
|
|
||||||
match stmt {
|
|
||||||
FunctionCall { name, arguments } => {
|
|
||||||
let name: &str = &name;
|
|
||||||
// println!("{} called!", name);
|
|
||||||
match name {
|
|
||||||
"log" => {
|
|
||||||
let mut args: Vec<String> = vec![];
|
|
||||||
for a in arguments {
|
|
||||||
match a {
|
|
||||||
Expression::Literal(literal) => match literal {
|
|
||||||
Literal::StringLiteral(s) => args.push(s),
|
|
||||||
Literal::IntLiteral(i) => args.push(format!("{}i", i)),
|
|
||||||
Literal::FloatLiteral(f) => {
|
|
||||||
if f.fract() == 0.0 {
|
|
||||||
args.push(format!("{}.0f", f));
|
|
||||||
} else {
|
|
||||||
args.push(format!("{}f", f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Literal::BooleanLiteral(b) => args.push(format!("{}", b)),
|
|
||||||
},
|
|
||||||
Expression::Null => {
|
|
||||||
args.push("null".to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("{}", args.join(", "));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Nop => {
|
|
||||||
// println!("No-op!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
15
src/lib.rs
15
src/lib.rs
@ -1,15 +0,0 @@
|
|||||||
pub mod interpreter;
|
|
||||||
pub mod parser;
|
|
||||||
pub mod tokenizer;
|
|
||||||
|
|
||||||
pub fn interpret(source: &str) {
|
|
||||||
let tokens = tokenizer::tokenize(source);
|
|
||||||
// println!("{:#?}", tokens);
|
|
||||||
let ast = parser::parse(tokens);
|
|
||||||
// println!("{:#?}", ast);
|
|
||||||
interpreter::interpret(ast);
|
|
||||||
}
|
|
||||||
pub fn interpret_file(filename: &str) {
|
|
||||||
let src = std::fs::read_to_string(filename).unwrap();
|
|
||||||
interpret(&src);
|
|
||||||
}
|
|
0
src/lib.ts
Normal file
0
src/lib.ts
Normal file
@ -1,8 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
let args = std::env::args().collect::<Vec<String>>();
|
|
||||||
if args.len() < 2 {
|
|
||||||
println!("Usage: pivot <filename>");
|
|
||||||
} else {
|
|
||||||
pivot::interpret_file(&args[1]);
|
|
||||||
}
|
|
||||||
}
|
|
127
src/parser.rs
127
src/parser.rs
@ -1,127 +0,0 @@
|
|||||||
use super::tokenizer::{Token, TokenKind};
|
|
||||||
|
|
||||||
pub fn parse(mut tokens: Vec<Token>) -> Program {
|
|
||||||
let mut stmts: Vec<Vec<Token>> = vec![];
|
|
||||||
let mut current = 0;
|
|
||||||
loop {
|
|
||||||
if current >= tokens.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// print!("{:?}", tokens[current]);
|
|
||||||
if tokens[current].kind == TokenKind::Semicolon {
|
|
||||||
stmts.push(tokens.drain(..=current).collect::<Vec<Token>>());
|
|
||||||
current = 0;
|
|
||||||
} else {
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut statements = vec![];
|
|
||||||
// for s in &stmts {
|
|
||||||
// for t in s {
|
|
||||||
// print!("{:?}", t.kind);
|
|
||||||
// }
|
|
||||||
// print!("\n");
|
|
||||||
// }
|
|
||||||
for s in stmts {
|
|
||||||
statements.push(parse_statement(s));
|
|
||||||
}
|
|
||||||
statements
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_statement(statement: Vec<Token>) -> Statement {
|
|
||||||
if statement.len() == 1 {
|
|
||||||
// Must just be a semicolon.
|
|
||||||
return Statement::Nop;
|
|
||||||
}
|
|
||||||
let parse_function_call = |tokens: &Vec<Token>| -> Option<Statement> {
|
|
||||||
// Check for <identifier>( ... );
|
|
||||||
if tokens[0].kind != TokenKind::Identifier {
|
|
||||||
return None;
|
|
||||||
} else if tokens[1].kind != TokenKind::LeftParen {
|
|
||||||
return None;
|
|
||||||
} else if tokens[tokens.len() - 2].kind != TokenKind::RightParen {
|
|
||||||
return None;
|
|
||||||
} else if tokens[tokens.len() - 1].kind != TokenKind::Semicolon {
|
|
||||||
return None;
|
|
||||||
} else {
|
|
||||||
let function_name = tokens[0].value.clone();
|
|
||||||
let mut args = vec![];
|
|
||||||
|
|
||||||
let mut current = 2;
|
|
||||||
loop {
|
|
||||||
args.push(parse_expression(tokens, &mut current));
|
|
||||||
if tokens[current].kind == TokenKind::Comma {
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
if tokens[current].kind == TokenKind::RightParen {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Statement::FunctionCall {
|
|
||||||
name: function_name,
|
|
||||||
arguments: args,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// The only form of statement.
|
|
||||||
parse_function_call(&statement).expect("could not parse function call")
|
|
||||||
}
|
|
||||||
fn parse_expression(tokens: &Vec<Token>, current: &mut usize) -> Expression {
|
|
||||||
if tokens[*current].kind == TokenKind::StringLiteral {
|
|
||||||
let out = Expression::Literal(Literal::StringLiteral(tokens[*current].value.clone()));
|
|
||||||
*current += 1;
|
|
||||||
out
|
|
||||||
} else if tokens[*current].kind == TokenKind::IntLiteral {
|
|
||||||
let val = tokens[*current]
|
|
||||||
.value
|
|
||||||
.clone()
|
|
||||||
.parse::<i32>()
|
|
||||||
.expect("could not parse int literal");
|
|
||||||
let out = Expression::Literal(Literal::IntLiteral(val));
|
|
||||||
*current += 1;
|
|
||||||
out
|
|
||||||
} else if tokens[*current].kind == TokenKind::FloatLiteral {
|
|
||||||
let val = tokens[*current]
|
|
||||||
.value
|
|
||||||
.clone()
|
|
||||||
.parse::<f32>()
|
|
||||||
.expect("could not parse float literal");
|
|
||||||
let out = Expression::Literal(Literal::FloatLiteral(val));
|
|
||||||
*current += 1;
|
|
||||||
out
|
|
||||||
} else if tokens[*current].kind == TokenKind::BooleanLiteral {
|
|
||||||
let mut val = false;
|
|
||||||
if tokens[*current].value == "true" {
|
|
||||||
val = true;
|
|
||||||
} else if tokens[*current].value == "false" {
|
|
||||||
val = false;
|
|
||||||
}
|
|
||||||
*current += 1;
|
|
||||||
Expression::Literal(Literal::BooleanLiteral(val))
|
|
||||||
} else {
|
|
||||||
Expression::Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Program = Vec<Statement>;
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Statement {
|
|
||||||
FunctionCall {
|
|
||||||
name: String,
|
|
||||||
arguments: Vec<Expression>,
|
|
||||||
},
|
|
||||||
Nop, // Equivalent to a C nop statement.
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Expression {
|
|
||||||
Literal(Literal),
|
|
||||||
Null,
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Literal {
|
|
||||||
StringLiteral(String),
|
|
||||||
IntLiteral(i32),
|
|
||||||
FloatLiteral(f32),
|
|
||||||
BooleanLiteral(bool),
|
|
||||||
}
|
|
200
src/parser.ts
Normal file
200
src/parser.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import { AST, Num, Id, Not, Equal, NotEqual, Add, Subtract, Multiply, Divide, FunctionCall, Return, If, While, Var, Assign, Block, FunctionDefinition } from './ast.ts';
|
||||||
|
import { Main, Assert } from './codegen.ts';
|
||||||
|
|
||||||
|
export function parse(source: string): AST {
|
||||||
|
// Whitespace and comments.
|
||||||
|
let whitespace = Parser.regex(/[ \n\r\t]+/y);
|
||||||
|
let comments = Parser.regex(/[/][/].*/y).or(
|
||||||
|
Parser.regex(/[/][*].*[*][/]/sy)
|
||||||
|
);
|
||||||
|
let ignored = Parser.zeroOrMore(whitespace.or(comments));
|
||||||
|
// Tokens
|
||||||
|
let token = (pattern: RegExp) => Parser.regex(pattern).bind((value: any) => ignored.and(Parser.constant(value)));
|
||||||
|
let FUNCTION = token(/function\b/y);
|
||||||
|
let IF = token(/if\b/y);
|
||||||
|
let ELSE = token(/else\b/y);
|
||||||
|
let RETURN = token(/return\b/y);
|
||||||
|
let ASSIGN = token(/=/y).map(_ => Assign);
|
||||||
|
let VAR = token(/var\b/y);
|
||||||
|
let WHILE = token(/while\b/y);
|
||||||
|
let COMMA = token(/[,]/y);
|
||||||
|
let SEMICOLON = token(/[;]/y);
|
||||||
|
let LEFT_PAREN = token(/[(]/y);
|
||||||
|
let RIGHT_PAREN = token(/[)]/y);
|
||||||
|
let LEFT_BRACE = token(/[{]/y);
|
||||||
|
let RIGHT_BRACE = token(/[}]/y);
|
||||||
|
let NUMBER = token(/[0-9]/y).map((digits: any) => new Num(parseInt(digits)));
|
||||||
|
let ID = token(/[a-zA-Z_][a-zA-Z0-9_]*/y).map((x: any) => new Id(x));
|
||||||
|
let NOT = token(/!/y).map(_ => Not);
|
||||||
|
let EQUAL = token(/==/y).map(_ => Equal);
|
||||||
|
let NOT_EQUAL = token(/!=/y).map(_ => NotEqual);
|
||||||
|
let PLUS = token(/[+]/y).map(_ => Add);
|
||||||
|
let MINUS = token(/[-]/y).map(_ => Subtract);
|
||||||
|
let STAR = token(/[*]/y).map(_ => Multiply);
|
||||||
|
let SLASH = token(/[/]/y).map(_ => Divide);
|
||||||
|
// Expression parser
|
||||||
|
let expression: Parser<AST> = Parser.error('expression parser used before definition');
|
||||||
|
// Call parser
|
||||||
|
let args: Parser<Array<AST>> = expression.bind((arg: any) => Parser.zeroOrMore(COMMA.and(expression)).bind((args: any) => Parser.constant([arg, ...args]))).or(Parser.constant([]));
|
||||||
|
let functionCall: Parser<AST> = ID.bind((callee: any) => LEFT_PAREN.and(args.bind((args: any) => RIGHT_PAREN.and(Parser.constant(callee.equals(new Id('assert')) ? new Assert(args[0]) : new FunctionCall(callee, args))))));
|
||||||
|
// Atom
|
||||||
|
let atom: Parser<AST> = functionCall.or(ID).or(NUMBER).or(LEFT_PAREN.and(expression).bind((e: any) => RIGHT_PAREN.and(Parser.constant(e))));
|
||||||
|
// Unary operators
|
||||||
|
let unary: Parser<AST> = Parser.optional(NOT).bind((not: any) => atom.map((operand: any) => not ? new Not(operand) : operand));
|
||||||
|
// Infix operators
|
||||||
|
let infix = (operatorParser: any, operandParser: any) =>
|
||||||
|
operandParser.bind((operand: any) =>
|
||||||
|
Parser.zeroOrMore(
|
||||||
|
operatorParser.bind((operator: any) =>
|
||||||
|
operandParser.bind((operand: any) =>
|
||||||
|
Parser.constant({ operator, operand })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).map((operatorTerms: any) =>
|
||||||
|
operatorTerms.reduce((left: any, { operator, operand }: { operator: any, operand: any }) =>
|
||||||
|
new operator(left, operand), operand)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let product = infix(STAR.or(SLASH), unary);
|
||||||
|
let sum = infix(PLUS.or(MINUS), product);
|
||||||
|
let comparison = infix(EQUAL.or(NOT_EQUAL), sum);
|
||||||
|
// Associativity
|
||||||
|
// Closing the loop: expression
|
||||||
|
expression.parse = comparison.parse;
|
||||||
|
// Statement
|
||||||
|
let statement: Parser<AST> = Parser.error('statement parser used before definition');
|
||||||
|
let returnStatement: Parser<AST> = RETURN.and(expression).bind((operand: any) => SEMICOLON.and(Parser.constant(new Return(operand))));
|
||||||
|
let expressionStatement: Parser<AST> = expression.bind((operand: any) => SEMICOLON.and(Parser.constant(operand)));
|
||||||
|
let ifStatement: Parser<AST> = IF.and(LEFT_PAREN).and(expression).bind((conditional: any) =>
|
||||||
|
RIGHT_PAREN.and(statement).bind((consequence: any) =>
|
||||||
|
ELSE.and(statement).bind((alternative: any) =>
|
||||||
|
Parser.constant(new If(conditional, consequence, alternative))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let whileStatement: Parser<AST> = WHILE.and(LEFT_PAREN).and(expression).bind((conditional: any) =>
|
||||||
|
RIGHT_PAREN.and(statement).bind((body: any) =>
|
||||||
|
Parser.constant(new While(conditional, body))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let varStatement: Parser<AST> = VAR.and(ID).bind((name: any) =>
|
||||||
|
ASSIGN.and(expression).bind((value: any) =>
|
||||||
|
SEMICOLON.and(Parser.constant(new Var(name, value)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let assignmentStatement: Parser<AST> = ID.bind((name: any) =>
|
||||||
|
ASSIGN.and(expression).bind((value: any) =>
|
||||||
|
SEMICOLON.and(Parser.constant(new Assign(name, value)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let blockStatement: Parser<AST> = LEFT_BRACE.and(Parser.zeroOrMore(statement)).bind((statements: any) =>
|
||||||
|
RIGHT_BRACE.and(Parser.constant(new Block(statements)))
|
||||||
|
);
|
||||||
|
let parameters: Parser<Array<string>> = ID.bind((param: any) =>
|
||||||
|
Parser.zeroOrMore(COMMA.and(ID)).bind((params: any) =>
|
||||||
|
Parser.constant([param, ...params])
|
||||||
|
)
|
||||||
|
).or(Parser.constant([]));
|
||||||
|
let functionStatement: Parser<AST> = FUNCTION.and(ID).bind((name: any) =>
|
||||||
|
LEFT_PAREN.and(parameters).bind((parameters: any) =>
|
||||||
|
RIGHT_PAREN.and(blockStatement).bind((block: any) =>
|
||||||
|
Parser.constant(name.equals(new Id('main')) ? new Main(block.statements) : new FunctionDefinition(name, parameters, block))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let statementParser: Parser<AST> =
|
||||||
|
returnStatement
|
||||||
|
.or(functionStatement)
|
||||||
|
.or(ifStatement)
|
||||||
|
.or(whileStatement)
|
||||||
|
.or(varStatement)
|
||||||
|
.or(assignmentStatement)
|
||||||
|
.or(blockStatement)
|
||||||
|
.or(expressionStatement);
|
||||||
|
statement.parse = statementParser.parse;
|
||||||
|
let parser: Parser<AST> = ignored.and(Parser.zeroOrMore(statement)).map((statements: any) => new Block(statements));
|
||||||
|
|
||||||
|
return parser.parseStringToCompletion(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Parser<T> {
|
||||||
|
constructor(public parse: (src: Source) => (ParseResult<T> | null)) {}
|
||||||
|
|
||||||
|
parseStringToCompletion(string: string): T {
|
||||||
|
let source = new Source(string, 0);
|
||||||
|
let result = this.parse(source);
|
||||||
|
if (!result)
|
||||||
|
throw Error('Parse error at index 0');
|
||||||
|
let index = result.source.index;
|
||||||
|
if (index != result.source.string.length)
|
||||||
|
throw Error(`Parse error at index ${index}`);
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static regex(regex: RegExp): Parser<string> {
|
||||||
|
return new Parser(source => source.match(regex));
|
||||||
|
}
|
||||||
|
static constant<U>(value: U): Parser<U> {
|
||||||
|
return new Parser(source => new ParseResult(value, source));
|
||||||
|
}
|
||||||
|
static error<U>(message: string): Parser<U> {
|
||||||
|
return new Parser(source => {
|
||||||
|
throw Error(message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
static zeroOrMore<U>(parser: Parser<U>): Parser<Array<U>> {
|
||||||
|
return new Parser(source => {
|
||||||
|
let results = [];
|
||||||
|
let item: any;
|
||||||
|
while (item = parser.parse(source)) {
|
||||||
|
source = item.source;
|
||||||
|
results.push(item.value);
|
||||||
|
}
|
||||||
|
return new ParseResult(results, source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
static optional<U>(parser: Parser<U>): Parser<U | null> {
|
||||||
|
return parser.or(Parser.constant(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
or(parser: Parser<T>): Parser<T> {
|
||||||
|
return new Parser(source => {
|
||||||
|
let result = this.parse(source);
|
||||||
|
return result ? result : parser.parse(source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bind<U>(callback: (value: T) => Parser<U>): Parser<U> {
|
||||||
|
return new Parser(source => {
|
||||||
|
let result = this.parse(source);
|
||||||
|
if (result) {
|
||||||
|
let value = result.value;
|
||||||
|
let source = result.source;
|
||||||
|
return callback(value).parse(source);
|
||||||
|
} else return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
and<U>(parser: Parser<U>): Parser<U> {
|
||||||
|
return this.bind(_ => parser);
|
||||||
|
}
|
||||||
|
map<U>(callback: (t: T) => U): Parser<U> {
|
||||||
|
return this.bind(value => Parser.constant(callback(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Source {
|
||||||
|
constructor(public string: string, public index: number) {}
|
||||||
|
|
||||||
|
match(regex: RegExp): (ParseResult<string> | null) {
|
||||||
|
regex.lastIndex = this.index;
|
||||||
|
let match = this.string.match(regex);
|
||||||
|
if (match) {
|
||||||
|
let value = match[0];
|
||||||
|
let newIndex = this.index + value.length;
|
||||||
|
let source = new Source(this.string, newIndex);
|
||||||
|
return new ParseResult(value, source);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ParseResult<T> {
|
||||||
|
constructor(public value: T, public source: Source) {}
|
||||||
|
}
|
159
src/tokenizer.rs
159
src/tokenizer.rs
@ -1,159 +0,0 @@
|
|||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum TokenKind {
|
|
||||||
Identifier,
|
|
||||||
StringLiteral,
|
|
||||||
IntLiteral,
|
|
||||||
FloatLiteral,
|
|
||||||
BooleanLiteral,
|
|
||||||
LeftParen,
|
|
||||||
RightParen,
|
|
||||||
Semicolon,
|
|
||||||
Comma,
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Token {
|
|
||||||
pub kind: TokenKind,
|
|
||||||
pub value: String,
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokenize(source: &str) -> Vec<Token> {
|
|
||||||
let chars: Vec<char> = source.chars().collect();
|
|
||||||
let mut tokens = Vec::new();
|
|
||||||
let mut current: usize = 0;
|
|
||||||
while let Some(c) = chars.get(current) {
|
|
||||||
if c.is_alphabetic() {
|
|
||||||
read_identifier(&chars, &mut current, &mut tokens);
|
|
||||||
} else if c.is_digit(10) {
|
|
||||||
read_number(&chars, &mut current, &mut tokens);
|
|
||||||
} else {
|
|
||||||
match c {
|
|
||||||
'\'' | '"' => read_string(&chars, &mut current, &mut tokens, c),
|
|
||||||
'(' => {
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: TokenKind::LeftParen,
|
|
||||||
value: "(".to_owned(),
|
|
||||||
index: current,
|
|
||||||
});
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
')' => {
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: TokenKind::RightParen,
|
|
||||||
value: ")".to_owned(),
|
|
||||||
index: current,
|
|
||||||
});
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
';' => {
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: TokenKind::Semicolon,
|
|
||||||
value: ";".to_owned(),
|
|
||||||
index: current,
|
|
||||||
});
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
',' => {
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: TokenKind::Comma,
|
|
||||||
value: ",".to_owned(),
|
|
||||||
index: current,
|
|
||||||
});
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
'/' => {
|
|
||||||
if chars.get(current + 1) == Some(&'/') {
|
|
||||||
// A "// ..." comment.
|
|
||||||
'comment: loop {
|
|
||||||
if chars.get(current) == Some(&'\n') || chars.get(current) == None {
|
|
||||||
break 'comment;
|
|
||||||
}
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => current += 1, // Just skip it if it's incorrect.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
fn read_identifier(chars: &Vec<char>, current: &mut usize, tokens: &mut Vec<Token>) {
|
|
||||||
let original_current = *current;
|
|
||||||
let mut identifier = String::new();
|
|
||||||
while let Some(c) = chars.get(*current) {
|
|
||||||
if c.is_alphabetic() {
|
|
||||||
identifier.push(*c);
|
|
||||||
*current += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut kind = TokenKind::Identifier;
|
|
||||||
if identifier == "true" || identifier == "false" {
|
|
||||||
kind = TokenKind::BooleanLiteral;
|
|
||||||
}
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: kind,
|
|
||||||
value: identifier,
|
|
||||||
index: original_current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn read_string(chars: &Vec<char>, current: &mut usize, tokens: &mut Vec<Token>, delimiter: &char) {
|
|
||||||
let original_current = *current;
|
|
||||||
let mut string = String::new();
|
|
||||||
*current += 1; // Move forward from the first delimiter.
|
|
||||||
while let Some(c) = chars.get(*current) {
|
|
||||||
if c == delimiter {
|
|
||||||
*current += 1;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
string.push(*c);
|
|
||||||
*current += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: TokenKind::StringLiteral,
|
|
||||||
value: string,
|
|
||||||
index: original_current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn read_number(chars: &Vec<char>, current: &mut usize, tokens: &mut Vec<Token>) {
|
|
||||||
let original_current = *current;
|
|
||||||
|
|
||||||
let mut kind = TokenKind::IntLiteral;
|
|
||||||
|
|
||||||
let mut num = String::new();
|
|
||||||
while let Some(c) = chars.get(*current) {
|
|
||||||
if c.is_digit(10) {
|
|
||||||
num.push(*c);
|
|
||||||
*current += 1;
|
|
||||||
} else if let Some(n) = chars.get(*current + 1) {
|
|
||||||
if *c == 'f' {
|
|
||||||
kind = TokenKind::FloatLiteral;
|
|
||||||
*current += 1;
|
|
||||||
break;
|
|
||||||
} else if *c == 'i' {
|
|
||||||
kind = TokenKind::IntLiteral;
|
|
||||||
*current += 1;
|
|
||||||
break;
|
|
||||||
} else if *c == '.' && n.is_digit(10) {
|
|
||||||
num.push(*c);
|
|
||||||
num.push(*n);
|
|
||||||
kind = TokenKind::FloatLiteral;
|
|
||||||
*current += 2;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens.push(Token {
|
|
||||||
kind: kind,
|
|
||||||
value: num,
|
|
||||||
index: original_current,
|
|
||||||
});
|
|
||||||
}
|
|
20
test.pvt
20
test.pvt
@ -1,19 +1 @@
|
|||||||
// A comment
|
let x = {2 + 2};
|
||||||
|
|
||||||
// An implicit int.
|
|
||||||
log(2);
|
|
||||||
// An explicit int.
|
|
||||||
log(5i);
|
|
||||||
|
|
||||||
// An implicit float.
|
|
||||||
log(2.5);
|
|
||||||
// An explicit float.
|
|
||||||
log(3f);
|
|
||||||
|
|
||||||
// Booleans and multiple arguments.
|
|
||||||
log(true, false);
|
|
||||||
|
|
||||||
// Strings.
|
|
||||||
log("Hello world!");
|
|
||||||
// A string showing how different delimiters layer.
|
|
||||||
log("What's that over there?");
|
|
||||||
|
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strictNullChecks": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user