Skip to content
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

Add imports #314

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 5 additions & 2 deletions satriani/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ Satriani is a JavaScript interpreter for the Rockstar programming language. Satr
## Usage

To run Satriani using nodeJS from the command line:
```
```bash
git clone https://github.com/RockstarLang/rockstar
cd rockstar/satriani
# If yarn is not available:
# npm install yarn
# node_modules/yarn/bin/yarn install
yarn install
yarn pegjs
node rockstar <program>.rock
Expand Down Expand Up @@ -58,7 +61,7 @@ you can use in web pages:
<html>
<body>

<script type="text/javascript" src="js/satriani.standalone.js"></script>
<script type="text/javascript" src="satriani.standalone.js"></script>
<script type="text/javascript">
let source = 'Shout "Hello World"';
let output = console.log;
Expand Down
2 changes: 1 addition & 1 deletion satriani/rockstar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function execute() {
let tree = rockstar.parse(data);
let input = readlineSync.question;
let output = console.log;
let result = rockstar.run(tree, input, output)
let result = rockstar.run(tree, input, output, sourceFilePath);
console.log(result ? result : "(program returned no output)");
} catch (e) {
if (e.location && e.location.start) {
Expand Down
54 changes: 51 additions & 3 deletions satriani/rockstar.peg
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ After updating, run pegjs -o rockstar-parser.js rockstar.peg
const keywords = new Set([
// common variable prefixes
'a', 'an', 'the', 'my', 'your', 'our',
// system variable prefix
'thy',

// pronouns
'it', 'he', 'she', 'him', 'her', 'they', 'them', 'ze', 'hir', 'zie', 'zir', 'xe', 'xem', 've', 'ver',
Expand Down Expand Up @@ -62,6 +64,15 @@ After updating, run pegjs -o rockstar-parser.js rockstar.peg
'takes', 'wants',
'give', 'return', 'send', 'back',
'taking',

// imports
'give', 'a', 'hand', 'for',
'featuring',
'welcome', 'to',
'quoting',
'contains', 'a', 'sample', 'of',
'as', 'aka', 'by',
'from', 'courtesy', 'of',
])

function isKeyword(string) {
Expand All @@ -85,7 +96,7 @@ EOF = !.

ignore_rest_of_line = (_[^\n]*)?

statement = _* s:(break / continue / function / function_call
statement = _* s:(break / continue / function / function_call / import
/ function_return / loop / conditional / operation / expression) { return s }

break = 'break'i ignore_rest_of_line {
Expand Down Expand Up @@ -302,7 +313,7 @@ pronoun = pronoun:(
) &(is / _ / EOL / EOF)
{ return { pronoun: pronoun.toLowerCase() } }

common_prefix = ( 'an'i / 'a'i / 'the'i / 'my'i / 'your'i / 'our'i)
common_prefix = ( 'an'i / 'a'i / 'the'i / 'my'i / 'your'i / 'our'i / 'thy'i )

uppercase_letter = [A-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĸĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽ]
lowercase_letter = [a-zàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷÿźżžʼnß]
Expand Down Expand Up @@ -467,4 +478,41 @@ math_round
= 'turn'i _ v:variable _ ('round'i/'around'i)
{ return { rounding: { variable: v, direction: 'nearest' } }; }
/ 'turn'i _ ('round'i/'around'i) _ v:variable
{ return { rounding: { variable: v, direction: 'nearest' } }; }
{ return { rounding: { variable: v, direction: 'nearest' } }; }


import_aliases = 'Give a hand for'i
/ 'Featuring'i
/ 'Welcome to'i
/ 'Quoting'i
/ 'Contains a sample of'i

import_as = 'as'i
/ 'aka'i
/ 'by'i

import_from = 'from'i
/ 'of'i
/ 'courtesy of'i

import
= import_aliases _ ul:util_list _ import_from _ f:library
{ return { import_utils : {utils : ul, library : f} } }
/ import_aliases _ fl:library_list
{ return { import_libraries : {libraries : fl} } }

util
= u:variable _ import_as _ v:variable
{ return { name : u, new_name : v } }
/ u:variable
{ return { name : u, new_name : u } }

util_list = head:util variable_list_separator tail:util_list
{ return [head].concat(tail) }
/ arg:util { return [arg] }

library_list = head:library variable_list_separator tail:library_list
{ return [head].concat(tail) }
/ arg:library { return [arg] }

library = v:variable { return { name : v } }
81 changes: 81 additions & 0 deletions satriani/satriani.interpreter.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
const fs = require('fs');
const path = require('path');
const parser = require('./satriani.parser.js');

module.exports = {
Environment: Environment,
eq: eq
Expand Down Expand Up @@ -50,6 +54,10 @@ Environment.prototype = {
},

run: function (program) {
if (!this.exists("thy_name")) {
// This script is not an import; it's executed directly.
this.assign("thy_name", "", null, 1);
}
let result = evaluate(program, this);
return (result ? result.value : undefined);
},
Expand Down Expand Up @@ -209,6 +217,33 @@ function evaluate(tree, env) {
return enlist(expr, env);
case "delist":
return delist(expr, env);
case "import_utils":
// Load library and inject requested symbols in the 'local=1' environment
let includeEnv = include(expr.library.name, env);
for (i = 0; i < expr.utils.length; ++i) {
let util = expr.utils[i];
if (util.name.startsWith("thy_")) {
throw ("System variables won't be imported: '"+util.name+"'");
}
if (includeEnv.exists(util.name)) {
env.assign(util.new_name, includeEnv.vars[util.name], null, 1);
} else {
throw ("The library '"+expr.library.name+"' does not contain the symbol '"+util.name+"'");
}
}
return;
case "import_libraries":
for (i = 0; i < expr.libraries.length; ++i) {
let library = expr.libraries[i];
// Load each library and inject all symbols in the 'local=1' environment
let includeEnv = include(library.name, env);
for (const [k, v] of Object.entries(includeEnv.vars)) {
if (!k.startsWith("thy_")) {// Do not retrieve system variables
env.assign(k, v, null, 1);
}
}
}
return;
default:
if (Array.isArray(tree) && tree.length == 1) return (evaluate(tree[0], env));
throw new Error("Sorry - I don't know how to evaluate this: " + JSON.stringify(tree))
Expand Down Expand Up @@ -438,3 +473,49 @@ function multiply_string(s, n) {
while (--n >= 0) result.push(s);
return (result.join(''));
}

function include(name, env) {
let includeEnv = new Environment();
let has_fs = (typeof fs !== 'undefined' && typeof fs.readdirSync === 'function' && typeof fs.readFileSync === 'function');
if (!has_fs) {
console.warn("Dependencies not found to perform the import of '"+name+".rock'");
// We ignore the missing includes in web-browser mode.
return includeEnv;
}
let filename = resolve_include_file(name, env);
// Parse the file
let data = fs.readFileSync(filename, 'utf8');
let ast = parser.parse(data);
// Interpret the AST
includeEnv.input = env.input;
includeEnv.output = env.output;
includeEnv.assign("thy_location", filename, null, 1);
includeEnv.assign("thy_name", name, null, 1);
includeEnv.run(ast);// We don't care about a return value.
return includeEnv;
}

function resolve_include_file(name, env) {
let currentRockFilename = null;
if (env.exists("thy_location")) {
// Each file must know its location, to find it's relative rock libraries
currentRockFilename = env.vars["thy_location"];
} else {
// Best effort: including in the current path will probably fail
currentRockFilename = path.join(thy_dirname, "anonymous.rock");
}
let currentRockDirname = path.dirname(currentRockFilename);
let files = fs.readdirSync(currentRockDirname);
let filename = null;
basename_i = name + ".rock";
for (i = 0; i < files.length; ++i) {
let file = files[i];
if (file.toLowerCase() === basename_i) {
filename = path.join(currentRockDirname, file);
if (filename === currentRockFilename) throw ("The file '"+currentRockFilename+"' is including itself");
break;
}
}
if (filename == null) throw ("Include file not found: " + path.join(currentRockDirname, basename_i));
return path.resolve(filename);// Absolute path
}
8 changes: 7 additions & 1 deletion satriani/satriani.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
const parser = require('./satriani.parser.js');
const interpreter = require('./satriani.interpreter.js');
const path = require('path');

module.exports = {
Interpreter : function() {
this.run = function(program, input, output) {
this.run = function(program, input, output, file) {
if (typeof(program) == 'string') program = this.parse(program);
let env = new interpreter.Environment();
env.output = output || console.log;
env.input = input || (() => "");
if (file == undefined) {
file = path.join(__dirname, "anonymous.rock");
}
file = path.resolve(file);// Absolute path
env.assign("thy_location", file, null, 1);
return env.run(program);
}

Expand Down
Loading