diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index ed4741f..0000000 --- a/Cargo.lock +++ /dev/null @@ -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" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 61281f9..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "pivot" -version = "0.1.0" -authors = ["ElementG9 "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/errors.txt b/errors.txt new file mode 100644 index 0000000..e69de29 diff --git a/output.s b/output.s new file mode 100644 index 0000000..fa57278 --- /dev/null +++ b/output.s @@ -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} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1923719 --- /dev/null +++ b/package.json @@ -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" +} diff --git a/src/ast.ts b/src/ast.ts new file mode 100644 index 0000000..fb5c9ff --- /dev/null +++ b/src/ast.ts @@ -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) {} + + 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) {} + + 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, + 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); + } +} diff --git a/src/codegen.ts b/src/codegen.ts new file mode 100644 index 0000000..84202de --- /dev/null +++ b/src/codegen.ts @@ -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) {} + + 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); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..141f84b --- /dev/null +++ b/src/index.ts @@ -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(); diff --git a/src/interpreter.rs b/src/interpreter.rs deleted file mode 100644 index e7d5e2f..0000000 --- a/src/interpreter.rs +++ /dev/null @@ -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 = 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!"); - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 258eba4..0000000 --- a/src/lib.rs +++ /dev/null @@ -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); -} diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index ee6d8c1..0000000 --- a/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -fn main() { - let args = std::env::args().collect::>(); - if args.len() < 2 { - println!("Usage: pivot "); - } else { - pivot::interpret_file(&args[1]); - } -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 1a0d5bc..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,127 +0,0 @@ -use super::tokenizer::{Token, TokenKind}; - -pub fn parse(mut tokens: Vec) -> Program { - let mut stmts: Vec> = 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::>()); - 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) -> Statement { - if statement.len() == 1 { - // Must just be a semicolon. - return Statement::Nop; - } - let parse_function_call = |tokens: &Vec| -> Option { - // Check for ( ... ); - 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, 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::() - .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::() - .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; -#[derive(Debug, PartialEq)] -pub enum Statement { - FunctionCall { - name: String, - arguments: Vec, - }, - 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), -} diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..3670eae --- /dev/null +++ b/src/parser.ts @@ -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 = Parser.error('expression parser used before definition'); + // Call parser + let args: Parser> = expression.bind((arg: any) => Parser.zeroOrMore(COMMA.and(expression)).bind((args: any) => Parser.constant([arg, ...args]))).or(Parser.constant([])); + let functionCall: Parser = 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 = functionCall.or(ID).or(NUMBER).or(LEFT_PAREN.and(expression).bind((e: any) => RIGHT_PAREN.and(Parser.constant(e)))); + // Unary operators + let unary: Parser = 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 = Parser.error('statement parser used before definition'); + let returnStatement: Parser = RETURN.and(expression).bind((operand: any) => SEMICOLON.and(Parser.constant(new Return(operand)))); + let expressionStatement: Parser = expression.bind((operand: any) => SEMICOLON.and(Parser.constant(operand))); + let ifStatement: Parser = 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 = 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 = VAR.and(ID).bind((name: any) => + ASSIGN.and(expression).bind((value: any) => + SEMICOLON.and(Parser.constant(new Var(name, value))) + ) + ); + let assignmentStatement: Parser = ID.bind((name: any) => + ASSIGN.and(expression).bind((value: any) => + SEMICOLON.and(Parser.constant(new Assign(name, value))) + ) + ); + let blockStatement: Parser = LEFT_BRACE.and(Parser.zeroOrMore(statement)).bind((statements: any) => + RIGHT_BRACE.and(Parser.constant(new Block(statements))) + ); + let parameters: Parser> = ID.bind((param: any) => + Parser.zeroOrMore(COMMA.and(ID)).bind((params: any) => + Parser.constant([param, ...params]) + ) + ).or(Parser.constant([])); + let functionStatement: Parser = 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 = + returnStatement + .or(functionStatement) + .or(ifStatement) + .or(whileStatement) + .or(varStatement) + .or(assignmentStatement) + .or(blockStatement) + .or(expressionStatement); + statement.parse = statementParser.parse; + let parser: Parser = ignored.and(Parser.zeroOrMore(statement)).map((statements: any) => new Block(statements)); + + return parser.parseStringToCompletion(source); +} + +export class Parser { + constructor(public parse: (src: Source) => (ParseResult | 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 { + return new Parser(source => source.match(regex)); + } + static constant(value: U): Parser { + return new Parser(source => new ParseResult(value, source)); + } + static error(message: string): Parser { + return new Parser(source => { + throw Error(message); + }) + } + static zeroOrMore(parser: Parser): Parser> { + 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(parser: Parser): Parser { + return parser.or(Parser.constant(null)); + } + + or(parser: Parser): Parser { + return new Parser(source => { + let result = this.parse(source); + return result ? result : parser.parse(source); + }); + } + bind(callback: (value: T) => Parser): Parser { + 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(parser: Parser): Parser { + return this.bind(_ => parser); + } + map(callback: (t: T) => U): Parser { + return this.bind(value => Parser.constant(callback(value))) + } +} +export class Source { + constructor(public string: string, public index: number) {} + + match(regex: RegExp): (ParseResult | 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 { + constructor(public value: T, public source: Source) {} +} diff --git a/src/tokenizer.rs b/src/tokenizer.rs deleted file mode 100644 index 20446bc..0000000 --- a/src/tokenizer.rs +++ /dev/null @@ -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 { - let chars: Vec = 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, current: &mut usize, tokens: &mut Vec) { - 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, current: &mut usize, tokens: &mut Vec, 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, current: &mut usize, tokens: &mut Vec) { - 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, - }); -} diff --git a/test.pvt b/test.pvt index 6f82562..114a326 100644 --- a/test.pvt +++ b/test.pvt @@ -1,19 +1 @@ -// A comment - -// 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?"); +let x = {2 + 2}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..217a628 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "strictNullChecks": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}