From 8e070bb698e63f4957d2f2cc5f38f7b07b283a5a Mon Sep 17 00:00:00 2001 From: cxl Date: Thu, 5 Jul 2012 17:57:50 +0000 Subject: [PATCH] uppsrc: Skylark moved from sandbox git-svn-id: svn://ultimatepp.org/upp/trunk@5125 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/Skylark/App.cpp | 288 ++++++++++++++++++++++++ uppsrc/Skylark/Base.witz | 14 ++ uppsrc/Skylark/Compile.cpp | 398 ++++++++++++++++++++++++++++++++++ uppsrc/Skylark/Dispatch.cpp | 374 ++++++++++++++++++++++++++++++++ uppsrc/Skylark/Exe.cpp | 396 +++++++++++++++++++++++++++++++++ uppsrc/Skylark/Http.cpp | 340 +++++++++++++++++++++++++++++ uppsrc/Skylark/Http.h | 142 ++++++++++++ uppsrc/Skylark/Optimize.cpp | 156 +++++++++++++ uppsrc/Skylark/Preprocess.cpp | 126 +++++++++++ uppsrc/Skylark/Renderer.cpp | 76 +++++++ uppsrc/Skylark/Session.cpp | 152 +++++++++++++ uppsrc/Skylark/Skylark.h | 90 ++++++++ uppsrc/Skylark/Skylark.upp | 45 ++++ uppsrc/Skylark/Sql.cpp | 56 +++++ uppsrc/Skylark/Static.icpp | 46 ++++ uppsrc/Skylark/StdLib.icpp | 47 ++++ uppsrc/Skylark/Witz.h | 228 +++++++++++++++++++ uppsrc/Skylark/init | 13 ++ uppsrc/Skylark/skylark.js | 85 ++++++++ 19 files changed, 3072 insertions(+) create mode 100644 uppsrc/Skylark/App.cpp create mode 100644 uppsrc/Skylark/Base.witz create mode 100644 uppsrc/Skylark/Compile.cpp create mode 100644 uppsrc/Skylark/Dispatch.cpp create mode 100644 uppsrc/Skylark/Exe.cpp create mode 100644 uppsrc/Skylark/Http.cpp create mode 100644 uppsrc/Skylark/Http.h create mode 100644 uppsrc/Skylark/Optimize.cpp create mode 100644 uppsrc/Skylark/Preprocess.cpp create mode 100644 uppsrc/Skylark/Renderer.cpp create mode 100644 uppsrc/Skylark/Session.cpp create mode 100644 uppsrc/Skylark/Skylark.h create mode 100644 uppsrc/Skylark/Skylark.upp create mode 100644 uppsrc/Skylark/Sql.cpp create mode 100644 uppsrc/Skylark/Static.icpp create mode 100644 uppsrc/Skylark/StdLib.icpp create mode 100644 uppsrc/Skylark/Witz.h create mode 100644 uppsrc/Skylark/init create mode 100644 uppsrc/Skylark/skylark.js diff --git a/uppsrc/Skylark/App.cpp b/uppsrc/Skylark/App.cpp new file mode 100644 index 000000000..f4b1f0f27 --- /dev/null +++ b/uppsrc/Skylark/App.cpp @@ -0,0 +1,288 @@ +#include "Skylark.h" +#ifdef PLATFORM_POSIX +#include +#endif + +#ifdef PLATFORM_WIN32 +#include +#endif + +#define CONSOLE(x) Cout() << x << '\n' + +#ifdef PLATFORM_WIN32 +BOOL WINAPI SkylarkApp::CtrlCHandlerRoutine(__in DWORD dwCtrlType) +{ + LOG("Ctrl+C handler"); + TheApp().quit = true; + Cout() << "Ctrl + C\n"; + TcpSocket h; + h.Connect("127.0.0.1", TheApp().port); + h.Put("quit"); + return TRUE; +} +#endif + +void SkylarkApp::WorkThread() +{ + RunThread(); +} + +void SkylarkApp::ThreadRun() +{ + WorkThread(); +} + +void SkylarkApp::RunThread() +{ + SQL.ClearError(); + SQLR.ClearError(); + SQL.GetSession().ThrowOnError(); + SQLR.GetSession().ThrowOnError(); + + for(;;) { + TcpSocket request; + accept_mutex.Enter(); + if(quit) { + accept_mutex.Leave(); + break; + } + bool b = request.Timeout(2000).Accept(server); + accept_mutex.Leave(); + if(quit) + break; + if(b) { + CONSOLE("Accepted " << Thread::GetCurrentId()); + #ifdef PLATFORM_POSIX + if(prefork) + alarm(timeout); + #endif + Http http(*this); + http.Dispatch(request); + #ifdef PLATFORM_POSIX + if(prefork) + alarm(0); + #endif + CONSOLE("Finished " << Thread::GetCurrentId()); + } + else + CONSOLE("Waiting " << Thread::GetCurrentId()); + } +} + +void SkylarkApp::Main() +{ + Buffer uwt(threads); + for(int i = 0; i < threads; i++) + Thread::Start(THISBACK(ThreadRun)); + + while(Thread::GetCount()) { + if(getpid() == main_pid && (msecs() % 1000) == 0) + ThreadRun(); + Sleep(100); + } +} + +void SkylarkApp::Broadcast(int signal) +{ +#ifdef PLATFORM_POSIX + if(getpid() == main_pid) + for(int i = 0; i < child_pid.GetCount(); i++) + kill(child_pid[i], signal); +#endif +} + +void SkylarkApp::Signal(int signal) +{ +#ifdef PLATFORM_POSIX + switch(signal) { + case SIGTERM: + case SIGHUP: + quit = true; + Broadcast(signal); + break; + case SIGINT: + Broadcast(signal); + exit(0); + break; + case SIGALRM: + if(getpid() != TheApp().main_pid) { + // "Timeout - session stoped" + exit(0); + } + break; + } +#endif +} + +void SkylarkApp::SignalHandler(int signal) +{ + TheApp().Signal(signal); +} + +void DisableHUP() +{ +#ifdef PLATFORM_POSIX + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGHUP); + sigprocmask(SIG_BLOCK, &mask, NULL); +#endif +} + +void EnableHUP() +{ +#ifdef PLATFORM_POSIX + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGHUP); + sigprocmask(SIG_UNBLOCK, &mask, NULL); +#endif +} + +void SkylarkApp::Run() +{ +// DisableHUP(); + SqlSession::PerThread(); + SqlId::UseQuotes(); + FinalizeViews(); + +#ifdef PLATFORM_WIN32 + SetConsoleCtrlHandler(CtrlCHandlerRoutine, true); +#endif + + main_pid = getpid(); + quit = false; + +#if defined(PLATFORM_POSIX) && defined(_DEBUG) + // Avoid the need to close running server in debug mode... + String pidf = ConfigFile("debug_pid"); + int prev_pid = atoi(LoadFile(pidf)); + if(prev_pid) { + kill(prev_pid, SIGTERM); + int status = 0; + waitpid(prev_pid, &status, 0); + } + for(int i = 0; i < 100; i++) { + Cout() << i; + if(server.Listen(port, 5)) + goto listening; + Sleep(10); + } + LOG("Cannot open server socket!"); + Cout() << "Cannot open server socket!\n"; + return; +listening:; +#else + if(!server.Listen(port, 5)) { + LOG("Cannot open server socket!"); + Cout() << "Cannot open server socket!\n"; + return; + } +#endif + +#ifdef PLATFORM_POSIX +#ifdef _DEBUG + SaveFile(pidf, AsString(main_pid)); +#endif + if(prefork) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SignalHandler; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGALRM, &sa, NULL); +// EnableHUP(); + while(!quit) { + while(child_pid.GetCount() < prefork && !quit) { + pid_t p = fork(); + if(p == 0) { + Main(); + return; + } + else + if(p > 0) + child_pid.Add(p); + else { + // "cant create new process" + Broadcast(SIGINT); + abort(); + } + } + int status = 0; + pid_t p = wait(&status); + if(p > 0) { + int q = FindIndex(child_pid, p); + if(q >= 0) + child_pid.Remove(q); + } + } + + Broadcast(SIGTERM); + int status = 0; + for(int i = 0; i < child_pid.GetCount(); i++) + waitpid(child_pid[i], &status, 0); + // "server stopped"; + } + else +#endif + Main(); + +#if defined(_DEBUG) && defined(POSIX) + FileDelete(pidf); +#endif + + CONSOLE("ExitSkylark"); +} + + +void SkylarkApp::SqlError(Http& http) +{ +} + +void SkylarkApp::InternalError(Http& http) +{ +} + +void SkylarkApp::NotFound(Http& http) +{ +} + +void SkylarkApp::Unauthorized(Http& http) +{ +} + +SkylarkApp *SkylarkApp::app; + +SkylarkApp& SkylarkApp::TheApp() +{ + ASSERT(app); + return *app; +} + +const SkylarkConfig& SkylarkApp::Config() +{ + ASSERT(app); + return *app; +} + +SkylarkApp::SkylarkApp() +{ + ASSERT(!app); + app = this; + threads = 3 * CPU_Cores() + 1; + port = 8001; + use_caching = true; +#ifdef _DEBUG + prefork = 0; + timeout = 0; +#else + prefork = 1; + timeout = 300; +#endif +} + +SkylarkApp::~SkylarkApp() +{ + app = NULL; +} diff --git a/uppsrc/Skylark/Base.witz b/uppsrc/Skylark/Base.witz new file mode 100644 index 000000000..db75dc449 --- /dev/null +++ b/uppsrc/Skylark/Base.witz @@ -0,0 +1,14 @@ + + + + + #TITLE + + #HEAD + + + #BODY + + + +#define TITLE Skylark application diff --git a/uppsrc/Skylark/Compile.cpp b/uppsrc/Skylark/Compile.cpp new file mode 100644 index 000000000..6bb61b616 --- /dev/null +++ b/uppsrc/Skylark/Compile.cpp @@ -0,0 +1,398 @@ +#include "Skylark.h" + +#define LLOG(x) // DLOG(x) + +Value Raw(const String& s) +{ + RawHtmlText r; + r.text = s; + return RawToValue(r); +} + +VectorMap&, const Renderer *)>& Compiler::functions() +{ + static VectorMap&, const Renderer *)> x; + return x; +} + +void Compiler::Register(const String& id, Value (*fn)(const Vector&, const Renderer *)) +{ + functions().GetAdd(id) = fn; +} + +int Compiler::ForVar(String id, int i) +{ + if(i + 1 < var.GetCount() && forvar[i]) + return i + 1; + p.ThrowError(id + " is not 'for' iterator"); + return 0; +} + +int CountLinkArgs(const Vector& part) +{ + int args = 0; + for(int i = 0; i < part.GetCount(); i++) { + int p = (byte)*part[i]; + if(p >= 0 && p < 30) + args = max(args, p + 1); + } + return args; +} + +One Compiler::Prim() +{ + One result; + if(p.Char('!')) + result = Create(Prim()); + else + if(p.Char('-')) + result = Create(Prim()); + else + if(p.Char('+')) + result = Prim(); + else + if(p.IsId()) { + String id = p.ReadId(); + int n = var.Find(id); + if(p.Char('(')) { + Value (*f)(const Vector&, const Renderer *) = functions().Get(id, NULL); + if(!f) { + Vector *part = GetUrlViewLinkParts(id); + if(!part) + p.ThrowError("function nor link not found '" + id + "'"); + ExeLink& ln = result.Create(); + ln.part = part; + if(!p.Char(')')) { + do + ln.arg.AddPick(Exp()); + while(p.Char(',')); + p.PassChar(')'); + } + if(CountLinkArgs(*part) > ln.arg.GetCount()) + p.ThrowError("invalid number of link arguments '" + id + "'"); + } + else { + ExeFn& fn = result.Create(); + fn.fn = f; + if(!p.Char(')')) { + do + fn.arg.AddPick(Exp()); + while(p.Char(',')); + p.PassChar(')'); + } + while(p.Char('.')) { + One r; + ExeField& f = r.Create(); + f.value = result; + f.id = p.ReadId(); + result = r; + } + } + return result; + } + if(n < 0) { + Vector *part = GetUrlViewLinkParts(id); + ExeConst& c = result.Create(); + if(!part) { + return result; + } + String l = "\"/"; + for(int i = 0; i < (*part).GetCount(); i++) { + if(i) + l << '/'; + l << UrlEncode((*part)[i]); + } + l << '\"'; + c.value = Raw(l); + } + else + if(p.Char('.')) { + if(p.Id("_first")) + result.Create().var_index = ForVar(id, n); + else + if(p.Id("_last")) + result.Create().var_index = ForVar(id, n); + else + if(p.Id("_index")) + result.Create().var_index = ForVar(id, n); + else + if(p.Id("_key")) + result.Create().var_index = ForVar(id, n); + else { + result.Create().var_index = n; + do { + One r; + ExeField& f = r.Create(); + f.value = result; + f.id = p.ReadId(); + result = r; + } + while(p.Char('.')); + } + } + else + result.Create().var_index = n; + } + else + if(p.Char('{')) { + ExeMap& m = result.Create(); + do { + m.key.AddPick(Exp()); + p.PassChar(':'); + m.value.AddPick(Exp()); + } + while(p.Char(',')); + p.PassChar('}'); + } + else + if(p.Char('[')) { + ExeArray& m = result.Create(); + do { + m.item.AddPick(Exp()); + } + while(p.Char(',')); + p.PassChar(']'); + } + else + if(p.Char('(')) { + result = Exp(); + p.PassChar(')'); + } + else { + ExeConst& c = result.Create(); + if(p.Char2('0', 'x') || p.Char2('0', 'X')) + c.value = (int)p.ReadNumber(16); + else + if(p.Char('0')) + c.value = int(p.IsNumber() ? p.ReadNumber(8) : 0); + else + c.value = p.IsString() ? Value(p.ReadString()) : Value(p.ReadDouble()); + } + return result; +} + +One Compiler::Mul() +{ + One result = Prim(); + for(;;) + if(p.Char('*')) + result = Create(result, Prim()); + else + if(p.Char('/')) + result = Create(result, Prim()); + else + if(p.Char('%')) + result = Create(result, Prim()); + else + return result; +} + +One Compiler::Add() +{ + One result = Mul(); + for(;;) + if(p.Char('+')) + result = Create(result, Mul()); + else + if(p.Char('-')) + result = Create(result, Mul()); + else + return result; +} + +One Compiler::Shift() +{ + One result = Add(); + for(;;) + if(p.Char3('>', '>', '>')) + result = Create(result, Add()); + else + if(p.Char2('>', '>')) + result = Create(result, Add()); + else + if(p.Char2('<', '<')) + result = Create(result, Add()); + else + return result; +} + +One Compiler::Rel() +{ + One result = Shift(); + for(;;) + if(p.Char2('<', '=')) + result = Create(result, Shift()); + else + if(p.Char2('>', '=')) + result = Create(Shift(), result); + else + if(p.Char('<')) + result = Create(result, Shift()); + else + if(p.Char('>')) + result = Create(Shift(), result); + else + return result; +} + +One Compiler::Eq() +{ + One result = Rel(); + for(;;) + if(p.Char2('=', '=')) + result = Create(result, Rel()); + else + if(p.Char2('!', '=')) + result = Create(Rel(), result); + else + return result; +} + +One Compiler::And() +{ + One result = Eq(); + while(!p.IsChar2('&', '&') && p.Char('&')) + result = Create(result, Eq()); + return result; +} + +One Compiler::Xor() +{ + One result = And(); + while(p.Char('^')) + result = Create(result, And()); + return result; +} + +One Compiler::Or() +{ + One result = Xor(); + while(!p.IsChar2('|', '|') && p.Char('|')) + result = Create(result, Xor()); + return result; +} + +One Compiler::LogAnd() +{ + One result = Or(); + while(p.Char2('&', '&')) + result = Create(result, Or()); + return result; +} + +One Compiler::LogOr() +{ + One result = LogAnd(); + while(p.Char2('|', '|')) + result = Create(result, LogAnd()); + return result; +} + +One Compiler::Conditional() +{ + One result = LogOr(); + if(p.Char('?')) { + One r; + ExeCond& c = r.Create(); + c.cond = result; + c.ontrue = LogOr(); + p.PassChar(':'); + c.onfalse = LogOr(); + result = r; + } + return result; +} + +One Compiler::Exp() +{ + return Conditional(); +} + +void Compiler::ExeBlock::AddText(const char *b, const char *s) +{ + if(s > b) { + RawHtmlText t; + t.text = String(b, s); + item.Add().Create().value = RawToValue(t); + } +} + +One Compiler::Block() +{ + One result; + ExeBlock& blk = result.Create(); + const char *s = p.GetSpacePtr(); + const char *b = s; + int line = 1; + while(*s) { + if(*s == '$') { + if(s[1] == '$') + s += 2; + else { + blk.AddText(b, s); + p.Set(s + 1, NULL, line); + if(p.Id("if")) { + ExeCond& c = blk.item.Add().Create(); + p.PassChar('('); + c.cond = Exp(); + p.PassChar(')'); + c.ontrue = Block(); + if(p.Id("else")) + c.onfalse = Block(); + if(!p.Char('/')) + p.PassId("endif"); + } + else + if(p.Id("for")) { + ExeFor& c = blk.item.Add().Create(); + p.PassChar('('); + int q = var.GetCount(); + var.Add(p.ReadId()); + var.Add(Null); // LoopInfo placeholder + forvar.Add(true); + forvar.Add(true); + p.PassId("in"); + c.value = Exp(); + p.PassChar(')'); + c.body = Block(); + var.Trim(q); + forvar.SetCount(q); + if(p.Id("else")) + c.onempty = Block(); + if(!p.Char('/')) + p.PassId("endfor"); + } + else + if(p.IsId("else") || p.IsId("endif") || p.IsId("endfor") || p.IsChar('/')) + return result; + else + blk.item.AddPick(Prim()); + b = s = p.GetSpacePtr(); + line = p.GetLine(); + } + } + else + if(*s++ == '\n') + line++; + } + blk.AddText(b, s); + p.Set(s, NULL, line); + return result; +} + +One Compile(const char *code, const Index& vars) +{ + Compiler c(code, vars); + try { + One exe = c.Block(); + LLOG("Before optimization node count: " << c.GetNodeCount(exe)); + c.Optimize(exe); + LLOG("After optimization node count: " << c.GetNodeCount(exe)); + return exe; + } + catch(CParser::Error e) { + One result; + result.Create().value = e; + return result; + } +} diff --git a/uppsrc/Skylark/Dispatch.cpp b/uppsrc/Skylark/Dispatch.cpp new file mode 100644 index 000000000..aca8a37da --- /dev/null +++ b/uppsrc/Skylark/Dispatch.cpp @@ -0,0 +1,374 @@ +#include "Skylark.h" + +#define LLOG(x) DLOG(x) +#define LDUMP(x) DDUMP(x) +#define LTIMING(x) RTIMING(x) + +enum { DISPATCH_VARARGS = -1 }; + +struct DispatchNode : Moveable { + VectorMap subnode; + void (*view)(Http&); + int argpos; + int method; + bool post_raw; + String id; + + enum { GET, POST }; + + DispatchNode() { view = NULL; argpos = Null; method = GET; post_raw = false; } +}; + +static Vector& sDispatchMap() +{ + static Vector x; + return x; +} + +static VectorMap >& sLinkMap() +{ + static VectorMap > x; + return x; +} + +static Index& sViewIndex() +{ + static Index x; + return x; +} + +void DumpDispatchMap() +{ + Vector& DispatchMap = sDispatchMap(); + for(int i = 0; i < DispatchMap.GetCount(); i++) { + LLOG("-------------"); + String sub; + for(int j = 0; j < DispatchMap[i].subnode.GetCount(); j++) + sub << DispatchMap[i].subnode.GetKey(j) << "->" << DispatchMap[i].subnode[j] << ", "; + LLOG(i << " " << (bool)DispatchMap[i].view << ": " << sub); + } +} + +Vector *GetUrlViewLinkParts(const String& id) +{ + int q = sLinkMap().Find(id); + if(q < 0) + return NULL; + return &sLinkMap()[q]; +} + +String MakeLink(void (*view)(Http&), const Vector& arg) +{ + int q = sViewIndex().Find((uintptr_t)view); + if(q < 0) + throw Exc("Invalid view"); + if(q < 0) + return String(); + StringBuffer out; + MakeLink(out, sLinkMap()[q], arg); + return out; +} + +void RegisterView0(void (*view)(Http&), const char *id, String path, bool primary) +{ + LLOG("RegisterView " << path); + Vector& linkpart = sLinkMap().GetAdd(id); + sViewIndex().FindAdd((uintptr_t)view); + Vector& DispatchMap = sDispatchMap(); + int method = DispatchNode::GET; + bool post_raw = false; + int q = path.Find(':'); + if(q >= 0) { + if(path.Mid(q + 1) == "POST") + method = DispatchNode::POST; + if(path.Mid(q + 1) == "POST_RAW") { + method = DispatchNode::POST; + post_raw = true; + } + path = path.Mid(0, q); + } + Vector h = Split(path, '/'); + if(DispatchMap.GetCount() == 0) + DispatchMap.Add(); + q = 0; + int linkargpos = 0; + for(int i = 0; i < h.GetCount(); i++) { + String s = h[i]; + LLOG(" Node " << h[i]); + DispatchNode& n = DispatchMap[q]; + if(*s == '*') { + int argpos = Null; + if(IsDigit(s[1])) + linkargpos = argpos = minmax(atoi(~s + 1), 0, 30); + else + if(s[1] == '*') + argpos = DISPATCH_VARARGS; + q = DispatchMap.GetCount(); + LLOG(" Adding arg " << argpos << ": " << q); + n.subnode.Add(Null, q); + DispatchMap.Add(); + DispatchMap[q].argpos = argpos; + if(primary) + linkpart.Add(String(linkargpos++, 1)); + } + else { + if(primary) + linkpart.Add(s); + q = n.subnode.Get(s, -1); + if(q < 0) { + q = DispatchMap.GetCount(); + LLOG(" Adding " << s << ": " << q); + n.subnode.Add(s, q); + DispatchMap.Add(); + } + } + } + ASSERT_(!DispatchMap[q].view, "duplicate view " + String(path)); + DispatchMap[q].view = view; + DispatchMap[q].method = method; + DispatchMap[q].id = id; + DispatchMap[q].post_raw = post_raw; +// DumpDispatchMap(); +} + +struct ViewData { + void (*view)(Http&); + String id; + String path; +}; + +static Array& sViewData() +{ + static Array x; + return x; +} + +void RegisterView(void (*view)(Http&), const char *id, const char *path) +{ + Array& v = sViewData(); + ViewData& w = v.Add(); + w.view = view; + w.id = id; + w.path = path; +} + +void SkylarkApp::FinalizeViews() +{ + Array& w = sViewData(); + for(int i = 0; i < w.GetCount(); i++) { + const ViewData& v = w[i]; + ASSERT_(sViewIndex().Find((uintptr_t)v.view) < 0, "duplicate view function registration " + String(v.id)); + Vector h = Split(ReplaceVars(root + '/' + v.path, view_var, '$'), ';'); + for(int i = 0; i < h.GetCount(); i++) + RegisterView0(v.view, v.id, h[i], i == 0); + } + w.Clear(); +} + +struct BestDispatch { + void (*view)(Http&); + int matched_parts; + int matched_params; + Vector& arg; + String id; + bool post_raw; + + BestDispatch(Vector& arg) : arg(arg) { matched_parts = -1; matched_params = 0; view = NULL; post_raw = false; } +}; + +void GetBestDispatch(int method, + const Vector& part, int ii, const DispatchNode& n, Vector& arg, + BestDispatch& bd, int matched_parts, int matched_params) +{ + Vector& DispatchMap = sDispatchMap(); + if(ii >= part.GetCount()) { + if(n.view && n.method == method && + (matched_parts > bd.matched_parts || + matched_parts == bd.matched_parts && matched_params > bd.matched_params)) { + bd.arg <<= arg; + bd.view = n.view; + bd.matched_parts = matched_parts; + bd.id = n.id; + bd.post_raw = n.post_raw; + } + int q = n.subnode.Find(String()); + while(q >= 0) { + const DispatchNode& an = DispatchMap[n.subnode[q]]; + if(an.argpos == DISPATCH_VARARGS && an.view && an.method == method) { + bd.view = an.view; + bd.arg.Clear(); + break; + } + q = n.subnode.FindNext(q); + } + return; + } + int qq = n.subnode.Get(part[ii], -1); + if(qq >= 0) + GetBestDispatch(method, part, ii + 1, DispatchMap[qq], arg, bd, matched_parts + 1, matched_params); + int q = n.subnode.Find(String()); + while(q >= 0) { + int qq = n.subnode[q]; + int ac = arg.GetCount(); + const DispatchNode& an = DispatchMap[qq]; + int apos = an.argpos; + LLOG(" *" << qq << " apos: " << apos); + if(apos == DISPATCH_VARARGS) { + if(an.view && an.method == method && + (matched_parts > bd.matched_parts || matched_parts == bd.matched_parts && matched_params > bd.matched_params)) { + bd.arg <<= arg; + bd.arg.Append(part, ii, part.GetCount() - ii); + bd.view = an.view; + bd.matched_parts = matched_parts; + bd.id = an.id; + } + } + else { + String pv; + if(IsNull(apos)) + arg.Add(part[ii]); + else { + String& at = arg.At(apos); + pv = at; + at = part[ii]; + } + GetBestDispatch(method, part, ii + 1, an, arg, bd, matched_parts, matched_params + 1); + if(!IsNull(apos)) + arg[apos] = pv; + } + arg.SetCount(ac); + q = n.subnode.FindNext(q); + } +} + +void Http::Dispatch(TcpSocket& socket) +{ + Vector& DispatchMap = sDispatchMap(); + if(hdr.Read(socket)) { + int len = GetLength(); + content = socket.GetAll(len); + LLOG("--------------------------------------------"); + LLOG(hdr.GetMethod() << " " << hdr.GetURI() << "\n"); + LDUMP(content); + Cout() << hdr.GetMethod() << " " << hdr.GetURI() << "\n"; + String r; + var.Clear(); + arg.Clear(); + LTIMING("Request processing"); + request_content_type = GetHeader("content-type"); + String rc = ToLower(request_content_type); + bool post = hdr.GetMethod() == "POST"; + if(post) + if(rc.StartsWith("application/x-www-form-urlencoded")) + ParseRequest(content); + else + if(rc.StartsWith("multipart/")) + ReadMultiPart(content); + String uri = hdr.GetURI(); + int q = uri.Find('?'); + if(q >= 0) { + if(!post) + ParseRequest(~uri + q + 1); + uri.Trim(q); + } + for(int i = hdr.fields.Find("cookie"); i >= 0; i = hdr.fields.FindNext(i)) { + const String& h = hdr.fields[i]; + int q = 0; + for(;;) { + int qq = h.Find('=', q); + if(qq < 0) + break; + String id = ToLower(TrimBoth(h.Mid(q, qq - q))); + qq++; + DUMP(id); + q = h.Find(';', qq); + if(q < 0) { + var.Add(id, UrlDecode(h.Mid(qq))); + break; + } + var.Add(id, UrlDecode(h.Mid(qq, q - qq))); + q++; + } + } + var.GetAdd("__identity__"); // To make StdLib.icpp GetIndentity work without changing preset stack positions + DUMPM(var); + Vector part = Split(uri, '/'); + for(int i = 0; i < part.GetCount(); i++) + part[i] = UrlDecode(part[i]); + DUMPC(part); + Vector a; + BestDispatch bd(arg); + if(DispatchMap.GetCount()) + GetBestDispatch(post ? DispatchNode::POST : DispatchNode::GET, part, 0, DispatchMap[0], a, bd, 0, 0); + DUMPC(arg); + response.Clear(); + if(bd.view) { + try { + SQL.Begin(); + LoadSession(); + session_dirty = false; + if(post && !bd.post_raw) { + String id = Nvl((*this)["__post_identity__"], (*this)["__js_identity__"]); + if(id != (*this)["__identity__"]) + throw AuthExc("identity error"); + } + lang = Nvl(Int("__lang__"), LNG_ENGLISH); + SetLanguage(lang); + viewid = bd.id; + (*bd.view)(*this); + if(session_dirty) + SaveSession(); + SQL.Commit(); + } + catch(SqlExc e) { + SQL.Rollback(); + response << "Internal server error
" + << "SQL ERROR: " << e; + code = 500; + code_text = "Internal server error"; + app.SqlError(*this); + } + catch(AuthExc e) { + SQL.Rollback(); + response << e; + code = 403; + code_text = "Unauthorized"; + app.Unauthorized(*this); + } + catch(Exc e) { + SQL.Rollback(); + response << "Internal server error
" + << e; + code = 500; + code_text = "Internal server error"; + app.InternalError(*this); + } + } + else { + response << "Page not found"; + code = 404; + code_text = "Not found"; + app.NotFound(*this); + } + r.Clear(); + if(redirect.GetCount()) { + r << "HTTP/1.1 " << code << " Found\r\n"; + r << "Location: " << redirect << "\r\n"; + } + else { + r << + "HTTP/1.0 " << code << ' ' << code_text << "\r\n" + "Date: " << WwwFormat(GetUtcTime()) << "\r\n" + "Server: U++\r\n" + "Content-Length: " << response.GetCount() << "\r\n" + "Connection: close\r\n" + "Cache-Control: no-cache\r\n" + "Content-Type: " << content_type << "\r\n"; + for(int i = 0; i < cookies.GetCount(); i++) + r << cookies[i]; + r << "\r\n"; + } + socket.PutAll(r); + socket.PutAll(response); + } +} diff --git a/uppsrc/Skylark/Exe.cpp b/uppsrc/Skylark/Exe.cpp new file mode 100644 index 000000000..d7cbd382c --- /dev/null +++ b/uppsrc/Skylark/Exe.cpp @@ -0,0 +1,396 @@ +#include "Skylark.h" + +#define LTIMING(x) // RTIMING(x) +#define LLOG(x) DLOG(x) + +force_inline +bool Compiler::IsTrue(const Value& v) +{ + return !(IsNull(v) || IsNumber(v) && (int)v == 0 || IsValueArray(v) && v.GetCount() == 0); +} + +String TypeMismatch(const char *s) +{ + return ErrorValue("<* type mismatch for '" + String(s) + "' *>"); +} + +Value Compiler::ExeVar::Eval(ExeContext& x) const +{ + LLOG("Retrieving var no " << var_index << " = " << x.stack[var_index]); + return x.stack[var_index]; +} + +Value Compiler::ExeConst::Eval(ExeContext& x) const +{ + return value; +} + +Value Compiler::ExeArray::Eval(ExeContext& x) const +{ + ValueArray va; + for(int i = 0; i < item.GetCount(); i++) + va.Add(item[i]->Eval(x)); + return va; +} + +Value Compiler::ExeMap::Eval(ExeContext& x) const +{ + ValueMap m; + for(int i = 0; i < key.GetCount(); i++) + m.Add(key[i]->Eval(x), value[i]->Eval(x)); + return m; +} + +Value Compiler::ExeNot::Eval(ExeContext& x) const +{ + return !IsTrue(a->Eval(x)); +} + +Value Compiler::ExeNeg::Eval(ExeContext& x) const +{ + Value v = a->Eval(x); + if(IsNumber(v)) + return -(double)v; + return TypeMismatch("unary -"); +} + +Value Compiler::ExeMul::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 * (double)v2; + return TypeMismatch("*"); +} + +Value Compiler::ExeDiv::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 / (double)v2; + return TypeMismatch("/"); +} + +Value Compiler::ExeMod::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) { + int m = v2; + if(m == 0) + return Null; + return (int)v1 % m; + } + return TypeMismatch("%"); +} + +Value Compiler::ExeAdd::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsString(v1) && IsString(v2)) + return (String)v1 + (String)v2; + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 + (double)v2; + if(v1.Is() || v2.Is()) { + RawHtmlText h; + h.text = AsString(v1) + AsString(v2); + return RawToValue(h); + } + return AsString(v1) + AsString(v2); +} + +Value Compiler::ExeSub::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 - (double)v2; + return TypeMismatch("-"); +} + +Value Compiler::ExeSll::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (int)v1 << min(32, (int)v2); + return TypeMismatch("<<"); +} + +Value Compiler::ExeSrl::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return int((unsigned)(int)v1 >> min(32, (int)v2)); + return TypeMismatch(">>>"); +} + +Value Compiler::ExeSra::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (int)v1 >> min(32, (int)v2); + return TypeMismatch(">>"); +} + +Value Compiler::ExeLt::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsString(v1) && IsString(v2)) + return (String)v1 < (String)v2; + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 < (double)v2; + return AsString(v1) < AsString(v2); +} + +Value Compiler::ExeLte::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsString(v1) && IsString(v2)) + return (String)v1 <= (String)v2; + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 <= (double)v2; + return AsString(v1) <= AsString(v2); +} + +Value Compiler::ExeEq::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsString(v1) && IsString(v2)) + return (String)v1 == (String)v2; + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 == (double)v2; + return AsString(v1) == AsString(v2); +} + +Value Compiler::ExeNeq::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsString(v1) && IsString(v2)) + return (String)v1 != (String)v2; + if(IsNumber(v1) && IsNumber(v2)) + return (double)v1 != (double)v2; + return AsString(v1) != AsString(v2); +} + +Value Compiler::ExeAnd::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (int)v1 & (int)v2; + return TypeMismatch("&"); +} + +Value Compiler::ExeXor::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (int)v1 ^ (int)v2; + return TypeMismatch("^"); +} + +Value Compiler::ExeOr::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + if(IsNumber(v1) && IsNumber(v2)) + return (int)v1 | (int)v2; + return TypeMismatch("|"); +} + +Value Compiler::ExeAnl::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + return IsTrue(v1) && IsTrue(v2); +} + +Value Compiler::ExeOrl::Eval(ExeContext& x) const +{ + Value v1 = a->Eval(x); + Value v2 = b->Eval(x); + return IsTrue(v1) || IsTrue(v2); +} + +Value Compiler::ExeCond::Eval(ExeContext& x) const +{ + if(cond->Eval(x)) + return ontrue->Eval(x); + else + if(onfalse) + return onfalse->Eval(x); + return Value(); +} + +Value Compiler::ExeField::Eval(ExeContext& x) const +{ + return value->Eval(x)[id]; +} + +Value Compiler::ExeVarField::Eval(ExeContext& x) const +{ + return x.stack[var_index][id]; +} + +Value Compiler::ExeFn::Eval(ExeContext& x) const +{ + Vector v; + v.SetCount(arg.GetCount()); + for(int i = 0; i < arg.GetCount(); i++) + v[i] = arg[i]->Eval(x); + return (*fn)(v, x.renderer); +} + +Value Compiler::ExeLink::Eval(ExeContext& x) const +{ + LTIMING("ExeLink"); + Vector v; + v.SetCount(arg.GetCount()); + for(int i = 0; i < arg.GetCount(); i++) { + LTIMING("arg eval"); + v[i] = arg[i]->Eval(x); + } + StringBuffer r; + r << "\""; + MakeLink(r, *part, v); + r << "\""; + return Raw(r); +} + +Value Compiler::ExeLinkVarField1::Eval(ExeContext& x) const +{ + LTIMING("ExeLinkVarField"); + Vector v; + v.Add(x.stack[var_index][id]); + StringBuffer r; + r << "\""; + MakeLink(r, *part, v); + r << "\""; + return Raw(r); +} + +Value Compiler::ExeFirst::Eval(ExeContext& x) const +{ + const LoopInfo& f = ValueTo(x.stack[var_index]); + return f.first; +} + +Value Compiler::ExeLast::Eval(ExeContext& x) const +{ + const LoopInfo& f = ValueTo(x.stack[var_index]); + return f.last; +} + +Value Compiler::ExeIndex::Eval(ExeContext& x) const +{ + const LoopInfo& f = ValueTo(x.stack[var_index]); + return f.index; +} + +Value Compiler::ExeKey::Eval(ExeContext& x) const +{ + const LoopInfo& f = ValueTo(x.stack[var_index]); + return f.key; +} + +force_inline +static void sCatAsString(StringBuffer& out, const Value& v) +{ + LTIMING("sCatAsString"); + if(IsNull(v)) + return; + if(v.Is()) { + LTIMING("Cat RawHtml"); + out.Cat(ValueTo(v).text); + } + else { + const char *s; + String h; + if(v.Is()) + s = ValueTo(v); + else { + LTIMING("AsString"); + h = AsString(v); + s = h; + } + LTIMING("Escape html"); + while(*s) { + if(*s == 31) + out.Cat(" "); + else + if(*s == '<') + out.Cat("<"); + else + if(*s == '>') + out.Cat(">"); + else + if(*s == '&') + out.Cat("&"); + else + if(*s == '\"') + out.Cat("""); + else + if((byte)*s < ' ') + out.Cat(NFormat("&#%d;", (byte)*s)); + else + out.Cat(*s); + s++; + } + } +} + +Value Compiler::ExeFor::Eval(ExeContext& x) const +{ + LTIMING("ExeFor"); + Value array = value->Eval(x); + LLOG("ExeFor array: " << array); + if(array.GetCount() == 0 && onempty) + return onempty->Eval(x); + ValueMap m; + bool map = array.Is(); + if(map) + m = array; + int q = x.stack.GetCount(); + x.stack.Add(); + x.stack.Add(); + for(int i = 0; i < array.GetCount(); i++) { + x.stack[q] = array[i]; + LoopInfo f; + f.first = i == 0; + f.last = i == array.GetCount() - 1; + f.index = i; + f.key = map ? m.GetKeys()[i] : (Value)i; + x.stack[q + 1] = RawToValue(f); + sCatAsString(x.out, body->Eval(x)); + } + x.stack.SetCount(q); + return Value(); +} + +Value Compiler::ExeBlock::Eval(ExeContext& x) const +{ + int q = x.stack.GetCount(); + for(int i = 0; i < item.GetCount(); i++) + sCatAsString(x.out, item[i]->Eval(x)); + x.stack.SetCount(q); + return Value(); +} + +String Render(const One& exe, Renderer *r, Vector& var) +{ + LTIMING("Render0"); + ExeContext x(var, r); + Value v = exe->Eval(x); + x.out.Cat(AsString(v)); + return x.out; +} diff --git a/uppsrc/Skylark/Http.cpp b/uppsrc/Skylark/Http.cpp new file mode 100644 index 000000000..eef49a47d --- /dev/null +++ b/uppsrc/Skylark/Http.cpp @@ -0,0 +1,340 @@ +#include "Skylark.h" + +#define LLOG(x) LOG(x) +#define LTIMING(x) RTIMING(x) + +Http::Http(SkylarkApp& app) +: app(app) +{ + code = 200; + content_type = "text/html; charset=UTF-8"; + session_dirty = false; + lang = LNG_ENGLISH; +} + +void Http::ParseRequest(const char *p) +{ + while(*p) { + const char *last = p; + while(*p && *p != '=' && *p != '&') + p++; + String key = UrlDecode(last, p); + if(*p == '=') + p++; + last = p; + while(*p && *p != '&') + p++; + var.GetAdd(key) = UrlDecode(last, p); + if(*p) + p++; + } +} + +String HttpResponse(int code, const char *phrase, const String& data, const char *content_type, + const char *cookies) +{ + String r; + r << + "HTTP/1.0 " << code << ' ' << phrase << "\r\n" + "Date: " << WwwFormat(GetUtcTime()) << "\r\n" + "Server: U++\r\n" + "Content-Length: " << data.GetCount() << "\r\n" + "Connection: close\r\n" + << cookies; + if(content_type) + r << "Content-Type: " << content_type << "\r\n"; + r << "\r\n" << data; + return r; +} + +Http& Http::SetRawCookie(const char *id, const String& value, Time expires, + const char *path, const char *domain, bool secure, + bool httponly) +{ + var.GetAdd(id) = value; + String& c = cookies.GetAdd(id); + c.Clear(); + c << "Set-Cookie:" << ' ' << id << '=' << value; + if(!IsNull(expires)) + c << "; " << WwwFormat(expires); + c << "; Path=" << (path && *path ? path : "/"); + if(domain && *domain) + c << "; Domain=" << domain; + if(secure) + c << "; Secure"; + if(httponly) + c << "; HttpOnly"; + c << "\r\n"; + return *this; +} + +int Http::Int(const char *id) const +{ + Value v = operator[](id); + if(v.Is()) + return v; + if(IsString(v)) + return ScanInt((String)v); + if(IsNull(v)) + return Null; + if(IsNumber(v)) { + double d = v; + if(d > INT_MIN && d <= INT_MAX) + return (int)d; + } + return Null; +} + +int Http::Int(int i) const +{ + return ScanInt(operator[](i)); +} + +String HttpAsString(const Value& v) +{ + if(v.Is()) + return v.To().text; + return AsString(v); +} + +Http& Http::Content(const char *s, const Value& data) +{ + content_type = s; + response = HttpAsString(data); + return *this; +} + +Http& Http::operator<<(const Value& s) +{ + response << HttpAsString(s); + return *this; +} + +Http& Http::SetCookie(const char *id, const String& value, Time expires, + const char *path, const char *domain, bool secure, bool httponly) +{ + return SetRawCookie(id, UrlEncode(value), expires, path, domain, secure); +} + +void Http::ReadMultiPart(const String& buffer) +{ + const char *p = buffer; + while(p[0] != '-' || p[1] != '-') { + while(*p != '\n') + if(*p++ == 0) + return; // end of file, boundary not found + p++; + } + String delimiter; + { // read multipart delimiter + const char *b = (p += 2); + while(*p && *p++ != '\n') + ; + const char *e = p; + while(e > b && (byte)e[-1] <= ' ') + e--; + delimiter = String(b, e); + } + + int delta = 4 + delimiter.GetLength(); + const char *e = buffer.End(); + if(e - p < delta) + return; + e -= delta; + while(p < e) { // read individual parts + String filename, content_type, name; + while(!MemICmp(p, "content-", 8)) { // parse content specifiers + p += 8; + if(!MemICmp(p, "disposition:", 12)) { + p += 12; + while(*p && *p != '\n') + if((byte)*p <= ' ') + p++; + else { // fetch key-value pair + const char *kp = p; + while(*p && *p != '\n' && *p != '=' && *p != ';') + p++; + const char *ke = p; + String value; + if(*p == '=') { + const char *b = ++p; + if(*p == '\"') { // quoted value + b++; + while(*++p && *p != '\n' && *p != '\"') + ; + value = String(b, p); + if(*p == '\"') + p++; + } + else { + while(*p && *p != '\n' && *p != ';') + p++; + value = String(b, p); + } + } + if(ke - kp == 4 && !MemICmp(kp, "name", 4)) + name = value; + else if(ke - kp == 8 && !MemICmp(kp, "filename", 8)) + filename = value; + if(*p == ';') + p++; + } + } + else if(!MemICmp(p, "type:", 5)) { + p += 5; + while(*p && *p != '\n' && (byte)*p <= ' ') + p++; + const char *b = p; + while(*p && *p != '\n') + p++; + const char *e = p; + while(e > b && (byte)e[-1] <= ' ') + e--; + content_type = String(b, e); + } + ; + while(*p && *p++ != '\n') + ; + } + if(*p++ != '\r' || *p++ != '\n') + return; + const char *b = p; + while(p < e) { + p = (const char *)memchr(p, '\r', e - p); + if(!p) + return; + if(p[0] == '\r' && p[1] == '\n' && p[2] == '-' && p[3] == '-' + && !memcmp(p + 4, delimiter, delimiter.GetLength())) + break; + p++; + } + if(!name.IsEmpty()) { // add variables + if(!filename.IsEmpty()) + var.GetAdd(name + ".filename") = filename; + if(!content_type.IsEmpty()) + var.GetAdd(name + ".content_type") = content_type; + var.Add(name, String(b, p)); + } + p += delta; + while(*p && *p++ != '\n') + ; + } +} + +static const char hex_digits[] = "0123456789ABCDEF"; + +void UrlEncode(StringBuffer& out, const String& s) +{ + static bool ok[256]; + ONCELOCK { + for(int ch = 0; ch < 256; ch++) + ok[ch] = IsAlNum(ch) || ch == ',' || ch == '.' || ch == '-' || ch == '_'; + } + const char *p = s, *e = s.End(); + for(; p < e; p++) + { + const char *b = p; + while(p < e && ok[(byte)*p]) + p++; + if(p > b) + out.Cat(b, int(p - b)); + if(p >= e) + break; + if(*p == ' ') + out << '+'; + else + out << '%' << hex_digits[(*p >> 4) & 15] << hex_digits[*p & 15]; + } +} + +void MakeLink(StringBuffer& out, const Vector& part, const Vector& arg) +{ + LTIMING("MakeLink"); + out.Cat("/"); + for(int i = 0; i < part.GetCount(); i++) { + const String& p = part[i]; + if(i) + out << '/'; + int q = (byte)*p; + if(q < 32) { + if(q >= 0 && q < arg.GetCount()) + UrlEncode(out, AsString(arg[q])); + } + else + UrlEncode(out, p); + } + bool get = false; + for(int i = 0; i < arg.GetCount(); i++) + if(IsValueMap(arg[i])) { + if(get) + out << '&'; + else + out << '?'; + get = true; + ValueMap m = arg[i]; + for(int i = 0; i < m.GetCount(); i++) { + if(i) + out << '&'; + UrlEncode(out, AsString(m.GetKeys()[i])); + out << '='; + UrlEncode(out, AsString(m.GetValues()[i])); + } + } +} + +Http& Http::RenderResult(const char *template_name) +{ + LTIMING("Render"); + response << ::Render(GetTemplate(template_name), this, var.GetValues()); + return *this; +} + +Http& Http::Redirect(void (*view)(Http&), const Vector& arg) +{ + Redirect(MakeLink(view, arg)); + return *this; +} + +Http& Http::Redirect(void (*view)(Http&)) +{ + Vector arg; + Redirect(view, arg); + return *this; +} + +Http& Http::Redirect(void (*view)(Http&), const Value& v1) +{ + Vector arg; + arg.Add(v1); + Redirect(view, arg); + return *this; +} + +Http& Http::Redirect(void (*view)(Http&), const Value& v1, const Value& v2) +{ + Vector arg; + arg.Add(v1); + arg.Add(v2); + Redirect(view, arg); + return *this; +} + +Http& Http::Ux(const char *id, const String& text) +{ + if(response.GetCount()) + response << '\1'; + response << id << ':' << text; + return *this; +} + +Http& Http::UxRender(const char *id, const char *template_name) +{ + Ux(id, RenderString(template_name)); + return *this; +} + +Http& Http::UxSetValue(const char *id, const String& value) +{ + Ux(String(">") + id, value); + return *this; +} diff --git a/uppsrc/Skylark/Http.h b/uppsrc/Skylark/Http.h new file mode 100644 index 000000000..164bd11d6 --- /dev/null +++ b/uppsrc/Skylark/Http.h @@ -0,0 +1,142 @@ +void MakeLink(StringBuffer& out, const Vector& part, const Vector& arg); + +class Renderer { +protected: + VectorMap var; + int lang; + + Renderer& Link(const char *id, void (*view)(Http&), const Vector& arg); + const One& GetTemplate(const char *template_name); + +public: + Renderer& operator()(const char *id, const Value& v) { var.Add(id, v); return *this; } + Renderer& operator()(const ValueMap& map); + Renderer& operator()(const char *id, void (*view)(Http&)); + Renderer& operator()(const char *id, void (*view)(Http&), const Value& arg1); + Renderer& operator()(const char *id, void (*view)(Http&), const Value& arg1, const Value& arg2); + + Renderer& operator()(const Sql& sql); + Renderer& operator()(Fields rec); + SqlUpdate Update(SqlId table); + SqlInsert Insert(SqlId table); + + Value operator[](const char *id) const { return var.Get(id, Null); } + + String RenderString(const String& template_name); + Value Render(const String& template_name) { return Raw(RenderString(template_name)); } + + Renderer& Render(const char *id, const String& template_name); + + Renderer() { lang = LNG_ENGLISH; } + virtual ~Renderer(); +}; + +class Http : public Renderer { + SkylarkApp& app; + HttpHeader hdr; + + String content; + String viewid; + + Vector arg; + String session_id; + VectorMap session_var; + bool session_dirty; + + String redirect; + int code; + String code_text; + String response; + String content_type; + String request_content_type; + + VectorMap cookies; + + void ParseRequest(const char *s); + void ReadMultiPart(const String& content); + + String SessionFile(const String& sid); + + void LoadSession(); + void SaveSession(); + +public: + Http& operator()(const char *id, const Value& v) { var.Add(id, v); return *this; } + Http& operator()(const ValueMap& map) { Renderer::operator()(map); return *this; } + Http& operator()(const char *id, void (*view)(Http&)) { Renderer::operator()(id, view); return *this; } + Http& operator()(const char *id, void (*view)(Http&), const Value& arg1) { Renderer::operator()(id, view, arg1); return *this; } + Http& operator()(const char *id, void (*view)(Http&), const Value& arg1, const Value& arg2) { Renderer::operator()(id, view, arg1, arg2); return *this; } + + Http& operator()(const Sql& sql) { Renderer::operator()(sql); return *this; } + Http& operator()(Fields rec) { Renderer::operator()(rec); return *this; } + Http& Render(const char *id, const String& template_name) { Renderer::Render(id, template_name); return *this; } + Value Render(const String& template_name) { return Renderer::Render(template_name); } + + void Dispatch(TcpSocket& socket); + + String GetHeader(const char *s) const { return hdr[s]; } + int GetLength() const { return atoi(GetHeader("content-length")); } + + String GetViewId() const { return viewid; } + + Value operator[](const char *id) const { return Renderer::operator[](id); } + String operator[](int i) const { return i >= 0 && i < arg.GetCount() ? arg[i] : String(); } + + int Int(const char *id) const; + int Int(int i) const; + + int GetParamCount() const { return arg.GetCount(); } + + Http& ContentType(const char *s) { content_type = s; return *this; } + + Http& Content(const char *s, const Value& data); + Http& operator<<(const Value& s); + + Http& SetRawCookie(const char *id, const String& value, + Time expires = Null, const char *path = NULL, + const char *domain = NULL, bool secure = false, bool httponly = false); + Http& SetCookie(const char *id, const String& value, + Time expires = Null, const char *path = NULL, + const char *domain = NULL, bool secure = false, bool httponly = false); + + Http& ClearSession(); + Http& SessionSet(const char *id, const Value& value); + + Http& NewIdentity() { SessionSet("__identity__", Null); return *this; } + Http& NewSessionId(); + Http& SetLanguage(int lang); + + Http& Response(int code_, const String& ctext) { code = code_; code_text = ctext; return *this; } + + Http& RenderResult(const char *template_name); + Http& Redirect(const char *url, int code_ = 302) { code = code_; redirect = url; return *this; } + Http& Redirect(void (*view)(Http&), const Vector& arg); + Http& Redirect(void (*view)(Http&)); + Http& Redirect(void (*view)(Http&), const Value& v1); + Http& Redirect(void (*view)(Http&), const Value& v1, const Value& v2); + + Http& Ux(const char *id, const String& text); + Http& UxRender(const char *id, const char *template_name); + Http& UxSetValue(const char *id, const String& value); + + String GetResponse() const { return response; } + + const SkylarkApp& App() const { return app; } + + Http(SkylarkApp& app); +}; + +String HttpResponse(int code, const char *phrase, const String& data, const char *content_type = NULL); + +void RegisterView(void (*view)(Http&), const char *id, const char *path); + +#define URL_VIEW(name, path) void name(Http& http); INITBLOCK { RegisterView(name, #name, path); } void name(Http& http) +#define SKYLARK(name, path) void name(Http& http); INITBLOCK { RegisterView(name, #name, path); } void name(Http& http) + +Vector *GetUrlViewLinkParts(const String& id); + +String MakeLink(void (*view)(Http&), const Vector& arg); + +enum { + SESSION_FORMAT_BINARY, SESSION_FORMAT_JSON, SESSION_FORMAT_XML +}; diff --git a/uppsrc/Skylark/Optimize.cpp b/uppsrc/Skylark/Optimize.cpp new file mode 100644 index 000000000..a4e63d82b --- /dev/null +++ b/uppsrc/Skylark/Optimize.cpp @@ -0,0 +1,156 @@ +#include "Skylark.h" + +#define LLOG(x) // DLOG(x) + +void Compiler::Iterate(Vector< One >& a, Callback1< One& > op) +{ + for(int i = 0; i < a.GetCount(); i++) + op(a[i]); +} + +void Compiler::OptimizeConst(One& exe) +{ + One oxe; + Vector stack; + ExeContext x(stack); + oxe.Create().value = exe->Eval(x); + LLOG("OPTIMIZED constant: " << exe->Eval(stack, out)); + exe = oxe; + optimized = true; +} + +void Compiler::Iterate(One& exe, Callback1< One& > op) +{ + if(ExeBlock *e = dynamic_cast(~exe)) { + Iterate(e->item, op); + return; + } + if(ExeFor *e = dynamic_cast(~exe)) { + op(e->value); + op(e->body); + op(e->onempty); + return; + } + if(ExeLink *e = dynamic_cast(~exe)) { + Iterate(e->arg, op); + return; + } + if(ExeFn *e = dynamic_cast(~exe)) { + Iterate(e->arg, op); + return; + } + if(ExeField *e = dynamic_cast(~exe)) { + op(e->value); + return; + } + if(ExeCond *e = dynamic_cast(~exe)) { + op(e->cond); + op(e->ontrue); + op(e->onfalse); + return; + } + if(ExeMap *e = dynamic_cast(~exe)) { + Iterate(e->key, op); + Iterate(e->value, op); + return; + } + if(ExeArray *e = dynamic_cast(~exe)) { + Iterate(e->item, op); + return; + } + if(Exe2 *e = dynamic_cast(~exe)) { + op(e->a); + op(e->b); + return; + } + if(Exe1 *e = dynamic_cast(~exe)) { + op(e->a); + return; + } +} + +void Compiler::CountNodes(One& exe) +{ + count_node++; + Iterate(exe, THISBACK(CountNodes)); +} + +int Compiler::GetNodeCount(One& exe) +{ + count_node = 0; + CountNodes(exe); + return count_node; +} + +void Compiler::Optimize(One& exe) +{ + if(!exe) + return; + bool optimized2 = false; +#ifdef _DEBUG0 + String name = "Optimizing " + String(typeid(*~exe).name()); + LLOGBLOCK(name); +#endif + do { + optimized = false; + Iterate(exe, THISBACK(Optimize)); + if(ExeField *e = dynamic_cast(~exe)) { + if(ExeVar *e1 = dynamic_cast(~e->value)) { + One oxe; + ExeVarField& vf = oxe.Create(); + vf.id = e->id; + vf.var_index = e1->var_index; + exe = oxe; + optimized = true; + LLOG("OPTIMIZED ExeVarField"); + } + } + if(ExeLink *e = dynamic_cast(~exe)) { + ExeVarField *e1; + if(e->arg.GetCount() == 1 && (e1 = dynamic_cast(~e->arg[0]))) { + One oxe; + ExeLinkVarField1& o = oxe.Create(); + o.id = e1->id; + o.var_index = e1->var_index; + o.part = e->part; + exe = oxe; + optimized = true; + LLOG("OPTIMIZED ExeLinkVarField1"); + } + } + if(ExeBlock *e = dynamic_cast(~exe)) { + Vector< One >& m = e->item; + int i = 0; + while(i < m.GetCount() - 1) { + ExeConst *e1 = dynamic_cast(~m[i]); + ExeConst *e2 = dynamic_cast(~m[i + 1]); + if(e1 && e2 && e1->value.Is() && e2->value.Is()) { + RawHtmlText t; + t.text = ValueTo(e1->value).text + ValueTo(e2->value).text; + One oxe; + oxe.Create().value = RawToValue(t); + m[i] = oxe; + m.Remove(i + 1); + optimized = true; + LLOG("OPTIMIZED ExeBlock constant folding"); + } + else + i++; + } + } + if(Exe2 *e = dynamic_cast(~exe)) { + if(dynamic_cast(~e->a) && dynamic_cast(~e->b)) + OptimizeConst(exe); + } + else + if(Exe1 *e = dynamic_cast(~exe)) { + if(dynamic_cast(~e->a)) + OptimizeConst(exe); + } + optimized2 = optimized2 || optimized; + LLOG("---------"); + } + while(optimized); + + optimized = optimized2; +} diff --git a/uppsrc/Skylark/Preprocess.cpp b/uppsrc/Skylark/Preprocess.cpp new file mode 100644 index 000000000..87da75cf0 --- /dev/null +++ b/uppsrc/Skylark/Preprocess.cpp @@ -0,0 +1,126 @@ +#include "Skylark.h" + +String GetFileOnPath1(const char *file, const char *path) +{ + String r = GetFileOnPath(file, path); + return r; +} + +String LoadTemplate(const char *file, const String& search_path, int lang) +{ + String f = file; + String path = GetFileOnPath1(f + '.' + ToLower(LNGAsText(lang)) + ".witz", search_path); + if(IsNull(path)) { + path = GetFileOnPath1(f + ".witz", search_path); + if(IsNull(path)) { + path = GetFileOnPath1(f, search_path); + if(IsNull(path)) + return Null; + } + } + FileIn in(path); + String r; + while(in && !in.IsEof()) { + String line = in.GetLine(); + CParser p(line); + if(p.Char('#') && p.Id("include")) { + String file = p.GetPtr(); + int q = file.Find(' '); + if(q < 0) + q = file.Find('\t'); + if(q >= 0) + file = file.Mid(0, q); + r << LoadTemplate(file, GetFileFolder(path) + ';' + search_path, lang); + } + else + r << line; + r << "\r\n"; + } + return r; +} + +VectorMap GetTemplateDefs(const char *file, int lang) +{ + String s = LoadTemplate(file, SkylarkApp::Config().path, lang); + VectorMap def; + int ti = def.FindAdd("MAIN"); + StringStream ss(s); + while(!ss.IsEof()) { + String line = ss.GetLine(); + CParser p(line); + if(p.Char('#') && p.Id("define")) { + String id = "MAIN"; + if(p.IsId()) + id = p.ReadId(); + ti = def.FindAdd(id); + def[ti].Clear(); + def[ti] << p.GetPtr(); + } + else + def[ti] << line << "\r\n"; + } + return def; +} + +int CharFilterIsCrLf(int c) +{ + return c == '\r' || c == '\n' ? c : 0; +} + +String ReplaceVars(const String& src, const VectorMap& def, int chr) +{ + Index expanded; + String r = src; + bool again; + do { + again = false; + String rr; + const char *s = ~r; + for(;;) { + const char *q = strchr(s, chr); + if(q) { + rr.Cat(s, q); + CParser p(q + 1); + if(p.Char(chr) || !p.IsId()) + rr << (char)20; + else { + String id = p.ReadId(); + if(expanded.Find(id) >= 0) + rr.Cat(q, p.GetSpacePtr()); + else { + rr.Cat(def.Get(id, Null)); + expanded.Add(id); + again = true; + } + } + s = p.GetSpacePtr(); + } + else { + rr.Cat(s, r.End()); + break; + } + } + r = rr; + } + while(again); + return r; +} + +int CharFilter20toHash(int c) +{ + return c == 20 ? '#' : c; +} + +String GetPreprocessedTemplate(const String& name, int lang) +{ + String id = "MAIN"; + String file = name; + int q = file.Find(':'); + if(q >= 0) { + id = file.Mid(q + 1); + file = file.Mid(0, q); + } + VectorMap def = GetTemplateDefs(file, lang); + String r = Filter(ReplaceVars(def.Get(id, Null), def, '#'), CharFilter20toHash); + return Join(Split(r, CharFilterIsCrLf), "\r\n"); +} diff --git a/uppsrc/Skylark/Renderer.cpp b/uppsrc/Skylark/Renderer.cpp new file mode 100644 index 000000000..bc019b4e8 --- /dev/null +++ b/uppsrc/Skylark/Renderer.cpp @@ -0,0 +1,76 @@ +#include "Skylark.h" + +#define LLOG(x) DLOG(x) +#define LTIMING(x) // RTIMING(x) + +Renderer& Renderer::operator()(const ValueMap& map) +{ + ValueArray v = map.GetValues(); + const Index& k = map.GetKeys(); + for(int i = 0; i < map.GetCount(); i++) + var.Add(k[i], v[i]); + return *this; +} + +Renderer& Renderer::Link(const char *id, void (*view)(Http&), const Vector& arg) +{ + var.Add(id, Raw('\"' + MakeLink(view, arg) + '\"')); + return *this; +} + +Renderer& Renderer::operator()(const char *id, void (*view)(Http&)) +{ + return Link(id, view, Vector()); +} + +Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1) +{ + return Link(id, view, Vector() << arg1); +} + +Renderer& Renderer::operator()(const char *id, void (*view)(Http&), const Value& arg1, const Value& arg2) +{ + return Link(id, view, Vector() << arg1 << arg2); +} + +StaticMutex template_cache_lock; +ArrayMap > template_cache; + +const One& Renderer::GetTemplate(const char *template_name) +{ + LTIMING("GetTemplate"); + StringBuffer s; + { + LTIMING("MakeSignature"); + for(int i = 0; i < var.GetCount(); i++) + s << var.GetKey(i) << ';'; + s << ':' << template_name; + } + String sgn = s; + LLOG("Trying to retrieve " << sgn << " from cache"); + DDUMPM(var); + Mutex::Lock __(template_cache_lock); + int q = template_cache.Find(sgn); + if(q >= 0 && SkylarkApp::Config().use_caching) + return template_cache[q]; + LLOG("About to compile: " << sgn); + LTIMING("Compile"); + One& exe = q >= 0 ? template_cache[q] : template_cache.Add(sgn); + exe = Compile(GetPreprocessedTemplate(template_name, lang), var.GetIndex()); + return exe; +} + +String Renderer::RenderString(const String& template_name) +{ + return ::Render(GetTemplate(template_name), this, var.GetValues()); +} + +Renderer& Renderer::Render(const char *id, const String& template_name) +{ + var.Add(id, Render(template_name)); + return *this; +} + +Renderer::~Renderer() +{ +} diff --git a/uppsrc/Skylark/Session.cpp b/uppsrc/Skylark/Session.cpp new file mode 100644 index 000000000..a2ff87f01 --- /dev/null +++ b/uppsrc/Skylark/Session.cpp @@ -0,0 +1,152 @@ +#include "Skylark.h" + +#define LLOG(x) //DLOG(x) +#define LDUMPC(x) //DDUMPC(x) +#define LDUMPM(x) //DDUMPM(x) +#define LLOGHEX(x) //DLOGHEX(x) + +SessionConfig::SessionConfig() +{ + cookie = "__skylark_session_cookie__"; + dir = ConfigFile("session"); + format = SESSION_FORMAT_BINARY; + id_column = "ID"; + data_column = "DATA"; + lastwrite_column = "LASTWRITE"; + expire = 3600 * 24 * 365; // one year to expire the session +} + +String Http::SessionFile(const String& sid) +{ + ONCELOCK + RealizeDirectory(app.session.dir); + return AppendFileName(app.session.dir, sid); +} + +void Http::LoadSession() +{ + const SessionConfig& cfg = app.session; + session_var.Clear(); + session_id = (*this)[cfg.cookie]; + if(IsNull(session_id)) + return; + String data; + if(cfg.table.IsNull()) + data = LoadFile(SessionFile(session_id)); + else + data = SQLR % Select(cfg.data_column).From(cfg.table) + .Where(cfg.id_column == session_id); + LLOGHEX(data); + switch(cfg.format) { + case SESSION_FORMAT_JSON: + LoadFromJson(session_var, data); + break; + case SESSION_FORMAT_XML: + LoadFromXML(session_var, data); + break; + case SESSION_FORMAT_BINARY: + LoadFromString(session_var, data); + break; + } + LLOG("Loaded session: " << session_id); + LDUMPM(session_var); + for(int i = 0; i < session_var.GetCount(); i++) + var.Add(session_var.GetKey(i), session_var[i]); +} + +thread__ int s_exp; + +void Http::SaveSession() +{ + DDUMPM(session_var); + const SessionConfig& cfg = app.session; + SetCookie(cfg.cookie, session_id); + if(IsNull(session_id)) + return; + String data; + switch(cfg.format) { + case SESSION_FORMAT_JSON: + data = StoreAsJson(session_var); + break; + case SESSION_FORMAT_XML: + data = StoreAsXML(session_var, "session"); + break; + case SESSION_FORMAT_BINARY: + data = StoreAsString(session_var); + break; + } + if(cfg.table.IsNull()) + SaveFile(SessionFile(session_id), data); + else { + SqlVal d = SqlBinary(data); + Time tm = GetSysTime(); + SQL * Update(cfg.table) + (cfg.data_column, d) + (cfg.lastwrite_column, tm) + .Where(cfg.id_column == session_id); + if(SQL.GetRowsProcessed() == 0) + SQL * Insert(cfg.table) + (cfg.id_column, session_id) + (cfg.data_column, d) + (cfg.lastwrite_column, tm); + } + LLOG("Stored session: " << session_id); + LDUMPM(session_var); + + if((s_exp++ % 1000) == 0) { + Time tm = GetSysTime() - cfg.expire; + LLOG("Expiring sessions older than " << tm); + if(cfg.table.IsNull()) { + FindFile ff(AppendFileName(cfg.dir, "*.*")); + Vector todelete; + while(ff) { + DDUMP(ff.GetPath()); + DDUMP(Time(ff.GetLastWriteTime())); + if(ff.GetLastWriteTime() < tm) + todelete.Add(ff.GetPath()); + ff.Next(); + } + DDUMPC(todelete); + for(int i = 0; i < todelete.GetCount(); i++) + FileDelete(todelete[i]); + } + else + SQL * Delete(cfg.table).Where(cfg.lastwrite_column < tm); + } +} + +Http& Http::ClearSession() +{ + session_var.Clear(); + session_id.Clear(); + return *this; +} + +Http& Http::SessionSet(const char *id, const Value& value) +{ + DLOG("SessionSet " << id << " = " << value); + if(IsNull(session_id)) + NewSessionId(); + session_var.GetAdd(id) = value; + var.GetAdd(id) = value; + DDUMPM(var); + session_dirty = true; + return *this; +} + +Http& Http::NewSessionId() +{ + session_id = AsString(Uuid::Create()); + session_dirty = true; + return *this; +} + +Http& Http::SetLanguage(int lang_) +{ + DDUMP(lang_); + lang = lang_; + Upp::SetLanguage(lang_); + SessionSet("__lang__", lang); + SessionSet("language", ToLower(LNGAsText(lang))); + return *this; +} diff --git a/uppsrc/Skylark/Skylark.h b/uppsrc/Skylark/Skylark.h new file mode 100644 index 000000000..2fe9d0f40 --- /dev/null +++ b/uppsrc/Skylark/Skylark.h @@ -0,0 +1,90 @@ +#ifndef _Wpp_Wpp_h +#define _Wpp_Wpp_h + +#include +#include +#include +#include + +using namespace Upp; + +class Renderer; +class Http; + +struct SessionConfig { + String cookie; + String dir; + int format; + SqlId table, id_column, data_column, lastwrite_column; + int expire; + + SessionConfig(); +}; + +struct AuthExc : Exc { + AuthExc(const String& s) : Exc(s) {} +}; + +struct SkylarkConfig { + String root; + VectorMap view_var; + String path; + SessionConfig session; + int threads; + int port; + int prefork; + int timeout; + bool use_caching; +}; + +class SkylarkApp : protected SkylarkConfig { + TcpSocket server; + Mutex accept_mutex; + int main_pid; + Vector child_pid; + bool quit; + + + void ThreadRun(); + void Broadcast(int signal); + void Signal(int signal); + static void SignalHandler(int signal); + void Main(); + + void FinalizeViews(); + + static SkylarkApp *app; + +#ifdef PLATFORM_WIN32 + static BOOL WINAPI CtrlCHandlerRoutine(__in DWORD dwCtrlType); +#endif + + typedef SkylarkApp CLASSNAME; + + friend class Http; + +public: + virtual void SqlError(Http& http); + virtual void InternalError(Http& http); + virtual void NotFound(Http& http); + virtual void Unauthorized(Http& http); + + virtual void WorkThread() = 0; + + void RunThread(); + + void Run(); + + static SkylarkApp& TheApp(); + static const SkylarkConfig& Config(); + + SkylarkApp(); + virtual ~SkylarkApp(); +}; + +void SetStaticPath(const String& path); + +#include "Witz.h" +#include "Http.h" + +#endif diff --git a/uppsrc/Skylark/Skylark.upp b/uppsrc/Skylark/Skylark.upp new file mode 100644 index 000000000..c2beaf00a --- /dev/null +++ b/uppsrc/Skylark/Skylark.upp @@ -0,0 +1,45 @@ +description "\377B128,0,0"; + +uses + Draw, + plugin\png, + plugin\jpg, + Sql; + +file + Skylark.h, + App.cpp, + Witz.h, + Preprocess.cpp, + Compile.cpp, + Optimize.cpp, + Exe.cpp, + Http.h, + Renderer.cpp, + Sql.cpp, + Http.cpp, + Session.cpp, + Dispatch.cpp, + StdLib.icpp, + Util readonly separator, + Static.icpp, + Client readonly separator, + Base.witz, + skylark.js; + +custom() ".css", + "cp $(PATH) $(EXEDIR)/static/$(PACKAGE)/$(RELPATH)", + "$(EXEDIR)/static/$(PACKAGE)/$(RELPATH)"; + +custom() ".html", + "cp $(PATH) $(EXEDIR)/static/$(PACKAGE)/$(RELPATH)", + "$(EXEDIR)/static/$(PACKAGE)/$(RELPATH)"; + +custom() ".js", + "cp $(PATH) $(EXEDIR)/static/$(PACKAGE)/$(RELPATH)", + "$(EXEDIR)/static/$(PACKAGE)/$(RELPATH)"; + +custom() ".whtml", + "cp $(PATH) $(EXEDIR)/template/$(PACKAGE)/$(RELPATH)", + "$(EXEDIR)/template/$(PACKAGE)/$(RELPATH)"; + diff --git a/uppsrc/Skylark/Sql.cpp b/uppsrc/Skylark/Sql.cpp new file mode 100644 index 000000000..945e7d57b --- /dev/null +++ b/uppsrc/Skylark/Sql.cpp @@ -0,0 +1,56 @@ +#include "Skylark.h" + +struct sFieldsToRenderer : public FieldOperator { + Renderer& http; + + void Field(const char *name, Ref f) { + http(name, f); + } + + sFieldsToRenderer(Renderer& http) : http(http) {} +}; + +Renderer& Renderer::operator()(Fields rec) +{ + sFieldsToRenderer x(*this); + rec(x); + return *this; +} + +Renderer& Renderer::operator()(const Sql& sql) +{ + int n = sql.GetColumns(); + for(int i = 0; i < n; i++) + (*this)(sql.GetColumnInfo(i).name, sql[i]); + return *this; +} + +SqlUpdate Renderer::Update(SqlId table) +{ + Vector col = GetSchColumns(~table); + SqlUpdate u(table); + for(int i = 0; i < col.GetCount(); i++) { + String c = col[i]; + int q = var.Find(c); + if(q < 0) + q = var.Find(ToLower(c)); + if(q >= 0) + u(c, var[q]); + } + return u; +} + +SqlInsert Renderer::Insert(SqlId table) +{ + Vector col = GetSchColumns(~table); + SqlInsert y(table); + for(int i = 0; i < col.GetCount(); i++) { + String c = col[i]; + int q = var.Find(c); + if(q < 0) + q = var.Find(ToLower(c)); + if(q >= 0) + y(c, var[q]); + } + return y; +} diff --git a/uppsrc/Skylark/Static.icpp b/uppsrc/Skylark/Static.icpp new file mode 100644 index 000000000..df13f8b0a --- /dev/null +++ b/uppsrc/Skylark/Static.icpp @@ -0,0 +1,46 @@ +#include "Skylark.h" + +// add static page caching? + +SKYLARK(ServeStaticPage, "static/**") +{ + String file; + for(int i = 0; i < http.GetParamCount(); i++) { + if(i) + file << '/'; + file << http[i]; + } + DDUMP(file); + DDUMP(SkylarkApp::Config().path); + String path = GetFileOnPath(file, SkylarkApp::Config().path, false); + if(path.GetCount()) { + String ext = ToLower(GetFileExt(file)); + DDUMP(ext); + String type = "text"; + if(ext == ".css") + type = "text/css"; + else + if(ext == ".js") + type = "text/javascript"; + else + if(ext == ".png" || ext == ".jpg" || ext == ".gif") + type = "image/" + ext.Mid(1); + http.Content(type, LoadFile(path)); + } +} + +SKYLARK(ServeIml, "iml/*") +{ + String name = http[0]; + int q = name.Find('.'); + String ext; + if(q >= 0) { + ext = name.Mid(q + 1); + name = name.Mid(0, q); + } + Image m = GetImlImage(name); + if(ext == "jpg" || ext == "JPG" || ext == "jpeg" || ext == "JPEG") + http.Content("image/jpeg", JPGEncoder().SaveString(m)); + else + http.Content("image/png", PNGEncoder().SaveString(m)); +} diff --git a/uppsrc/Skylark/StdLib.icpp b/uppsrc/Skylark/StdLib.icpp new file mode 100644 index 000000000..51107d14b --- /dev/null +++ b/uppsrc/Skylark/StdLib.icpp @@ -0,0 +1,47 @@ +#include "Skylark.h" + +Value Cycle(const Vector& arg, const Renderer *) +{ + if(arg.GetCount() < 3 && !IsNumber(arg[0])) + return String(); + return arg[1 + int(arg[0]) % (arg.GetCount() - 1)]; +} + +Value RawFn(const Vector& arg, const Renderer *) +{ + RawHtmlText r; + for(int i = 0; i < arg.GetCount(); i++) + r.text.Cat(AsString(arg[i])); + return RawToValue(r); +} + +String GetIdentity(const Renderer *r) +{ + // This ugly hack expects that __identity__ is always present in r->var + Http *http = const_cast(dynamic_cast(r)); + if(!http) + throw Exc("invalid POST identity call"); + String s = (*http)["__identity__"]; + if(s.GetCount()) + return s; + s = AsString(Uuid::Create()); + http->SessionSet("__identity__", s); + return s; +} + +Value PostIdentity(const Vector&, const Renderer *r) +{ + return Raw(""); +} + +Value JsIdentity(const Vector&, const Renderer *r) +{ + return Raw(""); +} + +INITBLOCK { + Compiler::Register("cycle", Cycle); + Compiler::Register("raw", RawFn); + Compiler::Register("post_identity", PostIdentity); + Compiler::Register("js_identity", JsIdentity); +}; diff --git a/uppsrc/Skylark/Witz.h b/uppsrc/Skylark/Witz.h new file mode 100644 index 000000000..964470f3e --- /dev/null +++ b/uppsrc/Skylark/Witz.h @@ -0,0 +1,228 @@ +struct ExeContext { + const Renderer *renderer; + Vector& stack; + StringBuffer out; + + ExeContext(Vector& stack, const Renderer *r = NULL) : renderer(r), stack(stack) {} +}; + +struct Exe { + virtual Value Eval(ExeContext& x) const { return Value(); } + virtual ~Exe() {} +}; + +struct RawHtmlText { + String text; +}; + +Value Raw(const String& s); + +struct Compiler { + static VectorMap&, const Renderer *)>& functions(); + + static bool IsTrue(const Value& v); + + struct Exe1 : Exe { + One a; + }; + + template + static One Create(One a) { + One rr; + T& m = rr.Create(); + m.a = a; + return rr; + } + + struct Exe2 : Exe { + One a; + One b; + }; + + template + static One Create(One a, One b) { + One rr; + T& m = rr.Create(); + m.a = a; + m.b = b; + return rr; + } + + struct ExeVar : Exe { + int var_index; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeConst : Exe { + Value value; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeArray : Exe { + Vector< One > item; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeMap : Exe { + Vector< One > key; + Vector< One > value; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeNot : Exe1 { virtual Value Eval(ExeContext& x) const; }; + struct ExeNeg : Exe1 { virtual Value Eval(ExeContext& x) const; }; + struct ExeMul : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeDiv : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeMod : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeAdd : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeSub : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeSll : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeSra : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeSrl : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeLt : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeLte : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeEq : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeNeq : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeAnd : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeXor : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeOr : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeAnl : Exe2 { virtual Value Eval(ExeContext& x) const; }; + struct ExeOrl : Exe2 { virtual Value Eval(ExeContext& x) const; }; + + struct ExeCond : Exe { + One cond; + One ontrue; + One onfalse; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeField : Exe { + One value; + String id; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeVarField : Exe { + int var_index; + String id; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeFn : Exe { + Value (*fn)(const Vector&, const Renderer *); + + Vector< One > arg; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeLink : Exe { + const Vector *part; + + Vector< One > arg; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeLinkVarField1 : Exe { + const Vector *part; + + int var_index; + String id; + + virtual Value Eval(ExeContext& x) const; + }; + + struct LoopInfo { + bool first; + bool last; + int index; + Value key; + }; + + struct ExeFirst : ExeVar { virtual Value Eval(ExeContext& x) const; }; + struct ExeLast : ExeVar { virtual Value Eval(ExeContext& x) const; }; + struct ExeIndex : ExeVar { virtual Value Eval(ExeContext& x) const; }; + struct ExeKey : ExeVar { virtual Value Eval(ExeContext& x) const; }; + + struct ExeFor : Exe { + One value; + One body; + One onempty; + + virtual Value Eval(ExeContext& x) const; + }; + + struct ExeBlock : Exe { + Vector< One > item; + + void AddText(const char *b, const char *s); + + virtual Value Eval(ExeContext& x) const; + }; + + struct CompiledTemplate { + String path; + Index var; + + Exe program; + + Vector data; + Value Eval(); + }; + + CParser p; + Index var; + Vector forvar; + bool optimized; + int count_node; + + int ForVar(String id, int i); + + One Prim(); + One Mul(); + One Add(); + One Shift(); + One Rel(); + One Eq(); + One And(); + One Xor(); + One Or(); + One LogAnd(); + One LogOr(); + One Conditional(); + + One Exp(); + + One Block(); + + typedef Compiler CLASSNAME; + + void Iterate(Vector< One >& a, Callback1< One& > op); + void Iterate(One& exe, Callback1< One& > op); + void OptimizeConst(One& exe); + void Optimize(One& exe); + + void CountNodes(One& exe); + + int GetNodeCount(One& exe); + + static void Register(const String& id, Value (*fn)(const Vector&, const Renderer *)); + + Compiler(const char *code, const Index& var) : p(code), var(var, 1) { forvar.SetCount(var.GetCount(), false); } +}; + +One Compile(const char *code, const Index& vars); + +String Render(const One& exe, Renderer *r, Vector& var); + +String GetPreprocessedTemplate(const String& name, int lang); + +String ReplaceVars(const String& src, const VectorMap& def, int chr); diff --git a/uppsrc/Skylark/init b/uppsrc/Skylark/init new file mode 100644 index 000000000..21b97af70 --- /dev/null +++ b/uppsrc/Skylark/init @@ -0,0 +1,13 @@ +#ifndef _Skylark_icpp_init_stub +#define _Skylark_icpp_init_stub +#include "Draw/init" +#include "plugin\png/init" +#include "plugin\jpg/init" +#include "Sql/init" +#define BLITZ_INDEX__ F5727c7d5aaf9771a467d28f05356763c +#include "StdLib.icpp" +#undef BLITZ_INDEX__ +#define BLITZ_INDEX__ F8bbcf8bffcf1dc379d0adde61a45f9fe +#include "Static.icpp" +#undef BLITZ_INDEX__ +#endif diff --git a/uppsrc/Skylark/skylark.js b/uppsrc/Skylark/skylark.js new file mode 100644 index 000000000..85c0312a0 --- /dev/null +++ b/uppsrc/Skylark/skylark.js @@ -0,0 +1,85 @@ +function Log(x) +{ +// document.writeln(x + "
"); +} + +function ProcessAjaxResult(ss) +{ + for(i = 0; i < ss.length; i++) { + var pos = ss[i].indexOf(':'); + if(pos >= 0) { + var id = ss[i].slice(0, pos); + var text = ss[i].slice(pos + 1); + if(id.length > 1 && id.charAt(0) == '>') + document.getElementById(id.slice(1)).value = text; + else + document.getElementById(id).innerHTML = text; + } + } +} + +function AjaxRequest() +{ + if(window.XMLHttpRequest) + return new XMLHttpRequest(); + else + return new ActiveXObject("Microsoft.XMLHTTP"); +} + +function IsNull(x) +{ + return x == null || x.length == 0; +} + +function ScanForValues(x, result) +{ + if(x) { + if(x.nodeType == 1 && + (x.nodeName == "INPUT" || x.nodeName == "SELECT" || x.nodeName == "TEXTAREA")) { + if(result.val.length) + result.val += "&"; + Log(x.nodeName); + Log("Name: " + x.name); + Log("Id: " + x.id); + Log(x.name == undefined); + Log(x.name == null); + var id = IsNull(x.name) ? x.id : x.name; + Log("ID: " + id); + if(!IsNull(id)) + result.val += id + '=' + encodeURIComponent(x.value); + } + for(var i = 0; i < x.childNodes.length; i++) + ScanForValues(x.childNodes[i], result); + } +} + +function UxGet(request) +{ + var xmlhttp = AjaxRequest(); + xmlhttp.onreadystatechange = function() { + if(xmlhttp.readyState == 4 && xmlhttp.status == 200) + ProcessAjaxResult(xmlhttp.responseText.split('\1')); + } + xmlhttp.open("GET", request, true); + xmlhttp.send(); +} + +function UxPost(request) +{ + var xmlhttp = AjaxRequest(); + xmlhttp.onreadystatechange = function() { + if(xmlhttp.readyState == 4 && xmlhttp.status == 200) + ProcessAjaxResult(xmlhttp.responseText.split('\1')); + } + var parameters = { val: "" }; + for(var i = 1; i < arguments.length; i++) + ScanForValues(document.getElementById(arguments[i]), parameters); + if(__js_identity__ != undefined) { + if(parameters.val.length) + parameters.val += "&"; + parameters.val += "__js_identity__=" + __js_identity__; + } + xmlhttp.open("POST", request, true); + xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xmlhttp.send(parameters.val); +}