diff options
-rwxr-xr-x | configure | 137 | ||||
-rwxr-xr-x | js2c.py | 317 | ||||
-rw-r--r-- | jsmin.py | 218 | ||||
-rw-r--r-- | src/file.cc | 153 | ||||
-rw-r--r-- | src/file.h | 8 | ||||
-rw-r--r-- | src/main.js | 85 | ||||
-rw-r--r-- | src/net.cc | 425 | ||||
-rw-r--r-- | src/net.h | 8 | ||||
-rw-r--r-- | src/node.cc | 296 | ||||
-rw-r--r-- | src/node.h | 7 | ||||
-rw-r--r-- | src/node_tcp.cc | 341 | ||||
-rw-r--r-- | src/node_tcp.h | 8 | ||||
-rw-r--r-- | src/process.cc | 39 | ||||
-rw-r--r-- | src/process.h | 8 | ||||
-rw-r--r-- | test/mjsunit.js | 165 | ||||
-rw-r--r-- | test/test-test.js | 12 | ||||
-rw-r--r-- | wscript | 28 |
17 files changed, 1759 insertions, 496 deletions
diff --git a/configure b/configure new file mode 100755 index 0000000000..01876912fc --- /dev/null +++ b/configure @@ -0,0 +1,137 @@ +#! /bin/sh + +# waf configure wrapper + +# Fancy colors used to beautify the output a bit. +# +if [ "$NOCOLOR" ] ; then + NORMAL="" + BOLD="" + RED="" + YELLOW="" + GREEN="" +else + NORMAL='\\033[0m' + BOLD='\\033[01;1m' + RED='\\033[01;91m' + YELLOW='\\033[00;33m' + GREEN='\\033[01;92m' +fi + +EXIT_SUCCESS=0 +EXIT_FAILURE=1 +EXIT_ERROR=2 +EXIT_BUG=10 + +CUR_DIR=$PWD + +#possible relative path +WORKINGDIR=`dirname $0` +cd $WORKINGDIR +#abs path +WORKINGDIR=`pwd` +cd $CUR_DIR + +# Checks for WAF. Honours $WAF if set. Stores path to 'waf' in $WAF. +# Requires that $PYTHON is set. +# +checkWAF() +{ + printf "Checking for WAF\t\t\t: " + #installed miniwaf in sourcedir + if [ -z "$WAF" ] ; then + if [ -f "${WORKINGDIR}/waf" ] ; then + WAF="${WORKINGDIR}/waf" + if [ ! -x "$WAF" ] ; then + chmod +x $WAF + fi + fi + fi + if [ -z "$WAF" ] ; then + if [ -f "${WORKINGDIR}/waf-light" ] ; then + ${WORKINGDIR}/waf-light --make-waf + WAF="${WORKINGDIR}/waf" + fi + fi + #global installed waf with waf->waf.py link + if [ -z "$WAF" ] ; then + WAF=`which waf 2>/dev/null` + fi + # neither waf nor miniwaf could be found + if [ ! -x "$WAF" ] ; then + printf $RED"not found"$NORMAL"\n" + echo "Go to http://code.google.com/p/waf/" + echo "and download a waf version" + exit $EXIT_FAILURE + else + printf $GREEN"$WAF"$NORMAL"\n" + fi +} + +# Generates a Makefile. Requires that $WAF is set. +# +generateMakefile() +{ + cat > Makefile << EOF +#!/usr/bin/make -f +# Waf Makefile wrapper +WAF_HOME=$CUR_DIR + +all: + @$WAF build + +all-debug: + @$WAF -v build + +all-progress: + @$WAF -p build + +install: + if test -n "\$(DESTDIR)"; then \\ + $WAF install --yes --destdir="\$(DESTDIR)" --prefix="$PREFIX"; \\ + else \\ + $WAF install --yes --prefix="$PREFIX"; \\ + fi; + +uninstall: + @if test -n "\$(DESTDIR)"; then \\ + $WAF uninstall --destdir="\$(DESTDIR)" --prefix="$PREFIX"; \\ + else \\ + $WAF uninstall --prefix="$PREFIX"; \\ + fi; + +clean: + @$WAF clean + +distclean: + @$WAF distclean + @-rm -rf _build_ + @-rm -f Makefile + +check: + @$WAF check + +dist: + @$WAF dist + +.PHONY: clean dist distclean check uninstall install all + +EOF +} + +checkWAF + +PREFIX=/usr/local +case $1 in + --prefix) + PREFIX=$2 + ;; +esac + +export PREFIX +generateMakefile + + +"${WAF}" configure --prefix "${PREFIX}" + +exit $? diff --git a/js2c.py b/js2c.py new file mode 100755 index 0000000000..312626101b --- /dev/null +++ b/js2c.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python +# +# Copyright 2006-2008 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This is a utility for converting JavaScript source code into C-style +# char arrays. It is used for embedded JavaScript code in the V8 +# library. + +import os, re, sys, string +import jsmin + + +def ToCArray(lines): + result = [] + for chr in lines: + value = ord(chr) + assert value < 128 + result.append(str(value)) + result.append("0") + return ", ".join(result) + + +def CompressScript(lines, do_jsmin): + # If we're not expecting this code to be user visible, we can run it through + # a more aggressive minifier. + if do_jsmin: + return jsmin.jsmin(lines) + + # Remove stuff from the source that we don't want to appear when + # people print the source code using Function.prototype.toString(). + # Note that we could easily compress the scripts mode but don't + # since we want it to remain readable. + #lines = re.sub('//.*\n', '\n', lines) # end-of-line comments + #lines = re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', lines) # comments. + #lines = re.sub('\s+\n+', '\n', lines) # trailing whitespace + return lines + + +def ReadFile(filename): + file = open(filename, "rt") + try: + lines = file.read() + finally: + file.close() + return lines + + +def ReadLines(filename): + result = [] + for line in open(filename, "rt"): + if '#' in line: + line = line[:line.index('#')] + line = line.strip() + if len(line) > 0: + result.append(line) + return result + + +def LoadConfigFrom(name): + import ConfigParser + config = ConfigParser.ConfigParser() + config.read(name) + return config + + +def ParseValue(string): + string = string.strip() + if string.startswith('[') and string.endswith(']'): + return string.lstrip('[').rstrip(']').split() + else: + return string + + +def ExpandConstants(lines, constants): + for key, value in constants.items(): + lines = lines.replace(key, str(value)) + return lines + + +def ExpandMacros(lines, macros): + for name, macro in macros.items(): + start = lines.find(name + '(', 0) + while start != -1: + # Scan over the arguments + assert lines[start + len(name)] == '(' + height = 1 + end = start + len(name) + 1 + last_match = end + arg_index = 0 + mapping = { } + def add_arg(str): + # Remember to expand recursively in the arguments + replacement = ExpandMacros(str.strip(), macros) + mapping[macro.args[arg_index]] = replacement + while end < len(lines) and height > 0: + # We don't count commas at higher nesting levels. + if lines[end] == ',' and height == 1: + add_arg(lines[last_match:end]) + last_match = end + 1 + elif lines[end] in ['(', '{', '[']: + height = height + 1 + elif lines[end] in [')', '}', ']']: + height = height - 1 + end = end + 1 + # Remember to add the last match. + add_arg(lines[last_match:end-1]) + result = macro.expand(mapping) + # Replace the occurrence of the macro with the expansion + lines = lines[:start] + result + lines[end:] + start = lines.find(name + '(', end) + return lines + +class TextMacro: + def __init__(self, args, body): + self.args = args + self.body = body + def expand(self, mapping): + result = self.body + for key, value in mapping.items(): + result = result.replace(key, value) + return result + +class PythonMacro: + def __init__(self, args, fun): + self.args = args + self.fun = fun + def expand(self, mapping): + args = [] + for arg in self.args: + args.append(mapping[arg]) + return str(self.fun(*args)) + +CONST_PATTERN = re.compile('^const\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$') +MACRO_PATTERN = re.compile('^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') +PYTHON_MACRO_PATTERN = re.compile('^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') + +def ReadMacros(lines): + constants = { } + macros = { } + for line in lines: + hash = line.find('#') + if hash != -1: line = line[:hash] + line = line.strip() + if len(line) is 0: continue + const_match = CONST_PATTERN.match(line) + if const_match: + name = const_match.group(1) + value = const_match.group(2).strip() + constants[name] = value + else: + macro_match = MACRO_PATTERN.match(line) + if macro_match: + name = macro_match.group(1) + args = map(string.strip, macro_match.group(2).split(',')) + body = macro_match.group(3).strip() + macros[name] = TextMacro(args, body) + else: + python_match = PYTHON_MACRO_PATTERN.match(line) + if python_match: + name = python_match.group(1) + args = map(string.strip, python_match.group(2).split(',')) + body = python_match.group(3).strip() + fun = eval("lambda " + ",".join(args) + ': ' + body) + macros[name] = PythonMacro(args, fun) + else: + raise ("Illegal line: " + line) + return (constants, macros) + + +HEADER_TEMPLATE = """\ +#ifndef node_natives_h +#define node_natives_h +namespace node { + +%(source_lines)s\ + +} +#endif +""" + + +SOURCE_DECLARATION = """\ + static const char native_%(id)s[] = { %(data)s }; +""" + + +GET_DELAY_INDEX_CASE = """\ + if (strcmp(name, "%(id)s") == 0) return %(i)i; +""" + + +GET_DELAY_SCRIPT_SOURCE_CASE = """\ + if (index == %(i)i) return Vector<const char>(%(id)s, %(length)i); +""" + + +GET_DELAY_SCRIPT_NAME_CASE = """\ + if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i); +""" + +def JS2C(source, target): + ids = [] + delay_ids = [] + modules = [] + # Locate the macros file name. + consts = {} + macros = {} + for s in source: + if 'macros.py' == (os.path.split(str(s))[1]): + (consts, macros) = ReadMacros(ReadLines(str(s))) + else: + modules.append(s) + + # Build source code lines + source_lines = [ ] + source_lines_empty = [] + for s in modules: + delay = str(s).endswith('-delay.js') + lines = ReadFile(str(s)) + do_jsmin = lines.find('// jsminify this file, js2c: jsmin') != -1 + lines = ExpandConstants(lines, consts) + lines = ExpandMacros(lines, macros) + lines = CompressScript(lines, do_jsmin) + data = ToCArray(lines) + id = (os.path.split(str(s))[1])[:-3] + if delay: id = id[:-6] + if delay: + delay_ids.append((id, len(lines))) + else: + ids.append((id, len(lines))) + source_lines.append(SOURCE_DECLARATION % { 'id': id, 'data': data }) + source_lines_empty.append(SOURCE_DECLARATION % { 'id': id, 'data': 0 }) + + # Build delay support functions + get_index_cases = [ ] + get_script_source_cases = [ ] + get_script_name_cases = [ ] + + i = 0 + for (id, length) in delay_ids: + native_name = "native %s.js" % id + get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i }) + get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % { + 'id': id, + 'length': length, + 'i': i + }) + get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % { + 'name': native_name, + 'length': len(native_name), + 'i': i + }); + i = i + 1 + + for (id, length) in ids: + native_name = "native %s.js" % id + get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i }) + get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % { + 'id': id, + 'length': length, + 'i': i + }) + get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % { + 'name': native_name, + 'length': len(native_name), + 'i': i + }); + i = i + 1 + + # Emit result + output = open(str(target[0]), "w") + output.write(HEADER_TEMPLATE % { + 'builtin_count': len(ids) + len(delay_ids), + 'delay_count': len(delay_ids), + 'source_lines': "\n".join(source_lines), + 'get_index_cases': "".join(get_index_cases), + 'get_script_source_cases': "".join(get_script_source_cases), + 'get_script_name_cases': "".join(get_script_name_cases) + }) + output.close() + + if len(target) > 1: + output = open(str(target[1]), "w") + output.write(HEADER_TEMPLATE % { + 'builtin_count': len(ids) + len(delay_ids), + 'delay_count': len(delay_ids), + 'source_lines': "\n".join(source_lines_empty), + 'get_index_cases': "".join(get_index_cases), + 'get_script_source_cases': "".join(get_script_source_cases), + 'get_script_name_cases': "".join(get_script_name_cases) + }) + output.close() diff --git a/jsmin.py b/jsmin.py new file mode 100644 index 0000000000..ae7581413a --- /dev/null +++ b/jsmin.py @@ -0,0 +1,218 @@ +#!/usr/bin/python
+
+# This code is original from jsmin by Douglas Crockford, it was translated to
+# Python by Baruch Even. The original code had the following copyright and
+# license.
+#
+# /* jsmin.c
+# 2007-05-22
+#
+# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# The Software shall be used for Good, not Evil.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# */
+
+from StringIO import StringIO
+
+def jsmin(js):
+ ins = StringIO(js)
+ outs = StringIO()
+ JavascriptMinify().minify(ins, outs)
+ str = outs.getvalue()
+ if len(str) > 0 and str[0] == '\n':
+ str = str[1:]
+ return str
+
+def isAlphanum(c):
+ """return true if the character is a letter, digit, underscore,
+ dollar sign, or non-ASCII character.
+ """
+ return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
+ (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
+
+class UnterminatedComment(Exception):
+ pass
+
+class UnterminatedStringLiteral(Exception):
+ pass
+
+class UnterminatedRegularExpression(Exception):
+ pass
+
+class JavascriptMinify(object):
+
+ def _outA(self):
+ self.outstream.write(self.theA)
+ def _outB(self):
+ self.outstream.write(self.theB)
+
+ def _get(self):
+ """return the next character from stdin. Watch out for lookahead. If
+ the character is a control character, translate it to a space or
+ linefeed.
+ """
+ c = self.theLookahead
+ self.theLookahead = None
+ if c == None:
+ c = self.instream.read(1)
+ if c >= ' ' or c == '\n':
+ return c
+ if c == '': # EOF
+ return '\000'
+ if c == '\r':
+ return '\n'
+ return ' '
+
+ def _peek(self):
+ self.theLookahead = self._get()
+ return self.theLookahead
+
+ def _next(self):
+ """get the next character, excluding comments. peek() is used to see
+ if an unescaped '/' is followed by a '/' or '*'.
+ """
+ c = self._get()
+ if c == '/' and self.theA != '\\':
+ p = self._peek()
+ if p == '/':
+ c = self._get()
+ while c > '\n':
+ c = self._get()
+ return c
+ if p == '*':
+ c = self._get()
+ while 1:
+ c = self._get()
+ if c == '*':
+ if self._peek() == '/':
+ self._get()
+ return ' '
+ if c == '\000':
+ raise UnterminatedComment()
+
+ return c
+
+ def _action(self, action):
+ """do something! What you do is determined by the argument:
+ 1 Output A. Copy B to A. Get the next B.
+ 2 Copy B to A. Get the next B. (Delete A).
+ 3 Get the next B. (Delete B).
+ action treats a string as a single character. Wow!
+ action recognizes a regular expression if it is preceded by ( or , or =.
+ """
+ if action <= 1:
+ self._outA()
+
+ if action <= 2:
+ self.theA = self.theB
+ if self.theA == "'" or self.theA == '"':
+ while 1:
+ self._outA()
+ self.theA = self._get()
+ if self.theA == self.theB:
+ break
+ if self.theA <= '\n':
+ raise UnterminatedStringLiteral()
+ if self.theA == '\\':
+ self._outA()
+ self.theA = self._get()
+
+
+ if action <= 3:
+ self.theB = self._next()
+ if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
+ self.theA == '=' or self.theA == ':' or
+ self.theA == '[' or self.theA == '?' or
+ self.theA == '!' or self.theA == '&' or
+ self.theA == '|' or self.theA == ';' or
+ self.theA == '{' or self.theA == '}' or
+ self.theA == '\n'):
+ self._outA()
+ self._outB()
+ while 1:
+ self.theA = self._get()
+ if self.theA == '/':
+ break
+ elif self.theA == '\\':
+ self._outA()
+ self.theA = self._get()
+ elif self.theA <= '\n':
+ raise UnterminatedRegularExpression()
+ self._outA()
+ self.theB = self._next()
+
+
+ def _jsmin(self):
+ """Copy the input to the output, deleting the characters which are
+ insignificant to JavaScript. Comments will be removed. Tabs will be
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
+ Most spaces and linefeeds will be removed.
+ """
+ self.theA = '\n'
+ self._action(3)
+
+ while self.theA != '\000':
+ if self.theA == ' ':
+ if isAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ elif self.theA == '\n':
+ if self.theB in ['{', '[', '(', '+', '-']:
+ self._action(1)
+ elif self.theB == ' ':
+ self._action(3)
+ else:
+ if isAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ else:
+ if self.theB == ' ':
+ if isAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ elif self.theB == '\n':
+ if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
+ self._action(1)
+ else:
+ if isAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ else:
+ self._action(1)
+
+ def minify(self, instream, outstream):
+ self.instream = instream
+ self.outstream = outstream
+ self.theA = '\n'
+ self.theB = None
+ self.theLookahead = None
+
+ self._jsmin()
+ self.instream.close()
+
+if __name__ == '__main__':
+ import sys
+ jsm = JavascriptMinify()
+ jsm.minify(sys.stdin, sys.stdout)
diff --git a/src/file.cc b/src/file.cc new file mode 100644 index 0000000000..6638f07536 --- /dev/null +++ b/src/file.cc @@ -0,0 +1,153 @@ +#include "node.h" +#include <string.h> + +using namespace v8; + +class Callback { + public: + Callback(Handle<Value> v); + ~Callback(); + Local<Value> Call(Handle<Object> recv, int argc, Handle<Value> argv[]); + private: + Persistent<Function> handle; +}; + + +Callback::Callback (Handle<Value> v) +{ + HandleScope scope; + Handle<Function> f = Handle<Function>::Cast(v); + handle = Persistent<Function>::New(f); +} + +Callback::~Callback () +{ + handle.Dispose(); + handle.Clear(); // necessary? +} + +Local<Value> +Callback::Call (Handle<Object> recv, int argc, Handle<Value> argv[]) +{ + HandleScope scope; + Local<Value> r = handle->Call(recv, argc, argv); + return scope.Close(r); +} + +static int +after_rename (eio_req *req) +{ + Callback *callback = static_cast<Callback*>(req->data); + if (callback != NULL) { + HandleScope scope; + const int argc = 2; + Local<Value> argv[argc]; + + argv[0] = Integer::New(req->errorno); + argv[1] = String::New(strerror(req->errorno)); + + callback->Call(Context::GetCurrent()->Global(), argc, argv); + delete callback; + } + return 0; +} + +JS_METHOD(rename) +{ + if (args.Length() < 2) + return Undefined(); + + HandleScope scope; + + String::Utf8Value path(args[0]->ToString()); + String::Utf8Value new_path(args[1]->ToString()); + + Callback *callback = NULL; + if (!args[2]->IsUndefined()) callback = new Callback(args[2]); + + eio_req *req = eio_rename(*path, *new_path, EIO_PRI_DEFAULT, after_rename, callback); + node_eio_submit(req); + + return Undefined(); +} + +static int +after_stat (eio_req *req) +{ + Callback *callback = static_cast<Callback*>(req->data); + if (callback != NULL) { + HandleScope scope; + const int argc = 3; + Local<Value> argv[argc]; + + Local<Object> stats = Object::New(); + argv[0] = stats; + argv[1] = Integer::New(req->errorno); + argv[2] = String::New(strerror(req->errorno)); + + if (req->result == 0) { + struct stat *s = static_cast<struct stat*>(req->ptr2); + + /* ID of device containing file */ + stats->Set(JS_SYMBOL("dev"), Integer::New(s->st_dev)); + /* inode number */ + stats->Set(JS_SYMBOL("ino"), Integer::New(s->st_ino)); + /* protection */ + stats->Set(JS_SYMBOL("mode"), Integer::New(s->st_mode)); + /* number of hard links */ + stats->Set(JS_SYMBOL("nlink"), Integer::New(s->st_nlink)); + /* user ID of owner */ + stats->Set(JS_SYMBOL("uid"), Integer::New(s->st_uid)); + /* group ID of owner */ + stats->Set(JS_SYMBOL("gid"), Integer::New(s->st_gid)); + /* device ID (if special file) */ + stats->Set(JS_SYMBOL("rdev"), Integer::New(s->st_rdev)); + /* total size, in bytes */ + stats->Set(JS_SYMBOL("size"), Integer::New(s->st_size)); + /* blocksize for filesystem I/O */ + stats->Set(JS_SYMBOL("blksize"), Integer::New(s->st_blksize)); + /* number of blocks allocated */ + stats->Set(JS_SYMBOL("blocks"), Integer::New(s->st_blocks)); + /* time of last access */ + stats->Set(JS_SYMBOL("atime"), Date::New(1000*static_cast<double>(s->st_atime))); + /* time of last modification */ + stats->Set(JS_SYMBOL("mtime"), Date::New(1000*static_cast<double>(s->st_mtime))); + /* time of last status change */ + stats->Set(JS_SYMBOL("ctime"), Date::New(1000*static_cast<double>(s->st_ctime))); + } + + callback->Call(Context::GetCurrent()->Global(), argc, argv); + delete callback; + } + return 0; +} + +JS_METHOD(stat) +{ + if (args.Length() < 1) + return v8::Undefined(); + + HandleScope scope; + + String::Utf8Value path(args[0]->ToString()); + + Callback *callback = NULL; + if (!args[1]->IsUndefined()) callback = new Callback(args[1]); + + eio_req *req = eio_stat(*path, EIO_PRI_DEFAULT, after_stat, callback); + node_eio_submit(req); + + return Undefined(); +} + +void +NodeInit_file (Handle<Object> target) +{ + HandleScope scope; + + Local<Object> fs = Object::New(); + target->Set(String::NewSymbol("fs"), fs); + + JS_SET_METHOD(fs, "rename", rename); + JS_SET_METHOD(fs, "stat", stat); +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000000..75fddb7871 --- /dev/null +++ b/src/file.h @@ -0,0 +1,8 @@ +#ifndef node_file_h +#define node_file_h + +#include <v8.h> + +void NodeInit_file (v8::Handle<v8::Object> target); + +#endif diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000000..ae847e798f --- /dev/null +++ b/src/main.js @@ -0,0 +1,85 @@ +// module search paths +node.includes = ["."]; + +node.path = new function () { + this.join = function () { + var joined = ""; + for (var i = 0; i < arguments.length; i++) { + var part = arguments[i].toString(); + if (i === 0) { + part = part.replace(/\/*$/, "/"); + } else if (i === arguments.length - 1) { + part = part.replace(/^\/*/, ""); + } else { + part = part.replace(/^\/*/, "") + .replace(/\/*$/, "/"); + } + joined += part; + } + return joined; + }; + + this.dirname = function (path) { + var parts = path.split("/"); + return parts.slice(0, parts.length-1); + }; +}; + +function __include (module, path) { + var export = module.require(path); + for (var i in export) { + if (export.hasOwnProperty(i)) + module[i] = export[i]; + } +} + + + +function __require (path, loading_file) { + + var filename = path; + // relative path + // absolute path + if (path.slice(0,1) === "/") { + } else { + filename = node.path.join(node.path.dirname(loading_file), path); + } + node.blocking.print("require: " + filename); + + /* + for (var i = 0; i < suffixes.length; i++) { + var f = filename + "." + suffixes[i]; + + var stats = node.blocking.stat(f); + for (var j in stats) { + node.blocking.print("stats." + j + " = " + stats[j].toString()); + } + } + */ + + var source = node.blocking.cat(filename); + + // wrap the source in a function + source = "function (__file__, __dir__) { " + + " var exports = {};" + + " function require (m) { return __require(m, __file__); }" + + " function include (m) { return __include(this, m); }" + + source + + " return exports;" + + "};" + ; + var create_module = node.blocking.exec(source, filename); + + // execute the function wrap + return create_module(filename, node.path.dirname(filename)); +} + +// main script execution. +//__require(ARGV[1], ARGV[1]); +// +fs.stat("/tmp/world", function (stat, status, msg) { + for ( var i in stat ) { + node.blocking.print(i + ": " + stat[i]); + } + node.blocking.print("done: " + status.toString() + " " + msg.toString()); +}); diff --git a/src/net.cc b/src/net.cc new file mode 100644 index 0000000000..b381ed99c7 --- /dev/null +++ b/src/net.cc @@ -0,0 +1,425 @@ +#include "net.h" +#include "node.h" + +#include <oi_socket.h> +#include <oi_buf.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <strings.h> + +using namespace v8; + +static Persistent<String> readyState_str; + +static Persistent<Integer> readyStateCONNECTING; +static Persistent<Integer> readyStateOPEN; +static Persistent<Integer> readyStateCLOSED; + +enum encoding {UTF8, RAW}; + +class Socket { +public: + Socket (Handle<Object> obj, double timeout); + ~Socket (); + + int ConnectTCP (char *port, char *host); + void Write (Handle<Value> arg); + void Disconnect (); + + void SetEncoding (enum encoding); + void SetTimeout (double); + + void OnConnect (); + void OnRead (const void *buf, size_t count); + void OnDrain (); + void OnError (oi_error e); + void OnClose (); + +private: + oi_socket socket_; + struct addrinfo *address_; + Persistent<Object> js_object_; +}; + +static void +on_connect (oi_socket *socket) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnConnect(); +} + +static void +on_read (oi_socket *socket, const void *buf, size_t count) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnRead(buf, count); +} + +static void +on_drain (oi_socket *socket) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnDrain(); +} + +static void +on_error (oi_socket *socket, oi_error e) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnError(e); +} + +static void +on_timeout (oi_socket *socket) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnTimeout(e); +} + +static void +on_close (oi_socket *socket) +{ + Socket *s = static_cast<Socket*> (socket->data); + s->OnClose(); +} + + +static Handle<Value> +NewSocket (const Arguments& args) +{ + if (args.Length() > 1) + return Undefined(); + + HandleScope scope; + + // Default options + double timeout = 60.0; // in seconds + enum {RAW, UTF8} encoding = RAW; + + // Set options from argument. + if (args.Length() == 1 && args[0]->IsObject()) { + Local<Object> options = args[0]->ToObject(); + Local<Value> timeout_value = options->Get(String::NewSymbol("timeout")); + Local<Value> encoding_value = options->Get(String::NewSymbol("encoding")); + + if (timeout_value->IsNumber()) { + // timeout is specified in milliseconds like other time + // values in javascript + timeout = timeout_value->NumberValue() / 1000; + } + + if (encoding_value->IsString()) { + Local<String> encoding_string = encoding_value->ToString(); + char buf[5]; // need enough room for "utf8" or "raw" + encoding_string->WriteAscii(buf, 0, 4); + buf[4] = '\0'; + if(strcasecmp(buf, "utf8") == 0) encoding = UTF8; + } + } + + Socket *s = new Socket(args.This(), timeout); + if(s == NULL) + return Undefined(); // XXX raise error? + + return args.This(); +} + +static Socket* +Unwrapsocket (Handle<Object> obj) +{ + HandleScope scope; + Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); + Socket* socket = static_cast<Socket*>(field->Value()); + return socket; +} + +static Handle<Value> +SocketConnectTCPCallback (const Arguments& args) +{ + if (args.Length() < 1) + return Undefined(); + + HandleScope scope; + Socket *socket = Unwrapsocket(args.Holder()); + + String::AsciiValue port(args[0]); + + char *host = NULL; + String::AsciiValue host_v(args[1]->ToString()); + if(args[1]->IsString()) { + host = *host_v; + } + + int r = socket->ConnectTCP(*port, host); + // TODO raise error if r != 0 + + return Undefined(); +} + +static Handle<Value> +SocketWriteCallback (const Arguments& args) +{ + HandleScope scope; + Socket *socket = Unwrapsocket(args.Holder()); + socket->Write(args[0]); + return Undefined(); +} + +static Handle<Value> +SocketCloseCallback (const Arguments& args) +{ + HandleScope scope; + Socket *socket = Unwrapsocket(args.Holder()); + socket->Disconnect(); + return Undefined(); +} + +static void +DestroySocket (Persistent<Value> _, void *data) +{ + Socket *s = static_cast<Socket*> (data); + delete s; +} + +Socket::Socket(Handle<Object> js_object, double timeout) +{ + oi_socket_init(&socket_, timeout); + socket_.on_connect = on_connect; + socket_.on_read = on_read; + socket_.on_drain = on_drain; + socket_.on_error = on_error; + socket_.on_close = on_close; + socket_.on_timeout = on_timeout; + socket_.data = this; + + HandleScope scope; + js_object_ = Persistent<Object>::New(js_object); + js_object_->SetInternalField (0, External::New(this)); + js_object_.MakeWeak (this, DestroySocket); +} + +Socket::~Socket () +{ + Disconnect(); + oi_socket_detach(&socket_); + js_object_.Dispose(); + js_object_.Clear(); // necessary? +} + +static struct addrinfo tcp_hints = +/* ai_flags */ { AI_PASSIVE +/* ai_family */ , AF_UNSPEC +/* ai_socktype */ , SOCK_STREAM +/* ai_protocol */ , 0 +/* ai_addrlen */ , 0 +/* ai_addr */ , 0 +/* ai_canonname */ , 0 +/* ai_next */ , 0 + }; + +int +Socket::ConnectTCP(char *port, char *host) +{ + int r; + + HandleScope scope; + + js_object_->Set(readyState_str, readyStateCONNECTING); + + /* FIXME Blocking DNS resolution. */ + printf("resolving host: %s, port: %s\n", host, port); + r = getaddrinfo (host, port, &tcp_hints, &address); + if(r != 0) { + perror("getaddrinfo"); + return r; + } + + r = oi_socket_connect (&socket, address); + if(r != 0) { + perror("oi_socket_connect"); + return r; + } + oi_socket_attach (&socket, node_loop()); + + freeaddrinfo(address); + address = NULL; +} + +void Socket::Write (Handle<Value> arg) +{ + HandleScope scope; + + if (arg == Null()) { + + oi_socket_write_eof(&socket); + + } else if (arg->IsString()) { + Local<String> s = arg->ToString(); + + size_t l1 = s->Utf8Length(), l2; + oi_buf *buf = oi_buf_new2(l1); + l2 = s->WriteUtf8(buf->base, l1); + assert(l1 == l2); + + oi_socket_write(&socket, buf); + + } else if (arg->IsArray()) { + size_t length = array->Length(); + Handle<Array> array = Handle<Array>::Cast(arg); + oi_buf *buf = oi_buf_new2(length); + for (int i = 0; i < length; i++) { + Local<Value> int_value = array->Get(Integer::New(i)); + buf[i] = int_value->Int32Value(); + } + + oi_socket_write(&socket, buf); + + } else { + // raise error bad argument. + assert(0); + } +} + +void +Socket::Disconnect() +{ + oi_socket_close(&socket); +} + +void +Socket::OnConnect() +{ + HandleScope scope; + + assert(READY_STATE_CONNECTING == ReadyState()); + js_object_->Set(readyState_str, readyStateOPEN); + + Handle<Value> on_connect_value = js_object_->Get( String::NewSymbol("on_connect") ); + if (!on_connect_value->IsFunction()) + return; + Handle<Function> on_connect = Handle<Function>::Cast(on_connect_value); + + TryCatch try_catch; + + Handle<Value> r = on_connect->Call(js_object_, 0, NULL); + + if(try_catch.HasCaught()) + node_fatal_exception(try_catch); +} + +void +Socket::OnRead (const void *buf, size_t count) +{ + HandleScope scope; + + assert(READY_STATE_OPEN == ReadyState()); + + Handle<Value> onread_value = js_object_->Get( String::NewSymbol("on_read") ); + if (!onread_value->IsFunction()) return; + Handle<Function> onread = Handle<Function>::Cast(onread_value); + + const int argc = 1; + Handle<Value> argv[argc]; + + if(count) { + Handle<String> chunk = String::New((const char*)buf, count); // TODO binary data? + argv[0] = chunk; + } else { + // TODO eof? delete write method? + argv[0] = Null(); + } + + TryCatch try_catch; + + Handle<Value> r = onread->Call(js_object_, argc, argv); + + if(try_catch.HasCaught()) + node_fatal_exception(try_catch); +} + +void +Socket::OnClose () +{ + HandleScope scope; + + printf("onclose readyState %d\n", ReadyState()); + + assert(READY_STATE_OPEN == ReadyState()); + js_object_->Set(readyState_str, readyStateCLOSED); + + Handle<Value> onclose_value = js_object_->Get( String::NewSymbol("on_close") ); + if (!onclose_value->IsFunction()) return; + Handle<Function> onclose = Handle<Function>::Cast(onclose_value); + + TryCatch try_catch; + + Handle<Value> r = onclose->Call(js_object_, 0, NULL); + + if(try_catch.HasCaught()) + node_fatal_exception(try_catch); +} + +void +NodeInit_net (Handle<Object> target) +{ + HandleScope scope; + + // + // Socket + // + Local<FunctionTemplate> socket_template = FunctionTemplate::New(NewSocket); + target->Set(String::NewSymbol("Socket"), socket_template->GetFunction()); + socket_template->InstanceTemplate()->SetInternalFieldCount(1); + + // socket.connectTCP() + Local<FunctionTemplate> socket_connect_tcp = + FunctionTemplate::New(SocketConnectTCPCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("connectTCP"), + socket_connect_tcp->GetFunction()); + + // socket.connectUNIX() + Local<FunctionTemplate> socket_connect_unix = + FunctionTemplate::New(SocketConnectUNIXCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("connectUNIX"), + socket_connect_unix->GetFunction()); + + // socket.write() + Local<FunctionTemplate> socket_write = + FunctionTemplate::New(SocketWriteCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("write"), + socket_write->GetFunction()); + + // socket.close() + Local<FunctionTemplate> socket_close = + FunctionTemplate::New(SocketCloseCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("close"), + socket_close->GetFunction()); + + // + // Server + // + Local<FunctionTemplate> server_template = FunctionTemplate::New(NewServer); + target->Set(String::NewSymbol("Server"), server_template->GetFunction()); + server_template->InstanceTemplate()->SetInternalFieldCount(1); + + // server.listenTCP() + Local<FunctionTemplate> server_listenTCP = + FunctionTemplate::New(ServerListenTCPCallback); + server_template->InstanceTemplate()->Set(String::NewSymbol("listenTCP"), + server_listenTCP->GetFunction()); + + // server.listenUNIX() + Local<FunctionTemplate> server_listenUNIX = + FunctionTemplate::New(ServerListenUNIXCallback); + server_template->InstanceTemplate()->Set(String::NewSymbol("listenUNIX"), + server_listenTCP->GetFunction()); + + // server.close() + Local<FunctionTemplate> server_close = FunctionTemplate::New(ServerCloseCallback); + server_template->InstanceTemplate()->Set(String::NewSymbol("close"), + server_close->GetFunction()); +} + diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000000..347a3ebceb --- /dev/null +++ b/src/net.h @@ -0,0 +1,8 @@ +#ifndef node_net_h +#define node_net_h + +#include <v8.h> + +void NodeInit_net (v8::Handle<v8::Object> target); + +#endif diff --git a/src/node.cc b/src/node.cc index e89b88c042..07d31b29d4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1,9 +1,13 @@ #include "node.h" -#include "node_tcp.h" +//#include "net.h" +#include "file.h" +#include "process.h" #include "node_http.h" #include "node_timer.h" +#include "natives.h" + #include <stdio.h> #include <assert.h> @@ -12,20 +16,114 @@ #include <map> using namespace v8; +using namespace node; using namespace std; static int exit_code = 0; -// Reads a file into a v8 string. -static Handle<String> -ReadFile (const string& name) +// Extracts a C string from a V8 Utf8Value. +const char* +ToCString(const v8::String::Utf8Value& value) { + return *value ? *value : "<string conversion failed>"; +} - FILE* file = fopen(name.c_str(), "rb"); - if (file == NULL) return Handle<String>(); +void +ReportException(v8::TryCatch* try_catch) +{ + v8::HandleScope handle_scope; + v8::String::Utf8Value exception(try_catch->Exception()); + const char* exception_string = ToCString(exception); + v8::Handle<v8::Message> message = try_catch->Message(); + if (message.IsEmpty()) { + // V8 didn't provide any extra information about this error; just + // print the exception. + printf("%s\n", exception_string); + } else { + message->PrintCurrentStackTrace(stdout); + + // Print (filename):(line number): (message). + v8::String::Utf8Value filename(message->GetScriptResourceName()); + const char* filename_string = ToCString(filename); + int linenum = message->GetLineNumber(); + printf("%s:%i: %s\n", filename_string, linenum, exception_string); + // Print line of source code. + v8::String::Utf8Value sourceline(message->GetSourceLine()); + const char* sourceline_string = ToCString(sourceline); + printf("%s\n", sourceline_string); + // Print wavy underline (GetUnderline is deprecated). + int start = message->GetStartColumn(); + for (int i = 0; i < start; i++) { + printf(" "); + } + int end = message->GetEndColumn(); + for (int i = start; i < end; i++) { + printf("^"); + } + printf("\n"); + } +} + +// Executes a string within the current v8 context. +Handle<Value> +ExecuteString(v8::Handle<v8::String> source, + v8::Handle<v8::Value> filename) +{ + HandleScope scope; + Handle<Script> script = Script::Compile(source, filename); + if (script.IsEmpty()) { + return ThrowException(String::New("Error compiling string")); + } + + Handle<Value> result = script->Run(); + if (result.IsEmpty()) { + return ThrowException(String::New("Error running string")); + } + + return scope.Close(result); +} + +JS_METHOD(print) +{ + if (args.Length() < 1) return v8::Undefined(); + HandleScope scope; + Handle<Value> arg = args[0]; + String::Utf8Value value(arg); + + printf("%s\n", *value); + fflush(stdout); + + return Undefined(); +} + + +JS_METHOD(cat) +{ + if (args.Length() < 1) return v8::Undefined(); + HandleScope scope; + + String::Utf8Value filename(args[0]); + + Local<String> error_msg = String::New("File I/O error"); + + FILE* file = fopen(*filename, "rb"); + if (file == NULL) { + // Raise error + perror("fopen()"); + return ThrowException(error_msg); + } - fseek(file, 0, SEEK_END); + int r = fseek(file, 0, SEEK_END); + if (r < 0) { + perror("fseek()"); + return ThrowException(error_msg); + } + int size = ftell(file); + if (size < 0) { + perror("ftell()"); + return ThrowException(error_msg); + } rewind(file); char chars[size+1]; @@ -34,6 +132,7 @@ ReadFile (const string& name) int read = fread(&chars[i], 1, size - i, file); if(read <= 0) { perror("read()"); + return ThrowException(error_msg); } i += read; } @@ -45,38 +144,27 @@ ReadFile (const string& name) fclose(file); - HandleScope scope; - Local<String> result = String::New(expanded_base, size); + Local<String> contents = String::New(expanded_base, size); - return scope.Close(result); + return scope.Close(contents); } -static Handle<Value> -Log (const Arguments& args) +JS_METHOD(exec) { - if (args.Length() < 1) return v8::Undefined(); + if (args.Length() < 2) + return Undefined(); + HandleScope scope; - Handle<Value> arg = args[0]; - String::Utf8Value value(arg); - printf("%s\n", *value); - fflush(stdout); + Local<String> source = args[0]->ToString(); + Local<String> filename = args[1]->ToString(); - return Undefined(); + Handle<Value> result = ExecuteString(source, filename); + + return scope.Close(result); } -static Handle<Value> -BlockingFileRead (const Arguments& args) -{ - if (args.Length() < 1) return v8::Undefined(); - HandleScope scope; - - String::Utf8Value filename(args[0]); - Handle<String> output = ReadFile (*filename); - return scope.Close(output); -} - static void OnFatalError (const char* location, const char* message) { @@ -85,45 +173,6 @@ OnFatalError (const char* location, const char* message) } -// Extracts a C string from a V8 Utf8Value. -const char* ToCString(const v8::String::Utf8Value& value) { - return *value ? *value : "<string conversion failed>"; -} - -void ReportException(v8::TryCatch* try_catch) { - v8::HandleScope handle_scope; - v8::String::Utf8Value exception(try_catch->Exception()); - const char* exception_string = ToCString(exception); - v8::Handle<v8::Message> message = try_catch->Message(); - if (message.IsEmpty()) { - // V8 didn't provide any extra information about this error; just - // print the exception. - printf("%s\n", exception_string); - } else { - message->PrintCurrentStackTrace(stdout); - - // Print (filename):(line number): (message). - v8::String::Utf8Value filename(message->GetScriptResourceName()); - const char* filename_string = ToCString(filename); - int linenum = message->GetLineNumber(); - printf("%s:%i: %s\n", filename_string, linenum, exception_string); - // Print line of source code. - v8::String::Utf8Value sourceline(message->GetSourceLine()); - const char* sourceline_string = ToCString(sourceline); - printf("%s\n", sourceline_string); - // Print wavy underline (GetUnderline is deprecated). - int start = message->GetStartColumn(); - for (int i = 0; i < start; i++) { - printf(" "); - } - int end = message->GetEndColumn(); - for (int i = start; i < end; i++) { - printf("^"); - } - printf("\n"); - } -} - void node_fatal_exception (TryCatch &try_catch) { @@ -132,60 +181,12 @@ node_fatal_exception (TryCatch &try_catch) exit_code = 1; } - -// Executes a string within the current v8 context. -bool ExecuteString(v8::Handle<v8::String> source, - v8::Handle<v8::Value> name, - bool print_result, - bool report_exceptions) { - v8::HandleScope handle_scope; - v8::TryCatch try_catch; - v8::Handle<v8::Script> script = v8::Script::Compile(source, name); - if (script.IsEmpty()) { - // Print errors that happened during compilation. - if (report_exceptions) - ReportException(&try_catch); - return false; - } else { - v8::Handle<v8::Value> result = script->Run(); - if (result.IsEmpty()) { - // Print errors that happened during execution. - if (report_exceptions) - ReportException(&try_catch); - return false; - } else { - if (print_result && !result->IsUndefined()) { - // If all went well and the result wasn't undefined then print - // the returned value. - v8::String::Utf8Value str(result); - const char* cstr = ToCString(str); - printf("%s\n", cstr); - } - return true; - } - } +void node_exit (int code) +{ + exit_code = code; + ev_unloop(node_loop(), EVUNLOOP_ALL); } -// The callback that is invoked by v8 whenever the JavaScript 'load' -// function is called. Loads, compiles and executes its argument -// JavaScript file. -v8::Handle<v8::Value> Load(const v8::Arguments& args) { - for (int i = 0; i < args.Length(); i++) { - v8::HandleScope handle_scope; - v8::String::Utf8Value file(args[i]); - if (*file == NULL) { - return v8::ThrowException(v8::String::New("Error loading file")); - } - v8::Handle<v8::String> source = ReadFile(*file); - if (source.IsEmpty()) { - return v8::ThrowException(v8::String::New("Error loading file")); - } - if (!ExecuteString(source, v8::String::New(*file), false, true)) { - return v8::ThrowException(v8::String::New("Error executing file")); - } - } - return v8::Undefined(); -} static ev_async thread_pool_watcher; @@ -212,49 +213,59 @@ node_eio_submit(eio_req *req) int main (int argc, char *argv[]) { + // start eio thread pool + ev_async_init(&thread_pool_watcher, thread_pool_cb); + eio_init(thread_pool_want_poll, NULL); + V8::SetFlagsFromCommandLine(&argc, argv, true); - if(argc != 2) { + if(argc < 2) { fprintf(stderr, "No script was specified.\n"); return 1; } - string filename(argv[1]); HandleScope handle_scope; Persistent<Context> context = Context::New(NULL, ObjectTemplate::New()); Context::Scope context_scope(context); + V8::SetFatalErrorHandler(OnFatalError); Local<Object> g = Context::GetCurrent()->Global(); - g->Set ( String::New("log") - , FunctionTemplate::New(Log)->GetFunction() - ); + Local<Object> node = Object::New(); + g->Set(String::New("node"), node); - g->Set ( String::New("load") - , FunctionTemplate::New(Load)->GetFunction() - ); + Local<Object> blocking = Object::New(); + node->Set(String::New("blocking"), blocking); - g->Set ( String::New("blockingFileRead") - , FunctionTemplate::New(BlockingFileRead)->GetFunction() - ); + JS_SET_METHOD(blocking, "exec", exec); + JS_SET_METHOD(blocking, "cat", cat); + JS_SET_METHOD(blocking, "print", print); + Local<Array> arguments = Array::New(argc); + for (int i = 0; i < argc; i++) { + Local<String> arg = String::New(argv[i]); + arguments->Set(Integer::New(i), arg); + } + g->Set(String::New("ARGV"), arguments); + + // BUILT-IN MODULES Init_timer(g); - Init_tcp(g); + //NodeInit_net(g); + NodeInit_process(g); + NodeInit_file(g); Init_http(g); - V8::SetFatalErrorHandler(OnFatalError); - - v8::Handle<v8::String> source = ReadFile(filename); - - // start eio thread pool - ev_async_init(&thread_pool_watcher, thread_pool_cb); - ev_async_start(EV_DEFAULT_ &thread_pool_watcher); - eio_init(thread_pool_want_poll, NULL); - - ExecuteString(source, String::New(filename.c_str()), false, true); + // NATIVE JAVASCRIPT MODULES + TryCatch try_catch; + Handle<Value> result = ExecuteString(String::New(native_main), + String::New("main.js")); + if (try_catch.HasCaught()) { + ReportException(&try_catch); + return 1; + } ev_loop(node_loop(), 0); @@ -262,4 +273,3 @@ main (int argc, char *argv[]) return exit_code; } - diff --git a/src/node.h b/src/node.h index 3d075a9abd..3547a403d7 100644 --- a/src/node.h +++ b/src/node.h @@ -5,8 +5,15 @@ #include <eio.h> #include <v8.h> +#define JS_SYMBOL(name) v8::String::NewSymbol(name) +#define JS_METHOD(name) v8::Handle<v8::Value> jsmethod_##name (const v8::Arguments& args) +#define JS_SET_METHOD(obj, name, callback) \ + obj->Set(JS_SYMBOL(name), v8::FunctionTemplate::New(jsmethod_##callback)->GetFunction()) + + void node_fatal_exception (v8::TryCatch &try_catch); #define node_loop() ev_default_loop(0) +void node_exit (int code); // call this after creating a new eio event. void node_eio_submit(eio_req *req); diff --git a/src/node_tcp.cc b/src/node_tcp.cc deleted file mode 100644 index febbe106fa..0000000000 --- a/src/node_tcp.cc +++ /dev/null @@ -1,341 +0,0 @@ -#include "node_tcp.h" -#include "node.h" - -#include <oi_socket.h> -#include <oi_buf.h> - -#include <assert.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> - -using namespace v8; - -static Persistent<String> readyState_str; - -static Persistent<Integer> readyState_CONNECTING; -static Persistent<Integer> readyState_OPEN; -static Persistent<Integer> readyState_CLOSED; - -enum readyState { READY_STATE_CONNECTING = 0 - , READY_STATE_OPEN = 1 - , READY_STATE_CLOSED = 2 - } ; - -class TCPClient { -public: - TCPClient(Handle<Object> obj); - ~TCPClient(); - - int Connect(char *host, char *port); - void Write (Handle<Value> arg); - void Disconnect(); - - void OnOpen(); - void OnRead(const void *buf, size_t count); - void OnClose(); - -private: - int ReadyState(); - oi_socket socket; - struct addrinfo *address; - Persistent<Object> js_client; -}; - - -static void -on_connect (oi_socket *socket) -{ - TCPClient *client = static_cast<TCPClient*> (socket->data); - client->OnOpen(); -} - -static void -on_close (oi_socket *socket) -{ - TCPClient *client = static_cast<TCPClient*> (socket->data); - client->OnClose(); -} - -static void -on_read (oi_socket *socket, const void *buf, size_t count) -{ - TCPClient *client = static_cast<TCPClient*> (socket->data); - client->OnRead(buf, count); -} - -static struct addrinfo tcp_hints = -/* ai_flags */ { AI_PASSIVE -/* ai_family */ , AF_UNSPEC -/* ai_socktype */ , SOCK_STREAM -/* ai_protocol */ , 0 -/* ai_addrlen */ , 0 -/* ai_addr */ , 0 -/* ai_canonname */ , 0 -/* ai_next */ , 0 - }; - -static Handle<Value> newTCPClient - ( const Arguments& args - ) -{ - if (args.Length() < 1) - return Undefined(); - - HandleScope scope; - - char *host = NULL; - String::AsciiValue host_v(args[0]->ToString()); - if(args[0]->IsString()) { - host = *host_v; - } - String::AsciiValue port(args[1]); - - TCPClient *client = new TCPClient(args.This()); - if(client == NULL) - return Undefined(); // XXX raise error? - - int r = client->Connect(host, *port); - if (r != 0) - return Undefined(); // XXX raise error? - - - return args.This(); -} - -static TCPClient* -UnwrapClient (Handle<Object> obj) -{ - HandleScope scope; - Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); - TCPClient* client = static_cast<TCPClient*>(field->Value()); - return client; -} - -static Handle<Value> -WriteCallback (const Arguments& args) -{ - HandleScope scope; - TCPClient *client = UnwrapClient(args.Holder()); - client->Write(args[0]); - return Undefined(); -} - -static Handle<Value> -DisconnectCallback (const Arguments& args) -{ - HandleScope scope; - TCPClient *client = UnwrapClient(args.Holder()); - client->Disconnect(); - return Undefined(); -} - -static void -client_destroy (Persistent<Value> _, void *data) -{ - TCPClient *client = static_cast<TCPClient *> (data); - delete client; -} - -TCPClient::TCPClient(Handle<Object> _js_client) -{ - oi_socket_init(&socket, 300.0); // TODO adjustable timeout - socket.on_connect = on_connect; - socket.on_read = on_read; - socket.on_drain = NULL; - socket.on_error = NULL; - socket.on_close = on_close; - socket.on_timeout = on_close; - socket.data = this; - - HandleScope scope; - js_client = Persistent<Object>::New(_js_client); - js_client->SetInternalField (0, External::New(this)); - js_client.MakeWeak (this, client_destroy); -} - -TCPClient::~TCPClient () -{ - Disconnect(); - oi_socket_detach (&socket); - js_client.Dispose(); - js_client.Clear(); // necessary? -} - -int -TCPClient::Connect(char *host, char *port) -{ - int r; - - HandleScope scope; - - js_client->Set(readyState_str, readyState_CONNECTING); - - /* FIXME Blocking DNS resolution. Use oi_async. */ - printf("resolving host: %s, port: %s\n", host, port); - r = getaddrinfo (host, port, &tcp_hints, &address); - if(r != 0) { - perror("getaddrinfo"); - return r; - } - - r = oi_socket_connect (&socket, address); - if(r != 0) { - perror("oi_socket_connect"); - return r; - } - oi_socket_attach (&socket, node_loop()); - - freeaddrinfo(address); - address = NULL; -} - -void TCPClient::Write (Handle<Value> arg) -{ - HandleScope scope; - - // - // TODO if ReadyState() is not READY_STATE_OPEN then raise INVALID_STATE_ERR - // - - if(arg == Null()) { - - oi_socket_write_eof(&socket); - - } else { - Local<String> s = arg->ToString(); - - size_t l1 = s->Utf8Length(), l2; - oi_buf *buf = oi_buf_new2(l1); - l2 = s->WriteUtf8(buf->base, l1); - assert(l1 == l2); - - oi_socket_write(&socket, buf); - } -} - -int -TCPClient::ReadyState() -{ - return js_client->Get(readyState_str)->IntegerValue(); -} - -void -TCPClient::Disconnect() -{ - oi_socket_close(&socket); -} - -void -TCPClient::OnOpen() -{ - HandleScope scope; - - assert(READY_STATE_CONNECTING == ReadyState()); - js_client->Set(readyState_str, readyState_OPEN); - - Handle<Value> onopen_value = js_client->Get( String::NewSymbol("onopen") ); - if (!onopen_value->IsFunction()) - return; - Handle<Function> onopen = Handle<Function>::Cast(onopen_value); - - TryCatch try_catch; - - Handle<Value> r = onopen->Call(js_client, 0, NULL); - - if(try_catch.HasCaught()) - node_fatal_exception(try_catch); -} - -void -TCPClient::OnRead(const void *buf, size_t count) -{ - HandleScope scope; - - assert(READY_STATE_OPEN == ReadyState()); - - Handle<Value> onread_value = js_client->Get( String::NewSymbol("onread") ); - if (!onread_value->IsFunction()) return; - Handle<Function> onread = Handle<Function>::Cast(onread_value); - - const int argc = 1; - Handle<Value> argv[argc]; - - if(count) { - Handle<String> chunk = String::New((const char*)buf, count); // TODO binary data? - argv[0] = chunk; - } else { - // TODO eof? delete write method? - argv[0] = Null(); - } - - TryCatch try_catch; - - Handle<Value> r = onread->Call(js_client, argc, argv); - - if(try_catch.HasCaught()) - node_fatal_exception(try_catch); -} - -void -TCPClient::OnClose() -{ - HandleScope scope; - - printf("onclose readyState %d\n", ReadyState()); - - assert(READY_STATE_OPEN == ReadyState()); - js_client->Set(readyState_str, readyState_CLOSED); - - Handle<Value> onclose_value = js_client->Get( String::NewSymbol("onclose") ); - if (!onclose_value->IsFunction()) return; - Handle<Function> onclose = Handle<Function>::Cast(onclose_value); - - TryCatch try_catch; - - Handle<Value> r = onclose->Call(js_client, 0, NULL); - - if(try_catch.HasCaught()) - node_fatal_exception(try_catch); -} - -void -Init_tcp (Handle<Object> target) -{ - HandleScope scope; - readyState_str = Persistent<String>::New(String::NewSymbol("readyState")); - - Local<FunctionTemplate> client_t = FunctionTemplate::New(newTCPClient); - - client_t->InstanceTemplate()->SetInternalFieldCount(1); - - /* readyState constants */ - - readyState_CONNECTING = Persistent<Integer>::New(Integer::New(READY_STATE_CONNECTING)); - client_t->Set ("CONNECTING", readyState_CONNECTING); - - readyState_OPEN = Persistent<Integer>::New(Integer::New(READY_STATE_OPEN)); - client_t->Set ("OPEN", readyState_OPEN); - - readyState_CLOSED = Persistent<Integer>::New(Integer::New(READY_STATE_CLOSED)); - client_t->Set ("CLOSED", readyState_CLOSED); - - /* write callback */ - - Local<FunctionTemplate> write_t = FunctionTemplate::New(WriteCallback); - - client_t->InstanceTemplate()->Set ( String::NewSymbol("write") - , write_t->GetFunction() - ); - - /* disconnect callback */ - - Local<FunctionTemplate> disconnect_t = FunctionTemplate::New(DisconnectCallback); - - client_t->InstanceTemplate()->Set ( String::NewSymbol("disconnect") - , disconnect_t->GetFunction() - ); - - target->Set(String::NewSymbol("TCPClient"), client_t->GetFunction()); -} - diff --git a/src/node_tcp.h b/src/node_tcp.h deleted file mode 100644 index 4193e160fc..0000000000 --- a/src/node_tcp.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef node_tcp_h -#define node_tcp_h - -#include <v8.h> - -void Init_tcp (v8::Handle<v8::Object> target); - -#endif diff --git a/src/process.cc b/src/process.cc new file mode 100644 index 0000000000..db18ad29d7 --- /dev/null +++ b/src/process.cc @@ -0,0 +1,39 @@ +#include "process.h" +#include "node.h" +#include <v8.h> +#include <stdlib.h> + +using namespace v8; + +static Handle<Value> +ExitCallback (const Arguments& args) +{ + int exit_code = 0; + if (args.Length() > 0) exit_code = args[0]->IntegerValue(); + exit(exit_code); + return Undefined(); +} + +static Handle<Value> +OnCallback (const Arguments& args) +{ + return Undefined(); +} + +void +NodeInit_process (Handle<Object> target) +{ + HandleScope scope; + + Local<Object> process = ObjectTemplate::New()->NewInstance(); + + target->Set(String::NewSymbol("process"), process); + + // process.exit() + Local<FunctionTemplate> process_exit = FunctionTemplate::New(ExitCallback); + process->Set(String::NewSymbol("exit"), process_exit->GetFunction()); + + // process.on() + Local<FunctionTemplate> process_on = FunctionTemplate::New(OnCallback); + process->Set(String::NewSymbol("on"), process_exit->GetFunction()); +} diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000000..b7ea663ed2 --- /dev/null +++ b/src/process.h @@ -0,0 +1,8 @@ +#ifndef node_process_h +#define node_process_h + +#include <v8.h> + +void NodeInit_process (v8::Handle<v8::Object> target); + +#endif diff --git a/test/mjsunit.js b/test/mjsunit.js new file mode 100644 index 0000000000..7c5e38dbfb --- /dev/null +++ b/test/mjsunit.js @@ -0,0 +1,165 @@ +// Copyright 2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// + +/*jslint + evil: true +*/ + +function MjsUnitAssertionError(message) { + this.message = message; +} + +MjsUnitAssertionError.prototype.toString = function () { + return this.message; +}; + +/* + * This file is included in all mini jsunit test cases. The test + * framework expects lines that signal failed tests to start with + * the f-word and ignore all other lines. + */ + +function fail (expected, found, name_opt) { + var start; + if (name_opt) { + // Fix this when we ditch the old test runner. + start = "Fail" + "ure (" + name_opt + "): "; + } else { + start = "Fail" + "ure:"; + } + throw new MjsUnitAssertionError(start + " expected <" + expected + "> found <" + found + ">"); +}; + + +function deepEquals (a, b) { + if (a == b) { + return true; + } + if ((typeof a) !== 'object' || (typeof b) !== 'object' || + (a === null) || (b === null)) { + return false; + } + if (a.constructor === Array) { + if (b.constructor !== Array) { + return false; + } + if (a.length != b.length) { + return false; + } + for (var i = 0; i < a.length; i++) { + if (i in a) { + if (!(i in b) || !(deepEquals(a[i], b[i]))) { + return false; + } + } else if (i in b) { + return false; + } + } + return true; + } + return false; +}; + + +exports.assertEquals = function (expected, found, name_opt) { + if (!deepEquals(found, expected)) { + fail(expected, found, name_opt); + } +}; + + +exports.assertArrayEquals = function (expected, found, name_opt) { + var start = ""; + if (name_opt) { + start = name_opt + " - "; + } + exports.assertEquals(expected.length, found.length, start + "array length"); + if (expected.length == found.length) { + for (var i = 0; i < expected.length; ++i) { + exports.assertEquals(expected[i], found[i], start + "array element at index " + i); + } + } +}; + + +exports.assertTrue = function (value, name_opt) { + exports.assertEquals(true, value, name_opt); +}; + + +exports.assertFalse = function (value, name_opt) { + exports.assertEquals(false, value, name_opt); +}; + + +exports.assertNaN = function (value, name_opt) { + if (!isNaN(value)) { + fail("NaN", value, name_opt); + } +}; + + +exports.assertThrows = function (code) { + var threwException = true; + try { + eval(code); + threwException = false; + } catch (e) { + // Do nothing. + } + if (!threwException) { + exports.assertTrue(false, "did not throw exception"); + } +}; + + +exports.assertInstanceof = function (obj, type) { + if (!(obj instanceof type)) { + exports.assertTrue(false, "Object <" + obj + "> is not an instance of <" + type + ">"); + } +}; + + +exports.assertDoesNotThrow = function (code) { + try { + eval(code); + } catch (e) { + exports.assertTrue(false, "threw an exception"); + } +}; + + +exports.assertUnreachable = function (name_opt) { + // Fix this when we ditch the old test runner. + var message = "Fail" + "ure: unreachable"; + if (name_opt) { + message += " - " + name_opt; + } + throw new MjsUnitAssertionError(message); +}; diff --git a/test/test-test.js b/test/test-test.js new file mode 100644 index 0000000000..18a364f416 --- /dev/null +++ b/test/test-test.js @@ -0,0 +1,12 @@ +node.blocking.print(__file__); +/* +if (node.path.dirname(__file__) !== "test-test.js") { + throw "wrong __file__ argument"; +} +*/ + +var mjsunit = require("./mjsunit.js"); +node.blocking.print(__file__); + +mjsunit.assertFalse(false, "testing the test program."); +//mjsunit.assertEquals("test-test.js", __file__); @@ -1,8 +1,11 @@ #! /usr/bin/env python import Options +import sys import os from os.path import join, dirname, abspath +import js2c + VERSION='0.0.1' APPNAME='node' @@ -13,12 +16,12 @@ def set_options(opt): # the gcc module provides a --debug-level option opt.tool_options('compiler_cxx') opt.tool_options('compiler_cc') - opt.tool_options('ragel', tdir = '.') + opt.tool_options('ragel', tdir=".") def configure(conf): conf.check_tool('compiler_cxx') conf.check_tool('compiler_cc') - conf.check_tool('ragel', tooldir = '.') + conf.check_tool('ragel', tooldir=".") conf.sub_config('deps/libeio') conf.sub_config('deps/libev') @@ -42,6 +45,7 @@ def configure(conf): conf.define("HAVE_CONFIG_H", 1) conf.write_config_header('config.h') + def build(bld): bld.add_subdirs('deps/libeio deps/libev') @@ -53,7 +57,7 @@ def build(bld): v8lib = bld.env["staticlib_PATTERN"] % "v8" v8 = bld.new_task_gen( target=join("deps/v8",v8lib), - rule='cp -rf %s %s && cd %s && scons library=static' + rule='cp -rf %s %s && cd %s && scons -Q library=static snapshot=on' % ( v8dir_src , deps_tgt , v8dir_tgt @@ -80,13 +84,29 @@ def build(bld): ebb.name = "ebb" ebb.target = "ebb" + ### src/native.cc + def javascript_in_c(task): + env = task.env + source = map(lambda x: x.srcpath(env), task.inputs) + targets = map(lambda x: x.srcpath(env), task.outputs) + js2c.JS2C(source, targets) + + native_cc = bld.new_task_gen( + source="src/main.js", + target="src/natives.h", + rule=javascript_in_c, + before="cxx" + ) + + ### node node = bld.new_task_gen("cxx", "program") node.target = 'node' node.source = """ src/node.cc src/node_http.cc - src/node_tcp.cc + src/process.cc + src/file.cc src/node_timer.cc """ node.includes = """ |