Skip to content

Commit cb7dbd2

Browse files
committed
Experimental support for CoffeeScript.
By default plcoffee is off. You'll need pass ENABLE_COFFEE to make to enable it. Its version is tentatively 0.9, as there is no test cases for it although the quality should match with plv8 itself.
1 parent d4dff62 commit cb7dbd2

File tree

5 files changed

+193
-26
lines changed

5 files changed

+193
-26
lines changed

Makefile

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ V8DIR = ../v8
33
# set your custom C++ compler
44
CUSTOM_CC = g++
55

6-
SRCS = plv8.cc plv8_type.cc plv8_func.cc
6+
JSS = coffee-script.js
7+
# .cc created from .js
8+
JSCS = $(JSS:.js=.cc)
9+
SRCS = plv8.cc plv8_type.cc plv8_func.cc $(JSCS)
710
OBJS = $(SRCS:.cc=.o)
811
MODULE_big = plv8
912
EXTENSION = plv8
@@ -15,10 +18,25 @@ SHLIB_LINK := $(SHLIB_LINK) -lv8
1518

1619
CCFLAGS := $(filter-out -Wmissing-prototypes, $(CFLAGS))
1720
CCFLAGS := $(filter-out -Wdeclaration-after-statement, $(CCFLAGS))
21+
ifdef ENABLE_COFFEE
22+
CCFLAGS := -DENABLE_COFFEE $(CCFLAGS)
23+
DATA = plcoffee.control plcoffee--0.9.sql
24+
endif
25+
26+
all:
1827

1928
%.o : %.cc
2029
g++ $(CCFLAGS) $(CPPFLAGS) -I $(V8DIR)/include -fPIC -c -o $@ $<
2130

31+
# Convert .js to .cc
32+
$(filter $(JSCS), $(SRCS)): %.cc: %.js
33+
echo "extern const unsigned char $(subst -,_,$(basename $@))_binary_data[] = {" >$@
34+
ifdef ENABLE_COFFEE
35+
(od -txC -v $< | \
36+
sed -e "s/^[0-9]*//" -e s"/ \([0-9a-f][0-9a-f]\)/0x\1,/g" -e"\$$d" ) >>$@
37+
endif
38+
echo "0x00};" >>$@
39+
2240
PG_CONFIG = pg_config
2341
PGXS := $(shell $(PG_CONFIG) --pgxs)
2442
include $(PGXS)
@@ -39,7 +57,7 @@ install: plv8--$(EXTVER).sql
3957
plv8--$(EXTVER).sql: plv8.sql.c
4058
$(CC) -E -P $(CPPFLAGS) $< > $@
4159
subclean:
42-
rm -f plv8--$(EXTVER).sql
60+
rm -f plv8--$(EXTVER).sql $(JSCS)
4361
else # 9.1
4462
ifeq ($(shell test $(PG_VERSION_NUM) -ge 90000 && echo yes), yes)
4563
REGRESS := init $(filter-out init-extension, $(REGRESS))
@@ -50,7 +68,7 @@ DATA = uninstall_plv8.sql
5068
plv8.sql.in: plv8.sql.c
5169
$(CC) -E -P $(CPPFLAGS) $< > $@
5270
subclean:
53-
rm -f plv8.sql.in
71+
rm -f plv8.sql.in $(JSCS)
5472
endif
5573

5674
ifneq ($(basename $(MAJORVERSION)), 9)

coffee-script.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plcoffee--0.9.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE FUNCTION plcoffee_call_handler() RETURNS language_handler
2+
AS 'MODULE_PATHNAME' LANGUAGE C;
3+
CREATE FUNCTION plcoffee_inline_handler(internal) RETURNS void
4+
AS 'MODULE_PATHNAME' LANGUAGE C;
5+
CREATE FUNCTION plcoffee_call_validator(oid) RETURNS void
6+
AS 'MODULE_PATHNAME' LANGUAGE C;
7+
CREATE TRUSTED LANGUAGE plcoffee
8+
HANDLER plcoffee_call_handler
9+
INLINE plcoffee_inline_handler
10+
VALIDATOR plcoffee_call_validator;

plcoffee.control

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# plcoffee extension
2+
comment = 'PL/CoffeeScript (v8) trusted procedural language'
3+
default_version = '0.9'
4+
module_pathname = '$libdir/plv8'
5+
relocatable = false
6+
schema = pg_catalog
7+
superuser = true

plv8.cc

Lines changed: 147 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,21 @@ PG_MODULE_MAGIC;
3535

3636
PG_FUNCTION_INFO_V1(plv8_call_handler);
3737
PG_FUNCTION_INFO_V1(plv8_call_validator);
38+
PG_FUNCTION_INFO_V1(plcoffee_call_handler);
39+
PG_FUNCTION_INFO_V1(plcoffee_call_validator);
3840

3941
Datum plv8_call_handler(PG_FUNCTION_ARGS) throw();
4042
Datum plv8_call_validator(PG_FUNCTION_ARGS) throw();
43+
Datum plcoffee_call_handler(PG_FUNCTION_ARGS) throw();
44+
Datum plcoffee_call_validator(PG_FUNCTION_ARGS) throw();
4145

4246
void _PG_init(void);
4347

4448
#if PG_VERSION_NUM >= 90000
4549
PG_FUNCTION_INFO_V1(plv8_inline_handler);
50+
PG_FUNCTION_INFO_V1(plcoffee_inline_handler);
4651
Datum plv8_inline_handler(PG_FUNCTION_ARGS) throw();
52+
Datum plcoffee_inline_handler(PG_FUNCTION_ARGS) throw();
4753
#endif
4854
} // extern "C"
4955

@@ -106,6 +112,8 @@ static HTAB *plv8_proc_cache_hash = NULL;
106112

107113
static plv8_exec_env *exec_env_head = NULL;
108114

115+
extern const unsigned char coffee_script_binary_data[];
116+
109117
/*
110118
* lower_case_functions are postgres-like C functions.
111119
* They could raise errors with elog/ereport(ERROR).
@@ -118,10 +126,11 @@ static void plv8_xact_cb(XactEvent event, void *arg);
118126
* They could raise errors with C++ throw statements, or never throw exceptions.
119127
*/
120128
static plv8_exec_env *CreateExecEnv(Handle<Function> script);
121-
static plv8_proc *Compile(Oid fn_oid, MemoryContext fn_mcxt, bool validate, bool is_trigger);
129+
static plv8_proc *Compile(Oid fn_oid, MemoryContext fn_mcxt,
130+
bool validate, bool is_trigger, bool is_coffee);
122131
static Local<Function> CompileFunction(const char *proname, int proarglen,
123132
const char *proargs[], const char *prosrc,
124-
bool is_trigger, bool retset);
133+
bool is_trigger, bool retset, bool is_coffee);
125134
static Datum CallFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv,
126135
int nargs, plv8_type argtypes[], plv8_type *rettype);
127136
static Datum CallSRFunction(PG_FUNCTION_ARGS, plv8_exec_env *xenv,
@@ -205,8 +214,8 @@ plv8_new_exec_env()
205214
return xenv;
206215
}
207216

208-
Datum
209-
plv8_call_handler(PG_FUNCTION_ARGS) throw()
217+
static Datum
218+
common_pl_call_handler(PG_FUNCTION_ARGS, bool is_coffee) throw()
210219
{
211220
Oid fn_oid = fcinfo->flinfo->fn_oid;
212221
bool is_trigger = CALLED_AS_TRIGGER(fcinfo);
@@ -218,7 +227,7 @@ plv8_call_handler(PG_FUNCTION_ARGS) throw()
218227
if (!fcinfo->flinfo->fn_extra)
219228
{
220229
plv8_proc *proc = Compile(fn_oid, fcinfo->flinfo->fn_mcxt,
221-
false, is_trigger);
230+
false, is_trigger, is_coffee);
222231
proc->xenv = CreateExecEnv(proc->cache->function);
223232
fcinfo->flinfo->fn_extra = proc;
224233
}
@@ -241,9 +250,21 @@ plv8_call_handler(PG_FUNCTION_ARGS) throw()
241250
return (Datum) 0; // keep compiler quiet
242251
}
243252

244-
#if PG_VERSION_NUM >= 90000
245253
Datum
246-
plv8_inline_handler(PG_FUNCTION_ARGS) throw()
254+
plv8_call_handler(PG_FUNCTION_ARGS) throw()
255+
{
256+
return common_pl_call_handler(fcinfo, false);
257+
}
258+
259+
Datum
260+
plcoffee_call_handler(PG_FUNCTION_ARGS) throw()
261+
{
262+
return common_pl_call_handler(fcinfo, true);
263+
}
264+
265+
#if PG_VERSION_NUM >= 90000
266+
static Datum
267+
common_pl_inline_handler(PG_FUNCTION_ARGS, bool is_coffee) throw()
247268
{
248269
InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
249270

@@ -252,9 +273,10 @@ plv8_inline_handler(PG_FUNCTION_ARGS) throw()
252273
try
253274
{
254275
HandleScope handle_scope;
276+
char *source_text = codeblock->source_text;
255277

256278
Handle<Function> function = CompileFunction(NULL, 0, NULL,
257-
codeblock->source_text, false, false);
279+
source_text, false, false, is_coffee);
258280
plv8_exec_env *xenv = CreateExecEnv(function);
259281
return CallFunction(fcinfo, xenv, 0, NULL, NULL);
260282
}
@@ -263,6 +285,18 @@ plv8_inline_handler(PG_FUNCTION_ARGS) throw()
263285

264286
return (Datum) 0; // keep compiler quiet
265287
}
288+
289+
Datum
290+
plv8_inline_handler(PG_FUNCTION_ARGS) throw()
291+
{
292+
return common_pl_inline_handler(fcinfo, false);
293+
}
294+
295+
Datum
296+
plcoffee_inline_handler(PG_FUNCTION_ARGS) throw()
297+
{
298+
return common_pl_inline_handler(fcinfo, true);
299+
}
266300
#endif
267301

268302
/*
@@ -558,8 +592,8 @@ CallTrigger(PG_FUNCTION_ARGS, plv8_exec_env *xenv)
558592
return result;
559593
}
560594

561-
Datum
562-
plv8_call_validator(PG_FUNCTION_ARGS) throw()
595+
static Datum
596+
common_pl_call_validator(PG_FUNCTION_ARGS, bool is_coffee) throw()
563597
{
564598
Oid fn_oid = PG_GETARG_OID(0);
565599
HeapTuple tuple;
@@ -596,7 +630,7 @@ plv8_call_validator(PG_FUNCTION_ARGS) throw()
596630
try
597631
{
598632
plv8_proc *proc = Compile(fn_oid, fcinfo->flinfo->fn_mcxt,
599-
true, is_trigger);
633+
true, is_trigger, is_coffee);
600634
(void) CreateExecEnv(proc->cache->function);
601635
/* the result of a validator is ignored */
602636
PG_RETURN_VOID();
@@ -607,6 +641,18 @@ plv8_call_validator(PG_FUNCTION_ARGS) throw()
607641
return (Datum) 0; // keep compiler quiet
608642
}
609643

644+
Datum
645+
plv8_call_validator(PG_FUNCTION_ARGS) throw()
646+
{
647+
return common_pl_call_validator(fcinfo, false);
648+
}
649+
650+
Datum
651+
plcoffee_call_validator(PG_FUNCTION_ARGS) throw()
652+
{
653+
return common_pl_call_validator(fcinfo, true);
654+
}
655+
610656
static plv8_proc *
611657
plv8_get_proc(Oid fn_oid, MemoryContext fn_mcxt, bool validate, char ***argnames) throw()
612658
{
@@ -769,8 +815,64 @@ CreateExecEnv(Handle<Function> function)
769815
return xenv;
770816
}
771817

818+
/* Source transformation from coffee to js */
819+
static char *
820+
CompileCoffee(const char *src)
821+
{
822+
HandleScope handle_scope;
823+
static Persistent<Context> context = Context::New(NULL);
824+
Context::Scope context_scope(context);
825+
TryCatch try_catch;
826+
Local<String> key = String::NewSymbol("CoffeeScript");
827+
char *cresult;
828+
829+
#ifndef ENABLE_COFFEE
830+
throw js_error("coffee script is not enabled");
831+
#endif
832+
833+
if (context->Global()->Get(key)->IsUndefined())
834+
{
835+
HandleScope handle_scope;
836+
Local<Script> script =
837+
Script::New(ToString((const char *) coffee_script_binary_data),
838+
ToString("coffee"));
839+
if (script.IsEmpty())
840+
throw js_error(try_catch);
841+
Local<v8::Value> result = script->Run();
842+
if (result.IsEmpty())
843+
throw js_error(try_catch);
844+
}
845+
846+
Local<Object> compiler = Local<Object>::Cast(context->Global()->Get(key));
847+
Local<Function> func = Local<Function>::Cast(
848+
compiler->Get(String::NewSymbol("compile")));
849+
int nargs = 1;
850+
Handle<v8::Value> args[nargs];
851+
852+
args[0] = ToString(src);
853+
Local<v8::Value> value = func->Call(compiler, nargs, args);
854+
855+
if (value.IsEmpty())
856+
throw js_error(try_catch);
857+
CString result(value);
858+
859+
PG_TRY();
860+
{
861+
MemoryContext oldcontext = MemoryContextSwitchTo(TopMemoryContext);
862+
cresult = pstrdup(result.str());
863+
MemoryContextSwitchTo(oldcontext);
864+
}
865+
PG_CATCH();
866+
{
867+
throw pg_error();
868+
}
869+
PG_END_TRY();
870+
871+
return cresult;
872+
}
873+
772874
static plv8_proc *
773-
Compile(Oid fn_oid, MemoryContext fn_mcxt, bool validate, bool is_trigger)
875+
Compile(Oid fn_oid, MemoryContext fn_mcxt, bool validate, bool is_trigger, bool is_coffee)
774876
{
775877
plv8_proc *proc;
776878
char **argnames;
@@ -795,7 +897,8 @@ Compile(Oid fn_oid, MemoryContext fn_mcxt, bool validate, bool is_trigger)
795897
(const char **) argnames,
796898
cache->prosrc,
797899
is_trigger,
798-
cache->retset));
900+
cache->retset,
901+
is_coffee));
799902

800903
return proc;
801904
}
@@ -807,14 +910,17 @@ CompileFunction(
807910
const char *proargs[],
808911
const char *prosrc,
809912
bool is_trigger,
810-
bool retset)
913+
bool retset,
914+
bool is_coffee)
811915
{
812916
HandleScope handle_scope;
813917
StringInfoData src;
814918
Handle<Context> global_context = GetGlobalContext();
815919

816920
initStringInfo(&src);
817921

922+
if (is_coffee)
923+
prosrc = CompileCoffee(prosrc);
818924
/*
819925
* (function (<arg1, ...>){
820926
* <prosrc>
@@ -842,7 +948,10 @@ CompileFunction(
842948
appendStringInfo(&src, "$%d", i + 1); // unnamed argument to $N
843949
}
844950
}
845-
appendStringInfo(&src, "){\n%s\n})", prosrc);
951+
if (is_coffee)
952+
appendStringInfo(&src, "){\nreturn %s\n})", prosrc);
953+
else
954+
appendStringInfo(&src, "){\n%s\n})", prosrc);
846955

847956
Handle<v8::Value> name;
848957
if (proname)
@@ -871,8 +980,9 @@ find_js_function(Oid fn_oid)
871980
{
872981
HeapTuple tuple;
873982
Form_pg_proc proc;
874-
Oid prolang, langtupoid;
875-
NameData langname = { "plv8" };
983+
Oid prolang, v8langtupoid, coffeelangtupoid;
984+
NameData v8langname = { "plv8" },
985+
coffeelangname = { "plcoffee" };
876986

877987
tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0);
878988
if (!HeapTupleIsValid(tuple))
@@ -881,21 +991,35 @@ find_js_function(Oid fn_oid)
881991
prolang = proc->prolang;
882992
ReleaseSysCache(tuple);
883993

884-
tuple = SearchSysCache(LANGNAME, NameGetDatum(&langname), 0, 0, 0);
994+
tuple = SearchSysCache(LANGNAME, NameGetDatum(&v8langname), 0, 0, 0);
885995
if (!HeapTupleIsValid(tuple))
886-
elog(ERROR, "cache look up failed for language %s", NameStr(langname));
887-
langtupoid = HeapTupleGetOid(tuple);
888-
ReleaseSysCache(tuple);
996+
v8langtupoid = InvalidOid;
997+
else
998+
{
999+
v8langtupoid = HeapTupleGetOid(tuple);
1000+
ReleaseSysCache(tuple);
1001+
}
1002+
1003+
tuple = SearchSysCache(LANGNAME, NameGetDatum(&coffeelangname), 0, 0, 0);
1004+
if (!HeapTupleIsValid(tuple))
1005+
coffeelangtupoid = InvalidOid;
1006+
else
1007+
{
1008+
coffeelangtupoid = HeapTupleGetOid(tuple);
1009+
ReleaseSysCache(tuple);
1010+
}
8891011

8901012
Local<Function> func;
8911013

8921014
/* Non-JS function */
893-
if (langtupoid != prolang)
1015+
if (prolang == InvalidOid ||
1016+
(v8langtupoid != prolang && coffeelangtupoid != prolang))
8941017
return func;
8951018

8961019
try
8971020
{
898-
plv8_proc *proc = Compile(fn_oid, CurrentMemoryContext, true, false);
1021+
plv8_proc *proc = Compile(fn_oid,CurrentMemoryContext,
1022+
true, false, coffeelangtupoid == prolang);
8991023

9001024
TryCatch try_catch;
9011025

0 commit comments

Comments
 (0)