# HG changeset patch # User Matthew Wild # Date 1287152237 -3600 # Node ID b2e55f320d48a2277e829232852799d268fab935 Initial commit diff -r 000000000000 -r b2e55f320d48 js2lua.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js2lua.lua Fri Oct 15 15:17:17 2010 +0100 @@ -0,0 +1,25 @@ +#!/usr/bin/env lua + +local jslex = require "lib.jslex" +local js2lua = require "lib.js2lua" + +local stream = jslex.new_stream(io.open(arg[1])); + +local list = {}; + +local i, token_type, token_value = 0, stream.get_token(); +while token_type do + i = i + 1; + list[i] = { type = token_type, value = token_value }; + +-- print("Line "..(stream.line or 1)..":", token_type, token_value); + token_type, token_value = stream.get_token(); +end + +io.stderr:write("js2lua: Translating...\n"); +local d = {}; +local function w(t) table.insert(d, t); end +js2lua(list, w); +io.stderr:write("===== Result ======\n", table.concat(d), "\n===== ====== =====\n"); +io.stderr:write("js2lua: Running...\n"); +assert(loadstring(table.concat(d)))(); diff -r 000000000000 -r b2e55f320d48 jslextest.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jslextest.lua Fri Oct 15 15:17:17 2010 +0100 @@ -0,0 +1,9 @@ +require "jslex" + +local stream = jslex.new_stream(io.open(arg[1])); + +local token_type, token_value = stream.get_token(); +while token_type do + print("Token:", token_type, token_value); + token_type, token_value = stream.get_token(); +end diff -r 000000000000 -r b2e55f320d48 lib/js2lua.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/js2lua.lua Fri Oct 15 15:17:17 2010 +0100 @@ -0,0 +1,113 @@ +local t = { op = {}, keyword = {}, name = {}, eos = {}, string = {}, regex = {} }; + +local debug = function (...) io.stderr:write(table.concat({...}, "\t")); io.stderr:write"\n";end; +local debug = function () end + +local function read_balanced(tokens, start, left, right) + local c = 0; + for i=start,#tokens do + if tokens[i].type == "op" then + local token = tokens[i].value; + if token == left then + c = c + 1; + elseif token == right then + c = c - 1; + end + end + if c == 0 then return i; end + end + return start; +end + +local pair_op = { ["("] = ")", ["{"] = "}", ["["] = "]" }; +local function read_balanced_to(tokens, start, to) + local c = 0; + local i = start; + while i <= #tokens do + if tokens[i].type == "op" or tokens[i].type == "eos" then + local token = tokens[i].value; + if token == to then return i; end + + if pair_op[token] then + i = read_balanced(tokens, i, token, pair_op[token]); + end + end + i = i + 1; + end + return nil; +end + +function js2lua(tokens, write) + +-- Scan + local i = 1; + while i <= #tokens do + local token = tokens[i]; + debug(token.type, token.value); + if token.type == "keyword" and token.value == "function" then + local j = read_balanced_to(tokens, i+3, ")"); -- Find matching ) + j = j + 1; -- j now points to { + local k = read_balanced_to(tokens, j+1, "}"); + table.remove(tokens, j); -- Remove { + tokens[k-1].type, tokens[k-1].value = "keyword", "end"; + elseif token.type == "keyword" and token.value == "if" then + table.remove(tokens, i+1); -- Remove ( + local j = read_balanced_to(tokens, i, ")"); + tokens[j].type, tokens[j].value = "keyword", "then"; + + -- Make sure to end a single-statement block + if tokens[j+1].type ~= "op" or tokens[j+1].value ~= "{" then + local eos = read_balanced_to(tokens, j+1, ";"); + if tokens[eos+1].type ~= "keyword" or tokens[eos+1].value ~= "else" then + table.insert(tokens, eos+1, { type = "keyword", value = "end" }); + end + end + elseif token.type == "keyword" and token.value == "else" then + if tokens[i+1].type == "keyword" and tokens[i+1].value == "if" then + token.value = "elseif"; + table.remove(tokens, i+1); + + table.remove(tokens, i+1); -- Remove ( + local j = read_balanced_to(tokens, i, ")"); + tokens[j].type, tokens[j].value = "keyword", "then"; + end + + -- Make sure to end a single-statement block + if tokens[i+1].type ~= "op" or tokens[i+1].value ~= "{" then + local eos = read_balanced_to(tokens, i+1, ";"); + if tokens[eos+1].type ~= "keyword" or tokens[eos+1].value ~= "else" then + table.insert(tokens, eos+1, { type = "keyword", value = "end" }); + end + end + elseif token.type == "op" and token.value == "+" and tokens[i-1].type == "string" then + token.value = ".."; + elseif token.type == "op" and token.value == "{" then + elseif token.type == "keyword" and token.value == "var" then + token.value = "local"; + end + i = i + 1; + end + +-- Serialize + local last_token_type; + for _, token in ipairs(tokens) do + if token.type == "string" then + write("\""); + elseif token.type == "name" then + if last_token_type == "keyword" or last_token_type == "name" or last_token_type == "number" then + write(" "); + end + elseif token.type == "keyword" then + if last_token_type == "keyword" or last_token_type == "name" or last_token_type == "number" then + write(" "); + end + end + write(token.value); + if token.type == "string" then + write("\""); + end + last_token_type = token.type; + end +end + +return js2lua; diff -r 000000000000 -r b2e55f320d48 lib/jslex.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/jslex.lua Fri Oct 15 15:17:17 2010 +0100 @@ -0,0 +1,175 @@ +module("jslex", package.seeall); + +function peek_char(stream) + return stream.next_char; +end + +function read_char(stream, param) + local c = stream.next_char; + stream.next_char = stream.file:read(1); + if c == "\n" then stream.line = (stream.line or 1) + 1; end + return c; +end + +function read_until(stream, char) + local r = {}; + while stream.next_char and stream.next_char ~= char do + r[#r+1] = read_char(stream); + end + return table.concat(r); +end + +function read_until_match(stream, pattern) + local r = {}; + while stream.next_char and not stream.next_char:match(pattern) do + r[#r+1] = read_char(stream); + end + return table.concat(r); +end + +local keywords = { + "abstract", "as", "break", "case", "catch", + "class", "const", "continue", "debugger", + "default", "delete", "do", "else", "enum", + "export", "extends", "false", "final", + "finally", "for", "function", "goto", "if", + "implements", "import", "in", "instanceof", + "interface", "is", "namespace", "native", + "new", "null", "package", "private", + "protected", "public", "return", "static", + "super", "switch", "synchronized", "this", + "throw", "throws", "transient", "true", + "try", "typeof", "use", "var", "volatile", + "while", "with" +}; + +for _, k in ipairs(keywords) do keywords[k] = true; end +local function is_keyword(name) + return keywords[name]; +end + +local operators = "+-/*(),={}.&|<>![]:?"; + +local function push_token(stream, token_type, token_value) + stream.last_token_type = token_type; + coroutine.yield(token_type, token_value); +end + +local handlers = { + -- Whitespace + function (stream) + local c = peek_char(stream); + if c:match("%s") then + read_until_match(stream, "%S"); + return true; + end + end; + -- Strings + function (stream) + local c = peek_char(stream); + if c == [["]] or c == [[']] then + read_char(stream); -- Use up the string marker + push_token(stream, "string", read_until(stream, c)); + read_char(stream); -- Use up the string terminator + return true; + end + end; + -- Identifiers + function (stream) + local c = peek_char(stream); + if c:match("[_a-zA-Z$]") then + local name = read_until_match(stream, "[^a-zA-Z0-9_$]"); + if is_keyword(name) then + push_token(stream, "keyword", name); + else + push_token(stream, "name", name); + end + return true; + end + end; + --Numbers + function (stream) + local c = peek_char(stream); + if c:match("%d") then + push_token(stream, "number", read_until_match(stream, "[^%d%.]")); + return true; + end + end; + -- Operators (and comments!) + function (stream) + local c = peek_char(stream); + if operators:match("%"..c) then + local op, c = read_char(stream), peek_char(stream); + if op == "/" and (c == "/" or c == "*") then -- A comment + if c == "/" then -- Comment until end of line + read_until(stream, "\n"); + return true; + else + while true do + read_until(stream, "*"); + read_char(stream); -- Read "*" + if peek_char(stream) == "/" then + read_char(stream); -- Read / + return true; + elseif peek_char(stream) == nil then + return true; + end + end + end + elseif op == "/" and stream.last_token_type == "op" then -- Regex + local regex = read_until(stream, "/"); + read_char(stream); + local flags = read_until_match(stream, "%A"); + push_token(stream, "regex", op..regex.."/"..flags); + read_char(stream); + elseif op == "=" and (c == "=") then -- Equality + op = "=="; + read_char(stream); + if peek_char(stream) == "=" then + op = "==="; + read_char(stream); + end + elseif op:match("[&|]") and c == op then + op = op:rep(2); + read_char(stream); + elseif op == "!" and (c == "=") then + op = "!="; + read_char(stream); + end + push_token(stream, "op", op); + return true; + end + end; + -- Semi-colons + function (stream) + local c = peek_char(stream); + if c == ";" then + read_char(stream); + push_token(stream, "eos", c); + return true; + end + end; +}; + + +function new_stream(file) + local stream = { file = file; }; + stream.next_char = stream.file:read(1); + + stream.get_token = coroutine.wrap( + function () + while stream.next_char do + local handled; + for _, handler in ipairs(handlers) do + handled = handler(stream) or handled; + if not stream.next_char then break; end + end + if not handled then error("Unexpected character on line "..(stream.line or 1)..": "..stream.next_char); end + end + end); + + + return stream; +end + +return _M; diff -r 000000000000 -r b2e55f320d48 test.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test.js Fri Oct 15 15:17:17 2010 +0100 @@ -0,0 +1,15 @@ + +function hello(i) +{ + var foo = i; + if(foo == 1) + return "Hello world! " + foo; + else if(foo == 2) + return "Haha!"; + else + return "Goodbye!"; +} + +print(hello(1)); +print(hello(2)); +print(hello(3));