Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add semantic rule for import statement.

  • Loading branch information...
commit 130af48a3b2539a90eb762fc45276d87194aeccb 1 parent 4bf0fb7
@coreh authored
Showing with 85 additions and 9 deletions.
  1. +10 −1 bin/katana
  2. +26 −5 lib/compiler.js
  3. +4 −3 lib/parser.js
  4. +45 −0 lib/semantics.js
View
11 bin/katana
@@ -6,6 +6,7 @@
var ansi = require('ansi')
var fs = require('fs')
var program = require('commander')
+var path = require('path')
var cursor = ansi(process.stderr)
@@ -30,6 +31,9 @@ program
.parse(process.argv)
var filename = program.args[0]
+if (!filename.match(/^\//)) {
+ filename = path.join(process.cwd(), filename)
+}
if (!filename) {
cursor.write('error: no input file\n')
@@ -54,7 +58,12 @@ try {
cursor.write('\n\nThe compiler encountered an internal logic error.\nThis is not a problem with your code, but within the compiler itself.\n\nPlease report this entire error log at ')
cursor.blue().underline().write('https://github.com/coreh/katana').reset()
cursor.write(',\nif possible along with the input that uncovered the error.\n\nSorry for the inconvenience.\n\n')
- cursor.write(err.stack).write('\n')
+ if (err.stack) {
+ cursor.write(err.stack.toString())
+ } else {
+ cursor.write(err.toString())
+ }
+ cursor.write('\n')
process.exit(INTERNAL_ERROR)
}
}
View
31 lib/compiler.js
@@ -1,5 +1,6 @@
var fs = require('fs')
var ansi = require('ansi')
+var path = require('path')
var misc = require('./misc')
@@ -13,14 +14,34 @@ var Compiler = module.exports = function() {
this.failed = false
}
-var compileCode = function(code, options) {
+var compileCode = function(code, modulePath, options) {
var lexerOutput = lexer(code)
var rewriterOutput = rewriter(lexerOutput)
- var parserOuput = parser(rewriterOutput, this, options)
+ var parserOuput = parser(rewriterOutput, this, modulePath, options)
return parserOuput
}
-Compiler.prototype.compile = function(modulePath) {
+Compiler.prototype.compile = function(importPath, fromPath) {
+ var modulePath
+
+ if (typeof fromPath == 'undefined') {
+ modulePath = importPath
+ } else {
+ if (importPath.match(/^\./)) {
+ // Relative path
+ modulePath = path.join(path.dirname(fromPath), importPath)
+ try {
+ if (fs.statSync(modulePath).isDirectory()) {
+ modulePath = modulePath + '/index.k'
+ } else {
+ modulePath = modulePath + '.k'
+ }
+ } catch (err) {
+ modulePath = modulePath + '.k'
+ }
+ }
+ }
+
var module = this.modules[modulePath]
if (!module) {
// Initialize the module
@@ -30,7 +51,7 @@ Compiler.prototype.compile = function(modulePath) {
module.code = fs.readFileSync(modulePath, 'utf-8')
// Compile code
- var result = compileCode(module.code)
+ var result = compileCode.call(this, module.code, modulePath, {})
// Add module path information to errors
result.errors.forEach(function(e) {
@@ -59,7 +80,7 @@ Compiler.prototype.compile = function(modulePath) {
// To avoid infinite recursion, we compile the module suppressing further
// imports
if (!module.exports) {
- module.exports = compile(modulePath, { suppressImports: true }).exports
+ module.exports = compileCode.call(this, module.code, modulePath, { suppressImports: true }).exports
}
}
return module
View
7 lib/parser.js
@@ -57,10 +57,11 @@ var leftAssociativeOperator = function(subExpressionName, optype, type) {
* @param {Object} rewriterOutput
*/
-var Parser = function(rewriterOutput, compiler, options) {
+var Parser = function(rewriterOutput, compiler, modulePath, options) {
// Store compiler (to recursively compile modules)
this.compiler = compiler
+ this.modulePath = modulePath
this.options = options
this.tokens = rewriterOutput.tokens
this.position = 0
@@ -909,8 +910,8 @@ Parser.prototype = {
* Take the output from the rewriter and parse it
* @param {Object} rewriterOutput
*/
-var parser = function(rewriterOutput, compiler, options) {
- var parser = new Parser(rewriterOutput, compiler, options)
+var parser = function(rewriterOutput, compiler, modulePath, options) {
+ var parser = new Parser(rewriterOutput, compiler, modulePath, options)
return { syntaxTree: parser.Program(), errors: parser.errors, exports: parser.semantics.exports }
}
View
45 lib/semantics.js
@@ -5,6 +5,7 @@ var KatanaError = misc.KatanaError
var SemanticAnalyzer = module.exports = function(parser, options) {
this.errors = parser.errors
this.compiler = parser.compiler
+ this.modulePath = parser.modulePath
this.options = options
this.scopeStack = [{ variables: {} }]
this.currentSymbol = null
@@ -237,6 +238,50 @@ SemanticAnalyzer.prototype.rules = {
}
},
+ 'import statement': function(semantics) {
+ for (var i = 0; i < this.children.length; i++) {
+ var importPath = this.children[i]
+
+ // Join all path components in a string to form the path
+ var importPathString = importPath.children.map(function(s) { return s.value }).join('')
+
+ // Grab the last path component, used to produce the import name
+ var lastPathComponent = importPath.children[importPath.children.length - 1]
+
+ // It must be an identifier
+ if (!lastPathComponent.is('identifier')) {
+ semantics.error('Invalid path for import statement: Last path component must be an identifier. Use import ... from statement instead.')
+ } else {
+ // Get the import name
+ var name = lastPathComponent.value
+
+ var type
+ if (semantics.options.suppressImports) {
+ // Do not compile imported module, to avoid infinite recursion.
+ // Default import type to var
+ type = new Type('var')
+ } else {
+ // Recursively compile the imported module
+ module = semantics.compiler.compile(importPathString, semantics.modulePath)
+ // Check if there's an export with the same name as the module.
+ if (module.exports[name]) {
+ // Grab the type, and use it for the import
+ type = module.exports[name]
+ } else {
+ // Set the type to var, as in run time we'll get an object with all the
+ // exported values.
+ type = new Type('var')
+ }
+ }
+ var other
+ if (other = semantics.addToScope(name, type, lastPathComponent.line, lastPathComponent.column)) {
+ semantics.error('Cannot import `' + name + '`. Name already declared at current scope.', lastPathComponent.line, lastPathComponent.column)
+ semantics.note('Previous declaration is here.', other.line, other.column)
+ }
+ }
+ }
+ },
+
'export statement': function(semantics) {
if (semantics.scopeStack.length > 1) {
semantics.error('Export statement cannot be used inside a function.')
Please sign in to comment.
Something went wrong with that request. Please try again.