From 63a9cd389773b0d986184f5989eeceb299b55ecd Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 15 Apr 2009 10:08:28 +0200 Subject: [PATCH] everything is changed. i've waited much too long to commit. this is awful. i'm sorry for being so messy. --- configure | 137 ++++++++++++++++++ js2c.py | 317 ++++++++++++++++++++++++++++++++++++++++ jsmin.py | 218 ++++++++++++++++++++++++++++ src/file.cc | 153 ++++++++++++++++++++ src/file.h | 8 + src/main.js | 85 +++++++++++ src/net.cc | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/net.h | 8 + src/node.cc | 296 +++++++++++++++++++------------------ src/node.h | 7 + src/node_tcp.cc | 341 ------------------------------------------- src/node_tcp.h | 8 - src/process.cc | 39 +++++ src/process.h | 8 + test/mjsunit.js | 165 +++++++++++++++++++++ test/test-test.js | 12 ++ wscript | 28 +++- 17 files changed, 1759 insertions(+), 496 deletions(-) create mode 100755 configure create mode 100755 js2c.py create mode 100644 jsmin.py create mode 100644 src/file.cc create mode 100644 src/file.h create mode 100644 src/main.js create mode 100644 src/net.cc create mode 100644 src/net.h delete mode 100644 src/node_tcp.cc delete mode 100644 src/node_tcp.h create mode 100644 src/process.cc create mode 100644 src/process.h create mode 100644 test/mjsunit.js create mode 100644 test/test-test.js diff --git a/configure b/configure new file mode 100755 index 0000000..0187691 --- /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 0000000..3126261 --- /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(%(id)s, %(length)i); +""" + + +GET_DELAY_SCRIPT_NAME_CASE = """\ + if (index == %(i)i) return Vector("%(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 0000000..ae75814 --- /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 0000000..6638f07 --- /dev/null +++ b/src/file.cc @@ -0,0 +1,153 @@ +#include "node.h" +#include + +using namespace v8; + +class Callback { + public: + Callback(Handle v); + ~Callback(); + Local Call(Handle recv, int argc, Handle argv[]); + private: + Persistent handle; +}; + + +Callback::Callback (Handle v) +{ + HandleScope scope; + Handle f = Handle::Cast(v); + handle = Persistent::New(f); +} + +Callback::~Callback () +{ + handle.Dispose(); + handle.Clear(); // necessary? +} + +Local +Callback::Call (Handle recv, int argc, Handle argv[]) +{ + HandleScope scope; + Local r = handle->Call(recv, argc, argv); + return scope.Close(r); +} + +static int +after_rename (eio_req *req) +{ + Callback *callback = static_cast(req->data); + if (callback != NULL) { + HandleScope scope; + const int argc = 2; + Local 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(req->data); + if (callback != NULL) { + HandleScope scope; + const int argc = 3; + Local argv[argc]; + + Local 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(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(s->st_atime))); + /* time of last modification */ + stats->Set(JS_SYMBOL("mtime"), Date::New(1000*static_cast(s->st_mtime))); + /* time of last status change */ + stats->Set(JS_SYMBOL("ctime"), Date::New(1000*static_cast(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 target) +{ + HandleScope scope; + + Local 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 0000000..75fddb7 --- /dev/null +++ b/src/file.h @@ -0,0 +1,8 @@ +#ifndef node_file_h +#define node_file_h + +#include + +void NodeInit_file (v8::Handle target); + +#endif diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..ae847e7 --- /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 0000000..b381ed9 --- /dev/null +++ b/src/net.cc @@ -0,0 +1,425 @@ +#include "net.h" +#include "node.h" + +#include +#include + +#include +#include +#include +#include +#include + +using namespace v8; + +static Persistent readyState_str; + +static Persistent readyStateCONNECTING; +static Persistent readyStateOPEN; +static Persistent readyStateCLOSED; + +enum encoding {UTF8, RAW}; + +class Socket { +public: + Socket (Handle obj, double timeout); + ~Socket (); + + int ConnectTCP (char *port, char *host); + void Write (Handle 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 js_object_; +}; + +static void +on_connect (oi_socket *socket) +{ + Socket *s = static_cast (socket->data); + s->OnConnect(); +} + +static void +on_read (oi_socket *socket, const void *buf, size_t count) +{ + Socket *s = static_cast (socket->data); + s->OnRead(buf, count); +} + +static void +on_drain (oi_socket *socket) +{ + Socket *s = static_cast (socket->data); + s->OnDrain(); +} + +static void +on_error (oi_socket *socket, oi_error e) +{ + Socket *s = static_cast (socket->data); + s->OnError(e); +} + +static void +on_timeout (oi_socket *socket) +{ + Socket *s = static_cast (socket->data); + s->OnTimeout(e); +} + +static void +on_close (oi_socket *socket) +{ + Socket *s = static_cast (socket->data); + s->OnClose(); +} + + +static Handle +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 options = args[0]->ToObject(); + Local timeout_value = options->Get(String::NewSymbol("timeout")); + Local 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 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 obj) +{ + HandleScope scope; + Handle field = Handle::Cast(obj->GetInternalField(0)); + Socket* socket = static_cast(field->Value()); + return socket; +} + +static Handle +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 +SocketWriteCallback (const Arguments& args) +{ + HandleScope scope; + Socket *socket = Unwrapsocket(args.Holder()); + socket->Write(args[0]); + return Undefined(); +} + +static Handle +SocketCloseCallback (const Arguments& args) +{ + HandleScope scope; + Socket *socket = Unwrapsocket(args.Holder()); + socket->Disconnect(); + return Undefined(); +} + +static void +DestroySocket (Persistent _, void *data) +{ + Socket *s = static_cast (data); + delete s; +} + +Socket::Socket(Handle 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::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 arg) +{ + HandleScope scope; + + if (arg == Null()) { + + oi_socket_write_eof(&socket); + + } else if (arg->IsString()) { + Local 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 = Handle::Cast(arg); + oi_buf *buf = oi_buf_new2(length); + for (int i = 0; i < length; i++) { + Local 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 on_connect_value = js_object_->Get( String::NewSymbol("on_connect") ); + if (!on_connect_value->IsFunction()) + return; + Handle on_connect = Handle::Cast(on_connect_value); + + TryCatch try_catch; + + Handle 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 onread_value = js_object_->Get( String::NewSymbol("on_read") ); + if (!onread_value->IsFunction()) return; + Handle onread = Handle::Cast(onread_value); + + const int argc = 1; + Handle argv[argc]; + + if(count) { + Handle 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 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 onclose_value = js_object_->Get( String::NewSymbol("on_close") ); + if (!onclose_value->IsFunction()) return; + Handle onclose = Handle::Cast(onclose_value); + + TryCatch try_catch; + + Handle r = onclose->Call(js_object_, 0, NULL); + + if(try_catch.HasCaught()) + node_fatal_exception(try_catch); +} + +void +NodeInit_net (Handle target) +{ + HandleScope scope; + + // + // Socket + // + Local socket_template = FunctionTemplate::New(NewSocket); + target->Set(String::NewSymbol("Socket"), socket_template->GetFunction()); + socket_template->InstanceTemplate()->SetInternalFieldCount(1); + + // socket.connectTCP() + Local socket_connect_tcp = + FunctionTemplate::New(SocketConnectTCPCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("connectTCP"), + socket_connect_tcp->GetFunction()); + + // socket.connectUNIX() + Local socket_connect_unix = + FunctionTemplate::New(SocketConnectUNIXCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("connectUNIX"), + socket_connect_unix->GetFunction()); + + // socket.write() + Local socket_write = + FunctionTemplate::New(SocketWriteCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("write"), + socket_write->GetFunction()); + + // socket.close() + Local socket_close = + FunctionTemplate::New(SocketCloseCallback); + socket_template->InstanceTemplate()->Set(String::NewSymbol("close"), + socket_close->GetFunction()); + + // + // Server + // + Local server_template = FunctionTemplate::New(NewServer); + target->Set(String::NewSymbol("Server"), server_template->GetFunction()); + server_template->InstanceTemplate()->SetInternalFieldCount(1); + + // server.listenTCP() + Local server_listenTCP = + FunctionTemplate::New(ServerListenTCPCallback); + server_template->InstanceTemplate()->Set(String::NewSymbol("listenTCP"), + server_listenTCP->GetFunction()); + + // server.listenUNIX() + Local server_listenUNIX = + FunctionTemplate::New(ServerListenUNIXCallback); + server_template->InstanceTemplate()->Set(String::NewSymbol("listenUNIX"), + server_listenTCP->GetFunction()); + + // server.close() + Local 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 0000000..347a3eb --- /dev/null +++ b/src/net.h @@ -0,0 +1,8 @@ +#ifndef node_net_h +#define node_net_h + +#include + +void NodeInit_net (v8::Handle target); + +#endif diff --git a/src/node.cc b/src/node.cc index e89b88c..07d31b2 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 #include @@ -12,20 +16,114 @@ #include using namespace v8; +using namespace node; using namespace std; static int exit_code = 0; -// Reads a file into a v8 string. -static Handle -ReadFile (const string& name) +// Extracts a C string from a V8 Utf8Value. +const char* +ToCString(const v8::String::Utf8Value& value) { + return *value ? *value : ""; +} - FILE* file = fopen(name.c_str(), "rb"); - if (file == NULL) return Handle(); +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 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 +ExecuteString(v8::Handle source, + v8::Handle filename) +{ + HandleScope scope; + Handle