Skip to content
/ brs Public
forked from sjbarag/brs

An interpreter for the BrightScript language that runs on non-Roku platforms.

License

Notifications You must be signed in to change notification settings

lkipke/brs

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of Contents generated with DocToc

BRS: Off-Roku BrightScript

An interpreter for the BrightScript language that runs on non-Roku platforms.

CircleCI NPM Version

Installation

The BRS project is published as a node package, so use npm:

$ npm install -g brs

or yarn if that's your preference:

$ yarn global add brs

Usage

This repo provides the brs executable, which operates in two ways.

REPL

An interactive BrightScript REPL (Read-Execute-Print Loop) is available by running brs with no arguments, e.g.:

$ brs
brs> ?"Dennis Ritchie said ""Hello, World!"""
Dennis Ritchie said "Hello, World!"

Quit by entering ^D (Control-D).

Executing a file

BRS can execute an arbitrary BrightScript file as well! Simply pass the file to the brs executable, e.g.:

$ cat hello-world.brs
?"Dennis Ritchie said ""Hello, World!"""

$ brs hello-world.brs
Dennis Ritchie said "Hello, World!"

Sure, but why?

The Roku series of media streaming devices are wildly popular amongst consumers, and several very popular streaming services offer Channels for the Roku platform. Unfortunately, Roku chanels must be written in a language called BrightScript, which is only executable directly on a Roku device. BRS hopes to change that by allowing Roku developers to test their code on their own machines, thus improving the quality of their channels and the end-user's experience as a whole.

So can I use this to watch TV without a Roku?

Nope! The BRS project currently has no intention of emulating the Roku user interface, integrating with the Roku store, or emulating content playback. In addition to likely getting this project in legal trouble, that sort of emulation is a ton of work. BRS isn't mature enough to be able to sustain that yet.

Building from source

The BRS project follows pretty standard node development patterns, with the caveat that it uses yarn for dependency management.

Prerequisites

BRS builds (and runs) in node, so you'll need to install that first.

Once that's ready, install yarn. Installing it with npm is probably the simplest:

$ npm install -g yarn

Setup

  1. Clone this repo:

    $ git clone https://github.com/sjbarag/brs.git
    
  2. Install dependencies:

    $ yarn install     # or just `yarn`
  3. Get brs onto your PATH:

    $ yarn link

The build-test-clean dance

Build

This project is written in TypeScript, so it needs to be compiled before it can be executed. yarn build compiles files in src/ into JavaScript and TypeScript declarations, and puts them in lib/ and types/ respectively.

$ yarn build

$ ls lib/
index.js (and friends)

$ ls types/
index.d.ts (and friends)

Alternatively, you can run the build step in "watch" mode. This will run yarn build for you automatically, every time it detects source file changes:

$ yarn watch

This is often useful for testing that local changes work in your BrightScript project, without having to run yarn build over and over.

Testing

Tests are written in plain-old JavaScript with Facebook's Jest, and can be run with the test target:

$ yarn test

# tests start running

Note that only test files ending in .test.js will be executed by yarn test.

Cleaning

Compiled output in lib/ and types/ can be removed with the clean target:

$ yarn clean

$ ls lib/
ls: cannot access 'lib': No such file or directory

$ ls types/
ls: cannot access 'types': No such file or directory

All Together

Thanks to the npm-run-all package, it's trivially easy to combine these into a sequence of tasks without relying on shell semantics:

$ yarn run-s clean build test

Extensions

For the most part, brs attempts to emulate BrightScript as closely as possible. However, in the spirit of unit testing, it also has a few extensions that will help with testing.

_brs_.process

Allows you to access the command line arguments. Usage:

print _brs_.process
' {
'   argv: [ "some", "arg" ]
' }

_brs_.global

Allows you to access m.global from inside your unit tests. Usage:

print _brs_.global
' {
'   someGlobalField: "someGlobalValue"
' }

_brs_.runInScope

Runs a file (or set of files) in the current global + module scope with the provided arguments, returning either the value returned by those files' main function or invalid if an error occurs.

_brs_.runInScope("/path/to/myFile.brs", { foo: "bar" })

_brs_.mockComponent

Allows you to mock a component. Usage:

_brs_.mockComponent("ComponentName", {
    someField: "foobar",
    someFunc: sub()
      return 123
    end sub
})

_brs_.mockFunction

Allows you to mock a function. It also returns an associative array mock function (also known as a "spy" or "stub") that keeps track of calls and return values. Usage:

mock = _brs_.mockFunction("FunctionName", sub()
    print "foobar"
end sub)

Mock Function API

The Mock Function API is modeled after Jest's mockFn API. Methods:

mock.calls

An array containing the arguments of each call to this function. Each item in the array is an array of the arguments for that call.

For example:

mock = _brs_.mockFunction("fooBar", function(arg1 as string, arg2 as integer)
    print "fooBar"
end function)

fooBar("baz", 123)
print mock.calls
' [
'     ["baz", 123]
' ]

fooBar("lorem", 456)
print mock.calls
' [
'     ["baz", 123]
'     ["lorem", 456]
' ]
mock.results

An array containing the return value for each call to this function. For example:

mock = _brs_.mockFunction("fooBar", function(arg as boolean)
    if arg then return "foo" : else return "bar" : end if
end function)

fooBar(true)
print mock.results ' => [ "foo" ]

fooBar(false)
print mock.results ' => [ "foo", "bar" ]
mock.clearMock()

Clears the calls and results arrays. Does not affect the mock implementation.

mock = _brs_.mockFunction("fooBar", function()
    return "hello, world"
end function)

fooBar()
print mock.calls.count()   ' => 1
print mock.results.count() ' => 1

mock.clearMock()
print mock.calls.count()   ' => 0
print mock.results.count() ' => 0
mock.getMockName()

Returns the name of the mocked function.

mock = _brs_.mockFunction("fooBar", function()
    return "hello, world"
end function)

print mock.getMockName() ' => "fooBar"

_brs_.resetMocks

Resets all component and function mocks. Usage:

_brs_.resetMocks()

_brs_.resetMockComponents

Resets all component mocks. Usage:

_brs_.resetMockComponents()

_brs_.resetMockComponent

Resets a specific component mock. Works on both partially mocked and fully mocked components. Usage:

_brs_.resetMockComponent("MyComponent")

_brs_.resetMockFunctions

Resets all function mocks. Usage:

_brs_.resetMockFunctions()

_brs_.resetMockFunction

Resets a specific function mock. Usage:

_brs_.resetMockFunction("MyFunction")

Note: If you have a mocked component that has a function with the same name, this will not reset that component member function. For example:

_brs_.mockComponent("Component", {
    foo: sub()
        print "mock component foo"
    end sub
})
_brs_.mockFunction("foo", sub()
    print "mock global foo"
end sub)

node = createObject("roSGNode", "Component")
foo() ' => "mock global foo"
node.foo() ' => "mock component foo"

_brs_.resetMockFunction("foo")
foo() ' => "original implementation"
node.foo() ' => "mock component foo"

_brs_.triggerKeyEvent(key as string, press as boolean)

This will call onKeyEvent handlers up the focus chain until the event is handled. If there is no focused component, nothing will happen (which is how RBI behaves). Usage:

_brs_.triggerKeyEvent("OK", true)

About

An interpreter for the BrightScript language that runs on non-Roku platforms.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 57.6%
  • TypeScript 39.2%
  • Brightscript 3.2%