Skip to content

Commit

Permalink
Merge branch 'handle-local-symbols-correctly'
Browse files Browse the repository at this point in the history
  • Loading branch information
redien committed Dec 8, 2015
2 parents cabad2f + 9f11971 commit fec1c80
Show file tree
Hide file tree
Showing 13 changed files with 392 additions and 126 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ npm test
#### Bugs/TODO

* Using an imported value from a parent module doesn't produce an error when it wasn't defined in the current module.
* Should produce an error when no values are used from an imported module.
* Importing modules should return error unused_import

#### Code coverage
[![Coverage Status](https://img.shields.io/coveralls/redien/reuse-lang.svg)](https://coveralls.io/r/redien/reuse-lang?branch=master)
Expand Down
15 changes: 15 additions & 0 deletions feature/import.feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

var it_should_evaluate_expression_to_value_given_program = require('./util/expect-value');
var it_should_return_error_given_program = require('./util/expect-error');

describe('Importing modules', function () {
it_should_evaluate_expression_to_value_given_program(
Expand All @@ -27,4 +28,18 @@ describe('Importing modules', function () {
'(import ./feature/util/test-module.ru something)(export value (lambda () (something:add-42 9)))',
'should prepend imported symbols with the specified name followed by a colon'
);

it_should_evaluate_expression_to_value_given_program(
'module.value()',
52,
'(import ./feature/util/test-module.ru something)(export value (lambda () (something:add-50 2)))',
'should keep references to exported symbols consistent after they are imported'
);

it_should_evaluate_expression_to_value_given_program(
'module.value()',
44,
'(define two 2)(import ./feature/util/test-module.ru something)(export value (lambda () (something:add-42 two)))',
'should not redefine local symbols with local symbols from imported modules'
);
});
2 changes: 1 addition & 1 deletion feature/util/expect-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var it_should_evaluate_expression_to_value_given_program = function (expression,

it(message, function (done) {
nodeTester.evaluateExpressionWithProgram(expression, program, function (error, result) {
if (error) { throw new Error(util.inspect(error)); }
if (error) { if (error instanceof Error) { throw error } else { throw new Error(util.inspect(error)); }; }
result.should.equal(expected);
done();
});
Expand Down
2 changes: 2 additions & 0 deletions feature/util/test-module.ru
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
)

(export add-42 (lambda (x) (+ x 42)))
(export add-50 (lambda (x) (add-42 (+ x 8))))
(define two 123)
48 changes: 38 additions & 10 deletions lib/definition-importer.js

Large diffs are not rendered by default.

284 changes: 186 additions & 98 deletions lib/definition-importer.ru
Original file line number Diff line number Diff line change
Expand Up @@ -18,122 +18,210 @@
(import stdlib/string.ru)
(import ./lib/parse-tree.ru)

(define export-value (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 101) 120) 112) 111) 114) 116))
(define import-value (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 105) 109) 112) 111) 114) 116))
(define underscore 95)
(define colon 58)

(define new-define-from-import-atom (lambda (line column) (parse-tree:atom (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 100) 101) 102) 105) 110) 101) 45) 102) 114) 111) 109) 45) 105) 109) 112) 111) 114) 116) line column)))
(define export-form (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 101) 120) 112) 111) 114) 116))
(define import-form (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 105) 109) 112) 111) 114) 116))
(define define-form (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 100) 101) 102) 105) 110) 101))
(define define-from-import-form (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:push (string:new) 100) 101) 102) 105) 110) 101) 45) 102) 114) 111) 109) 45) 105) 109) 112) 111) 114) 116))

(define module-list-contains? (lambda (module-list module-name)
(if (nil? module-list)
(define form-from-statement (lambda (statement)
(parse-tree:value (parse-tree:child statement 0))))

(define string-exists-in-list? (lambda (string list)
(if (nil? list)
false
(if (string:equal? module-name (first module-list))
(if (string:equal? string (first list))
true
(recur (rest module-list) module-name)))))
(recur string (rest list))))))

(define module-already-imported? (lambda (module-name module-list)
(string-exists-in-list? module-name module-list)))

(define is-statement-import-form? (lambda (statement)
(string:equal? import-form (form-from-statement statement))))

(define is-statement-export-form? (lambda (statement)
(string:equal? export-form (form-from-statement statement))))

(define is-statement-any-define-form? (lambda (statement)
(or
(string:equal? define-form (form-from-statement statement))
(string:equal? define-from-import-form (form-from-statement statement)))))

(define module-name-from-statement (lambda (statement)
(parse-tree:value
(parse-tree:child statement 1))))

(define has-module-prefix (lambda (statement)
(== (parse-tree:count statement) 3)))

(define make-state (lambda (program statement-index module-name import-prefix redefine-exports)
(define module-prefix-from-statement (lambda (statement)
(parse-tree:value (parse-tree:child statement 2))))

(define make-state (lambda (program statement-index module-name module-prefix should-redefine-exports)
(cons program
(cons statement-index
(cons module-name
(cons import-prefix
redefine-exports))))))
(cons module-prefix
should-redefine-exports))))))

(define append-state (lambda (state-list state)
(cons state state-list)))

(export import (lambda (program module-loader)
(let (convert-module (lambda (state new-program imported-modules)
(if (nil? state)
new-program

(let (program (first (first state)))
(let (statement-index (first (rest (first state))))
(let (module-name (first (rest (rest (first state)))))
(let (import-prefix (first (rest (rest (rest (first state))))))
(let (redefine-exports (rest (rest (rest (rest (first state))))))

(define program-from-state (lambda (state)
(first (first state))))

(define statement-index-from-state (lambda (state)
(first (rest (first state)))))

(define module-name-from-state (lambda (state)
(first (rest (rest (first state))))))

(define module-prefix-from-state (lambda (state)
(first (rest (rest (rest (first state)))))))

(define should-redefine-exports-from-state (lambda (state)
(rest (rest (rest (rest (first state)))))))

(define redefine-export-statement (lambda (statement module-name module-prefix)
(parse-tree:push (parse-tree:push (parse-tree:push (parse-tree:push (parse-tree:list)
(parse-tree:atom define-from-import-form 0 0))
(parse-tree:atom (parse-tree:value (parse-tree:child statement 1)) 0 0))
(parse-tree:child statement 2))
(parse-tree:atom module-name 0 0))))

(define state-with-imported-module (lambda (module-prefix module-name imported-module next-state)
(append-state
next-state
(make-state
imported-module
0
module-name
module-prefix
true))))

(define state-for-next-statement (lambda (state module-prefix)
(let (program (program-from-state state))
(let (statement-index (statement-index-from-state state))
(if (< (+ statement-index 1) (parse-tree:count program))
(append-state
(rest state)
(make-state
program
(+ statement-index 1)
(module-name-from-state state)
module-prefix
(should-redefine-exports-from-state state)))
(rest state))))))

(export enumerate-statements-matching-predicate (lambda (program predicate)
(let (enumerate-statements-with-accumulator (lambda (statement-index list)
(if (< statement-index (parse-tree:count program))
(let (statement (parse-tree:child program statement-index))
(let (form-name (parse-tree:value (parse-tree:child statement 0)))

(let (module-import?
(and
(string:equal? import-value form-name)
(not (module-list-contains?
imported-modules
(parse-tree:value
(parse-tree:child statement 1))))))
(let (import-prefix
(if (and
module-import?
(== (parse-tree:count statement) 3))
(string:push (parse-tree:value (parse-tree:child statement 2)) 58)
import-prefix))

(let (state-for-next-statement (lambda ()
(if (< (+ statement-index 1) (parse-tree:count program))
(append-state
(rest state)
(make-state
program
(+ statement-index 1)
module-name
import-prefix
redefine-exports))
(rest state))))

(let (state-with-imported-module (lambda (module-name imported-module)
(append-state
(state-for-next-statement)
(make-state
imported-module
0
module-name
import-prefix
true))))

(let (statement
(if (and
redefine-exports
(string:equal? export-value form-name))
(parse-tree:push (parse-tree:push (parse-tree:push (parse-tree:push (parse-tree:list)
(new-define-from-import-atom 0 0))
(parse-tree:atom (string:concatenate import-prefix (parse-tree:value (parse-tree:child statement 1))) 0 0))
(parse-tree:child statement 2))
(parse-tree:atom module-name 0 0))
statement))

(let (new-program
(if (or
(string:equal? import-value form-name)
(>= statement-index (parse-tree:count program)))
new-program
(parse-tree:push new-program statement)))

(let (next-state
(if module-import?
(let (module-name (parse-tree:value (parse-tree:child statement 1)))
(let (loaded-module (module-loader module-name))
(if (> (parse-tree:count loaded-module) 0)
(state-with-imported-module
module-name
loaded-module)
(state-for-next-statement))))
(state-for-next-statement)))

(let (imported-modules
(if module-import?
(cons
(parse-tree:value
(parse-tree:child statement 1))
imported-modules)
imported-modules))

(recur next-state new-program imported-modules)))))))))))))))))))
(let (next-list (if (predicate statement)
(cons (parse-tree:value (parse-tree:child statement 1)) list)
list))
(recur (+ statement-index 1) next-list)))
list)))
(enumerate-statements-with-accumulator 0 nil))))

(export enumerate-defines (lambda (program)
(enumerate-statements-matching-predicate program is-statement-any-define-form?)))

(export enumerate-exports (lambda (program)
(enumerate-statements-matching-predicate program is-statement-export-form?)))

(define prefix-symbols (lambda (program prefix enumerator)
(let (symbols (enumerator program))
(parse-tree:transform program
(lambda (expression)
(if (parse-tree:atom? expression)
(string-exists-in-list? (parse-tree:value expression) symbols)
false))
(lambda (expression)
(parse-tree:atom
(string:concatenate prefix (parse-tree:value expression))
(parse-tree:line expression)
(parse-tree:column expression)))))))

(define scope-local-defines (lambda (program module-name)
(prefix-symbols program (string:push module-name underscore) enumerate-defines)))

(define prefix-exported-symbols (lambda (program module-prefix)
(prefix-symbols program module-prefix enumerate-exports)))

(define convert-module (lambda (program module-loader state new-program imported-modules)
(if (nil? state)
new-program

(let (program (program-from-state state))
(let (statement-index (statement-index-from-state state))
(let (module-name (module-name-from-state state))
(let (module-prefix (module-prefix-from-state state))
(let (should-redefine-exports (should-redefine-exports-from-state state))

(let (statement (parse-tree:child program statement-index))
(let (form-name (form-from-statement statement))

(let (statement-is-module-import
(and
(is-statement-import-form? statement)
(not (module-already-imported? (module-name-from-statement statement) imported-modules))))

(let (module-prefix
(if (and
statement-is-module-import
(has-module-prefix statement))
(string:push (module-prefix-from-statement statement) colon)
module-prefix))

(let (new-statement
(if (and
should-redefine-exports
(is-statement-export-form? statement))
(redefine-export-statement statement module-name module-prefix)
statement))

(let (new-program
(if (or
(is-statement-import-form? statement)
(>= statement-index (parse-tree:count program)))
new-program
(parse-tree:push new-program new-statement)))

(let (next-state (state-for-next-statement state module-prefix))
(let (next-state
(if statement-is-module-import
(let (module-name (module-name-from-statement statement))
(let (loaded-module
(scope-local-defines
(prefix-exported-symbols
(module-loader module-name)
module-prefix)
module-name))
(if (> (parse-tree:count loaded-module) 0)
(state-with-imported-module module-prefix module-name loaded-module next-state)
next-state)))
next-state))

(let (imported-modules
(if statement-is-module-import
(cons (module-name-from-statement statement) imported-modules)
imported-modules))

(recur program module-loader next-state new-program imported-modules))))))))))))))))))


(export import (lambda (program module-loader)
(if (> (parse-tree:count program) 0)
(convert-module
program
module-loader
(append-state
nil
(make-state program 0 (string:new) (string:new) false))
(parse-tree:list)
nil)
(parse-tree:list)))))
(parse-tree:list))))
Loading

0 comments on commit fec1c80

Please sign in to comment.