From c8ac7f05339dda11813c13e02e62c2849a6b5939 Mon Sep 17 00:00:00 2001 From: Jeffery Olson Date: Mon, 1 Feb 2010 20:57:03 -0800 Subject: [PATCH 1/2] starting port of nodes.rb to coffee-script.. Node only, so far --- src/nodes.coffee | 102 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/nodes.coffee b/src/nodes.coffee index 793ed44d..b3617627 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1,5 +1,107 @@ +# The abstract base class for all CoffeeScript nodes. +# All nodes are implement a "compile_node" method, which performs the +# code generation for that node. To compile a node, call the "compile" +# method, which wraps "compile_node" in some extra smarts, to know when the +# generated code should be wrapped up in a closure. An options hash is passed +# and cloned throughout, containing messages from higher in the AST, +# information about the current scope, and indentation level. exports.Node: -> this.values: arguments +exports.Node::TAB: ' ' + +# Tag this node as a statement, meaning that it can't be used directly as +# the result of an expression. +exports.Node::mark_as_statement: -> + this.is_statement: -> true + +# Tag this node as a statement that cannot be transformed into an expression. +# (break, continue, etc.) It doesn't make sense to try to transform it. +exports.Node::mark_as_statement_only: -> + this.mark_as_statement() + this.is_statement_only: -> true + +# This node needs to know if it's being compiled as a top-level statement, +# in order to compile without special expression conversion. +exports.Node::mark_as_top_sensitive: -> + this.is_top_sensitive: -> true + +flatten: (aggList, newList) -> + for item in newList + aggList.push(item) + aggList + +compact: (input) -> + compected: [] + for item in input + if item? + compacted.push(item) + +# Provide a quick implementation of a children method. +exports.Node::children: (attributes) -> + # TODO -- are these optimal impls of flatten and compact + # .. do better ones exist in a stdlib? + agg = [] + for item in attributes + agg: flatten agg, item + compacted: compact agg + this.children: -> + compacted + +exports.Node::write: (code) -> + # hm.. + # TODO -- should print to STDOUT in "VERBOSE" how to + # go about this.. ? jsonify 'this'? + code + +# This is extremely important -- we convert JS statements into expressions +# by wrapping them in a closure, only if it's possible, and we're not at +# the top level of a block (which would be unnecessary), and we haven't +# already been asked to return the result. +exports.Node::compile: (o) -> + # TODO -- need JS dup/clone + opts: if not o? then {} else o + this.options: opts + this.indent: opts.indent + top: this.options.top + if not this.is_top_sentitive() + this.options.top: undefined + closure: this.is_statement() and not this.is_statement_only() and not top and typeof(this) == "CommentNode" + closure &&= not this.do_i_contain (n) -> n.is_statement_only() + if closure then this.compile_closure(this.options) else compile_node(this.options) + +# Statements converted into expressions share scope with their parent +# closure, to preserve JavaScript-style lexical scope. +exports.Node::compile_closure: (o) -> + opts: if not o? then {} else o + this.indent: opts.indent + opts.shared_scope: o.scope + exports.ClosureNode.wrap(this).compile(opts) + +# Quick short method for the current indentation level, plus tabbing in. +exports.Node::idt: (tLvl) -> + tabs: if tLvl? then tLvl else 0 + tabAmt: '' + for x in [0...tabs] + tabAmt: tabAmt + this.TAB + this.indent + tabAmt + +exports.Node::is_a_node: -> + true + +#Does this node, or any of it's children, contain a node of a certain kind? +exports.Node::do_i_contain: (block) -> + for node in this.children + return true if block(node) + return true if node.is_a_node() and node.do_i_contain(block) + false + +# Default implementations of the common node methods. +exports.Node::unwrap: -> this +exports.Node::children: [] +exports.Node::is_a_statement: -> false +exports.Node::is_a_statement_only: -> false +exports.Node::is_top_sensitive: -> false + exports.Expressions : exports.Node exports.LiteralNode : exports.Node exports.ReturnNode : exports.Node From 74b9545dc863cb5724615b9e2dbdcb785323c34b Mon Sep 17 00:00:00 2001 From: Jeffery Olson Date: Thu, 4 Feb 2010 10:36:33 -0800 Subject: [PATCH 2/2] work on nodes.coffee and adding scope.coffee --- src/nodes.coffee | 144 +++++++++++++++++++++++++++++++++++++++++------ src/scope.coffee | 99 ++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 src/scope.coffee diff --git a/src/nodes.coffee b/src/nodes.coffee index b3617627..91f9793b 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1,3 +1,31 @@ +# Some helper functions + +# TODO -- shallow (1 deep) flatten.. +# need recursive version.. +flatten: (aggList, newList) -> + for item in newList + aggList.push(item) + aggList + +compact: (input) -> + compected: [] + for item in input + if item? + compacted.push(item) + +dup: (input) -> + output: null + if input instaceof Array + output: [] + for val in input + output.push(val) + else + output: {} + for key, val of input + output.key: val + output + output + # The abstract base class for all CoffeeScript nodes. # All nodes are implement a "compile_node" method, which performs the # code generation for that node. To compile a node, call the "compile" @@ -25,22 +53,11 @@ exports.Node::mark_as_statement_only: -> exports.Node::mark_as_top_sensitive: -> this.is_top_sensitive: -> true -flatten: (aggList, newList) -> - for item in newList - aggList.push(item) - aggList - -compact: (input) -> - compected: [] - for item in input - if item? - compacted.push(item) - # Provide a quick implementation of a children method. exports.Node::children: (attributes) -> # TODO -- are these optimal impls of flatten and compact # .. do better ones exist in a stdlib? - agg = [] + agg: [] for item in attributes agg: flatten agg, item compacted: compact agg @@ -51,6 +68,7 @@ exports.Node::write: (code) -> # hm.. # TODO -- should print to STDOUT in "VERBOSE" how to # go about this.. ? jsonify 'this'? + # use node's puts ?? code # This is extremely important -- we convert JS statements into expressions @@ -85,14 +103,11 @@ exports.Node::idt: (tLvl) -> tabAmt: tabAmt + this.TAB this.indent + tabAmt -exports.Node::is_a_node: -> - true - #Does this node, or any of it's children, contain a node of a certain kind? exports.Node::do_i_contain: (block) -> for node in this.children return true if block(node) - return true if node.is_a_node() and node.do_i_contain(block) + return true if node instanceof exports.Node and node.do_i_contain(block) false # Default implementations of the common node methods. @@ -102,7 +117,102 @@ exports.Node::is_a_statement: -> false exports.Node::is_a_statement_only: -> false exports.Node::is_top_sensitive: -> false -exports.Expressions : exports.Node +# A collection of nodes, each one representing an expression. +exports.Expressions: (nodes) -> + this.mark_as_statement() + this.expressions: [] + this.children([this.expressions]) + for n in nodes + this.expressions: flatten this.expressions, n +exports.Expressions extends exports.Node + +exports.Expressions::TRAILING_WHITESPACE: /\s+$/ + +# Wrap up a node as an Expressions, unless it already is. +exports.Expressions::wrap: (nodes) -> + return nodes[0] if nodes.length == 1 and nodes[0] instanceof exports.Expressions + new Expressions(nodes) + +# Tack an expression on to the end of this expression list. +exports.Expressions::push: (node) -> + this.expressions.push(node) + this + +# Tack an expression on to the beginning of this expression list. +exports.Expressions::unshift: (node) -> + this.expressions.unshift(node) + this + +# If this Expressions consists of a single node, pull it back out. +exports.Expressions::unwrap: -> + if this.expressions.length == 1 then this.expressions[0] else this + +# Is this an empty block of code? +exports.Expressions::is_empty: -> + this.expressions.length == 0 + +# Is the node last in this block of expressions. +exports.Expressions::is_last: (node) -> + arr_length: this.expressions.length + this.last_index ||= if this.expressions[arr_length - 1] instanceof exports.CommentNode then -2 else -1 + node == this.expressions[arr_length - this.last_index] + +exports.Expressions::compile: (o) -> + opts: if o? then o else {} + if opts.scope then super(dup(opts)) else this.compile_root(o) + +# Compile each expression in the Expressions body. +exports.Expressions::compile_node: (options) -> + opts: if options? then options else {} + compiled: [] + for e in this.expressions + compiled.push(this.compile_expression(e, dup(options))) + code: '' + for line in compiled + code: code + line + '\n' + +# If this is the top-level Expressions, wrap everything in a safety closure. +exports.Expressions::compile_root: (o) -> + opts: if o? then o else {} + indent: if opts.no_wrap then '' else this.TAB + this.indent: indent + opts.indent: indent + opts.scope: new Scope(null, this, null) + code: if opts.globals then compile_node(opts) else compile_with_declarations(opts) + code.replace(this.TRAILING_WHITESPACE, '') + this.write(if opts.no_wrap then code else "(function(){\n"+code+"\n})();") + +# Compile the expressions body, with declarations of all inner variables +# at the top. +exports.Expressions::compile_with_declaractions: (o) -> + opts: if o? : o else {} + code: this.compile_node(opts) + args: this.do_i_contain (n) -> n instanceof LiteralNode and n.arguments? + argv: if args and opts.scope.arguments then '' else 'var ' + code: this.idt() + argv+"arguments = Array.prototype.slice.call(arguments, 0);\n"+code if args + code: this.idt() + "var "+opts.scope.compiled_assignments+";\n"+code if o.scope.has_assignments(this) + this.write(code) + + # Compiles a single expression within the expression list. + def compile_expression(node, o) + @indent = o[:indent] + stmt = node.statement? + # We need to return the result if this is the last node in the expressions body. + returns = o.delete(:return) && last?(node) && !node.statement_only? + # Return the regular compile of the node, unless we need to return the result. + return "#{stmt ? '' : idt}#{node.compile(o.merge(:top => true))}#{stmt ? '' : ';'}" unless returns + # If it's a statement, the node knows how to return itself. + return node.compile(o.merge(:return => true)) if node.statement? + # If it's not part of a constructor, we can just return the value of the expression. + return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor? + # It's the last line of a constructor, add a safety check. + temp = o[:scope].free_variable + "#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};" + end + + end + + exports.LiteralNode : exports.Node exports.ReturnNode : exports.Node exports.CommentNode : exports.Node diff --git a/src/scope.coffee b/src/scope.coffee new file mode 100644 index 00000000..7b56f7bd --- /dev/null +++ b/src/scope.coffee @@ -0,0 +1,99 @@ +dup: (input) -> + output: null + if input instaceof Array + output: [] + for val in input + output.push(val) + else + output: {} + for key, val of input + output.key: val + output + output + +# scope objects form a tree corresponding to the shape of the function +# definitions present in the script. They provide lexical scope, to determine +# whether a variable has been seen before or if it needs to be declared. +exports.Scope: (parent, expressions, func) -> + # Initialize a scope with its parent, for lookups up the chain, + # as well as the Expressions body where it should declare its variables, + # and the function that it wraps. + this.parent: parent + this.expressions: expressions + this.function: func + this.variables: {} + this.temp_variable: if this.parent then dup(this.parent.temp_variable) : '__a' + +# Look up a variable in lexical scope, or declare it if not found. +exports.Scope::find: (name, rem) -> + remote: if rem? then rem else false + found: this.check(name) + return found if found || remote + this.variables[name]: 'var' + found + +# Define a local variable as originating from a parameter in current scope +# -- no var required. +exports.Scope::parameter: (name) -> + this.variables[name]: = 'param' + +# Just check to see if a variable has already been declared. +exports.Scope::check: (name) -> + return true if this.variables[name]? + # TODO: what does that ruby !! mean..? need to follow up + # .. this next line is prolly wrong .. + not not (this.parent and this.parent.check(name)) + +# You can reset a found variable on the immediate scope. +exports.Scope::reset: (name) -> + this.variables[name]: undefined + +exports.Scope::free_variable: -> + # need .succ! impl + + # Find an available, short, name for a compiler-generated variable. + def free_variable + @temp_variable.succ! while check(@temp_variable) + @variables[@temp_variable.to_sym] = :var + Value.new(@temp_variable.dup) + end + + # Ensure that an assignment is made at the top of scope (or top-level + # scope, if requested). + def assign(name, value, top=false) + return @parent.assign(name, value, top) if top && @parent + @variables[name.to_sym] = Value.new(value) + end + + def declarations?(body) + !declared_variables.empty? && body == @expressions + end + + def assignments?(body) + !assigned_variables.empty? && body == @expressions + end + + # Return the list of variables first declared in current scope. + def declared_variables + @variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort + end + + # Return the list of variables that are supposed to be assigned at the top + # of scope. + def assigned_variables + @variables.select {|k, v| v.is_a?(Value) }.sort_by {|pair| pair[0].to_s } + end + + def compiled_declarations + declared_variables.join(', ') + end + + def compiled_assignments + assigned_variables.map {|name, val| "#{name} = #{val}"}.join(', ') + end + + def inspect + "" + end + + end