Skip to content

Swap out Brython for RustPython #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"lodash": "^4.17.11",
"marked": "^0.5.0",
"path-to-regexp": "^3.0.0",
"rustpython_wasm": "^0.1.0-pre-alpha.1",
"vue": "^2.5.17",
"vue-codemirror": "^4.0.5",
"vue-router": "^3.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/nico/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"rustpython_wasm": "0.1.0-pre-alpha.1",
"vue": "^2.5.17",
"vue-codemirror": "^4.0.5",
"vuex": "^3.0.1"
Expand Down
20 changes: 13 additions & 7 deletions src/nico/src/nico/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import _ from 'lodash/fp'
export const FUNCTIONS = [
{
name: 'User',
description: 'You define these functions to tell nico how to run your game.',
description:
'You define these functions to tell nico how to run your game.',
functions: [
{
name: 'init',
parameters: [],
description: 'Contains any code that you should run in the very beginning.',
description:
'Contains any code that you should run in the very beginning.',
},
{
name: 'update',
Expand Down Expand Up @@ -83,7 +85,8 @@ export const FUNCTIONS = [
{
name: 'keyPressed',
parameters: ['key'],
description: 'Returns whether a key was pressed and then immediately released.',
description:
'Returns whether a key was pressed and then immediately released.',
},
],
},
Expand All @@ -110,7 +113,8 @@ export const FUNCTIONS = [
{
name: 'buttonPressed',
parameters: ['button'],
description: 'Returns whether a button was pressed and then immediately released.',
description:
'Returns whether a button was pressed and then immediately released.',
},
],
},
Expand All @@ -121,12 +125,14 @@ export const FUNCTIONS = [
{
name: 'hasFlag',
parameters: ['flag', 'x', 'y'],
description: 'Returns whether a tile has the flag. Flags are indexed starting with 0.',
description:
'Returns whether a tile has the flag. Flags are indexed starting with 0.',
},
{
name: 'getTile',
parameters: ['x', 'y'],
description: 'Returns the index of the sprite of a tile given its coordinates.',
description:
'Returns the index of the sprite of a tile given its coordinates.',
},
{
name: 'changeTile',
Expand All @@ -140,6 +146,6 @@ export const FUNCTIONS = [

export const FUNCTIONS_BARE =
FUNCTIONS |>
_.filter((section) => section.name !== 'User') |>
_.filter(section => section.name !== 'User') |>
_.flatMap('functions') |>
_.map('name')
19 changes: 0 additions & 19 deletions src/nico/src/nico/languages/Lang.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
export default class Lang {
needsLoading = false

constructor (mars) {
this.mars = mars
}

cleanup () {}

convertWindowError ({ lineno: lineNumber, colno: columnNumber, ...other }, OFFSET = 0) {
return Object.freeze({
...other,

from: {
line: lineNumber - 1 - OFFSET,
ch: columnNumber - 1,
},
to: {
line: lineNumber - 1 - OFFSET,
ch: columnNumber,
},
})
}
}
137 changes: 40 additions & 97 deletions src/nico/src/nico/languages/Python.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@ import _ from 'lodash/fp'
import Lang from './Lang'
import { FUNCTIONS_BARE } from '../constants.js'

function ifDoesntExist (id, callback) {
if (!document.querySelector(id)) callback()
}

function loadScript (id, source, onLoad) {
const script = document.createElement('script')

script.src = source
script.type = 'text/javascript'
script.id = id
script.setAttribute('crossorigin', 'anonymous')
script.onload = onLoad
document.querySelector('head').appendChild(script)
}
let rustpython, rpProm

export default class Python extends Lang {
name = 'Python'
Expand All @@ -32,110 +19,66 @@ def draw():
pass
`

// prettier-ignore
PYTHON_TEMPLATE = `
from browser import window
` + FUNCTIONS_BARE.map(funcName => `
def ${_.snakeCase(funcName)}(*args):
return window.${funcName}(*args)
`).join('\n')
constructor (onLoad) {
super()

PYTHON_TEMPLATE_LENGTH = this.PYTHON_TEMPLATE.split('\n').length

constructor (mars, onLoad) {
super(mars)

if (onLoad) {
ifDoesntExist('python', () => {
loadScript('python', 'https://s3.us-east-2.amazonaws.com/chicode/brython.js', onLoad)
if (onLoad && !rustpython) {
rpProm = import('rustpython_wasm').then(rp => {
rustpython = rp
})
rpProm.then(onLoad)
}
}

transformPython (code) {
return this.PYTHON_TEMPLATE + code
}

transformJS (code, scriptId) {
return `
var $locals_${scriptId} = {}
${code};

const init = $locals___main__.init || (() => {})
const update = $locals___main__.update || (() => {})
const draw = $locals___main__.draw

${this.mars}
`
}

convertWindowError ({ error, ...other }) {
return this.convertError(error)
}

convertError (error) {
// TODO find a way to get the location of runtime errors
// https://github.com/brython-dev/brython/issues/938
console.log('RUNTIME ERROR:')
console.dir(error)
return {
message: `${error.args[0]}`,
}
}

convertSyntaxError (e) {
return {
message: `${e.msg}: ${e.text.trim()}`,
from: {
line: e.lineno - this.PYTHON_TEMPLATE_LENGTH,
ch: e.offset - 1,
},
to: {
line: e.lineno - this.PYTHON_TEMPLATE_LENGTH,
ch: e.offset,
},
}
}

async prepareCode (code) {
async refresh (code, mars) {
// https://github.com/brython-dev/brython/issues/937

const $B = window.__BRYTHON__
if (!$B) {
throw new Error('Brython is not loaded!')
}
await rpProm

$B.meta_path = $B.$meta_path.slice()
if (!$B.use_VFS) {
$B.meta_path.shift()
}
// clear previous state
rustpython.vmStore.destroy('mars')
const vm = rustpython.vmStore.init('mars', false)

const scriptId = '__main__'
for (const func of FUNCTIONS_BARE) {
vm.addToScope(this.translateName(func), mars[func])
}

let py = this.transformPython(code)
// console.log('PYTHON CODE:\n', py)
vm.setStdout()

let js
try {
js = $B.py2js(py, scriptId, scriptId).to_js()
} catch (e) {
// console.dir(e)
vm.exec(code)
} catch (err) {
console.error(err)
return {
success: false,
errors: [this.convertSyntaxError(e)],
errors: [err],
}
}

js = this.transformJS(js, scriptId)
// console.log('JS CODE:\n', js)

return {
const ret = {
success: true,
code: js,
}
for (const fn of ['init', 'draw', 'update']) {
try {
ret[fn] = vm.eval(fn) || (() => {})
} catch (_) {
ret[fn] = () => {}
}
}
return ret
}

translateName (name) {
return _.snakeCase(name)
}

getSyntax ({ name, parameters }) {
return `${_.snakeCase(name)}(${parameters |> _.map(_.snakeCase) |> _.join(', ')})`
return `${this.translateName(name)}(${parameters |>
_.map(this.translateName) |>
_.join(', ')})`
}

transformError (err) {
return { message: err }
}
}
Loading