Skip to content

Commit

Permalink
Started python transpiler, cleaned up parser code. Added LL example
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Berezin committed Jun 11, 2019
1 parent 518749f commit 53e237e
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 49 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.linting.pylintEnabled": false
}
5 changes: 4 additions & 1 deletion browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const exampleProgramSource = [
readFileSync(__dirname + '/examples/interactiveFibonacci.lox', 'utf-8'),
readFileSync(__dirname + '/examples/closureLinkedList.lox', 'utf-8'),
readFileSync(__dirname + '/examples/kitchenSink.lox', 'utf-8'),
readFileSync(__dirname + '/examples/linkedList.lox', 'utf-8'),
readFileSync(__dirname + '/examples/classExample.lox', 'utf-8')
]
const examplePrograms = exampleProgramSource.map(program => {
Expand Down Expand Up @@ -94,7 +95,9 @@ button.onclick = () => {
const formatButton = document.getElementById('format')
formatButton.onclick = () => {
try {
code.value = parse(code.value)
const ast = parse(code.value)
global.ast = ast
code.value = ast
.map(stmt => printLoxAST(stmt))
.join('\n')
handleError(null)
Expand Down
9 changes: 6 additions & 3 deletions examples/kitchenSink.lox
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ fun hello (arg1, arg2) {
var maxAge = 18;
var age;
age = maxAge + 3;
{
print 23;
}
if (age < 21) {
alert("Too young");
print "Too young";
while(age < 21) {
age = age + 1;
}
Expand All @@ -23,6 +26,6 @@ fun hello (arg1, arg2) {


print "hello source: ";
print printFunctionBody(hello);
// print printFunctionBody(hello);

// hello("boss", "man");
hello("boss", "man");
70 changes: 70 additions & 0 deletions lox2python.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#! /usr/bin/env node
const fs = require('fs')
const chalk = require('chalk')
const { parse } = require('./index')
const { loxToPython2 } = require('./transpilers/python')
const { formatLoxError } = require('./errors')

let options = {}

const printErrorMessage = (e, code) => {
const { oneLiner, preErrorSection, errorSection, postErrorSection } = formatLoxError(e, code)
console.error(oneLiner)
if (errorSection) {
console.error(preErrorSection + chalk.bgRed(errorSection) + postErrorSection)
}
}

const fmtFile = (filename, outputfile = 'a.py') => {
try {
const file = fs.readFileSync(filename, 'utf8')
try {
const newText = parse(file)
.map(stmt => loxToPython2(stmt, 0, options))
.join('\n')
if (!options.silent) console.log(newText)
if (options.write) fs.writeFileSync(outputfile, newText)
} catch (e) {
printErrorMessage(e, file)
}
} catch (e) {
console.error(`YALI could not read the file ${filename}`)
console.error(e)
}
}

const optionRegex = /--(\w+)(?:=(.+))?/
const processOptions = args =>
args
.map(arg => {
const match = optionRegex.exec(arg)
if (match) {
const [, option, value] = match
if (!value) {
options[option] = !options[option]
} else {
options[option] = value
}
} else {
return arg
}
})
.filter(Boolean)

const main = argv => {
process.title = 'lox2python'
const args = processOptions(argv.slice(2))
if (options.help) {
console.log('Usage: lox2python [script] --out="a.py"')
return 0
}

if (args.length === 1) {
fmtFile(args[0])
} else {
console.error('Usage: lox2python [script]')
return 64
}
}

main(process.argv)
90 changes: 45 additions & 45 deletions parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ class Parser {
this.current = 0
}

expression() {
return this.assignment()
}

parse() {
let statements = []
while (!this.isAtEnd) {
Expand Down Expand Up @@ -106,41 +102,6 @@ class Parser {
return this.expressionStatement()
}

returnStatement() {
const prev = this.previous()
let value = null
if (!this.check(token.SEMICOLON)) {
value = this.expression()
}
this.consume(token.SEMICOLON, 'Expected ";" after return value')
return new Return(prev, value)
}

assignment() {
const expr = this.or()
if (this.match(token.EQUAL)) {
const equalToken = this.previous()
const value = this.assignment()
if (expr instanceof Var) {
const nameToken = expr.name
return new Assignment(nameToken, value)
} else if (expr instanceof Get) {
return new Set(expr.object, expr.name, value)
}
throw ParseError('Expected Expression', equalToken)
}

return expr
}

or() {
return this.matchBinary('and', Logical, token.OR)
}

and() {
return this.matchBinary('equality', Logical, token.AND)
}

forStatement() {
this.consume(token.LEFT_PAREN, 'Expected "(" after "for"')

Expand Down Expand Up @@ -206,16 +167,56 @@ class Parser {
return statements
}

expressionStatement() {
printStatement() {
const val = this.expression()
this.consume(token.SEMICOLON, 'Expect ; after value.')
return new ExpressionStatement(val)
return new PrintStatement(val)
}

printStatement() {
returnStatement() {
const prev = this.previous()
let value = null
if (!this.check(token.SEMICOLON)) {
value = this.expression()
}
this.consume(token.SEMICOLON, 'Expected ";" after return value')
return new Return(prev, value)
}


expressionStatement() {
const val = this.expression()
this.consume(token.SEMICOLON, 'Expect ; after value.')
return new PrintStatement(val)
return new ExpressionStatement(val)
}

expression() {
return this.assignment()
}

assignment() {
const expr = this.or()
if (this.match(token.EQUAL)) {
const equalToken = this.previous()
const value = this.assignment()
if (expr instanceof Var) {
const nameToken = expr.name
return new Assignment(nameToken, value)
} else if (expr instanceof Get) {
return new Set(expr.object, expr.name, value)
}
throw ParseError('Expected Expression', equalToken)
}

return expr
}

or() {
return this.matchBinary('and', Logical, token.OR)
}

and() {
return this.matchBinary('equality', Logical, token.AND)
}

matchBinary(method, Class, ...operators) {
Expand Down Expand Up @@ -262,8 +263,7 @@ class Parser {

call() {
let expr = this.primary()
while (true) {
//eslint-disable-line
while (true) { //eslint-disable-line
if (this.match(token.LEFT_PAREN)) {
expr = this.finishCall(expr)
} else if (this.match(token.DOT)) {
Expand Down
131 changes: 131 additions & 0 deletions transpilers/python.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const {
Binary,
Unary,
Var,
Call,
Literal,
While,
// @TODO: Support Classes
// Class,
// Get,
// Set,
Grouping,
Return,
LoxFunction,
PrintStatement,
ExpressionStatement,
VarStatement,
Assignment,
Logical,
Block,
Condition
} = require('../types')

// const condChar = (condition, replacer = ' ') => condition ? replacer : ''

const ASTNodeMap = new Map()

// Declarations
ASTNodeMap.set(ExpressionStatement, (node, scope, options, initialIndent) => {
// console.log(initialIndent)
return loxToPython2(node.expression, 0, options, initialIndent)
})

ASTNodeMap.set(PrintStatement, node => 'print ' + loxToPython2(node.expression))

ASTNodeMap.set(Return, node => 'return ' + loxToPython2(node.value))

ASTNodeMap.set(VarStatement, (node) => {
const name = node.name.lexeme
const initializer = node.initializer ? loxToPython2(node.initializer) : null
// Python doesn't have plain declarations...
if (!node.initializer) return ''
return `${name}` + (initializer ? ` = ${initializer}` : '')
})

ASTNodeMap.set(Condition, ({ condition, thenBranch, elseBranch }, scope, options) => {
const cond = loxToPython2(condition)
const conditionSection = `if ${cond}:\n`
const thenSection = loxToPython2(thenBranch, scope + 1, options, false)
const elseSection = elseBranch && loxToPython2(elseBranch, scope + 1, options, false)
return conditionSection + thenSection + (elseSection ? `\n${options.indent.repeat(scope)}else:\n${elseSection}` : '')
})

ASTNodeMap.set(LoxFunction, ({ bodyStatements: body, name: { lexeme: name }, params}, scope, options) => {
const parameters = params.map(token => token.lexeme)
const head = `def ${name}(${parameters.join(', ')}):`
const fnBody = body.map(stmt => loxToPython2(stmt, scope + 1, options))
// console.log(fnBody)
return [head, ...fnBody].join('\n')
})

ASTNodeMap.set(While, ({ body, condition }, scope, options) => {
const cond = loxToPython2(condition)
const conditionSection = `while ${cond}:\n`
const bodySection = loxToPython2(body, scope + 1, options, false)
return conditionSection + bodySection
})

ASTNodeMap.set(Block, ({ statements }, scope, options, initialIndent) => statements.map(stmt => loxToPython2(stmt, scope, options, initialIndent)).join('\n'))

// Expressions
ASTNodeMap.set(Var, ({ name: { lexeme } }) => lexeme)

ASTNodeMap.set(Grouping, ({ expression }) => '(' + loxToPython2(expression) + ')')


const handleBinary = node => {
const left = loxToPython2(node.left)
const operator = node.operator.lexeme
const right = loxToPython2(node.right)
return [left, operator, right].join(' ')
}

ASTNodeMap.set(Binary, handleBinary)

ASTNodeMap.set(Logical, handleBinary)

ASTNodeMap.set(Unary, node => {
const operator = node.operator.lexeme
const right = loxToPython2(node.right)
return operator + right
})


ASTNodeMap.set(Call, node => {
const args = node.arguments.map(args => loxToPython2(args)).join(', ')
const callee = loxToPython2(node.callee)
return `${callee}(${args})`
})

ASTNodeMap.set(Assignment, node => {
const name = node.name.lexeme
const value = loxToPython2(node.value)
return name + ' = ' + value
})

ASTNodeMap.set(Literal, ({ value }) => {
if (typeof value === 'string') {
return `"${value}"`
} else if (value === null) {
return 'nil'
} else {
return value
}
})

const loxToPython2 = (node, scope = 0, optionsOverride = {}, initialIndent = true) => {
const options = Object.assign({}, {
indent: ' ',
}, optionsOverride)

if (ASTNodeMap.has(node.constructor)) {
const indentation = (initialIndent ? options.indent.repeat(scope) : '')
return indentation + ASTNodeMap.get(node.constructor)(node, scope, options)
}
throw new Error(`Don't support that context yet`, node.constructor)
}

module.exports = {
loxToPython2
}

0 comments on commit 53e237e

Please sign in to comment.