diff options
Diffstat (limited to 'tcllib/modules/pt/pt_cparam_config_critcl.tcl')
-rw-r--r-- | tcllib/modules/pt/pt_cparam_config_critcl.tcl | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/tcllib/modules/pt/pt_cparam_config_critcl.tcl b/tcllib/modules/pt/pt_cparam_config_critcl.tcl new file mode 100644 index 0000000..656105f --- /dev/null +++ b/tcllib/modules/pt/pt_cparam_config_critcl.tcl @@ -0,0 +1,492 @@ +# -*- tcl -*- +# Copyright (c) 2009-2014 Andreas Kupries <andreas_kupries@sourceforge.net> + +# TODO: Refactor this and pt::cparam::configuration::critcl to avoid +# TODO: duplication of the supporting code (creation of the RDE +# TODO: amalgamation, basic C template). + +# Canned configuration for the converter to C/PARAM representation, +# causing generation of a proper critcl-based parser. + +# The requirements of the embedded template are not our requirements. +# @mdgen NODEP: critcl + +# # ## ### ##### ######## ############# ##################### +## Requirements + +package require Tcl 8.5 ; # Required runtime. + +# # ## ### ##### ######## ############# ##################### +## + +namespace eval ::pt::cparam::configuration::critcl { + namespace export def + namespace ensemble create + + # @mdgen OWNER: rde_critcl/util.* + # @mdgen OWNER: rde_critcl/stack.* + # @mdgen OWNER: rde_critcl/tc.* + # @mdgen OWNER: rde_critcl/param.* + # Access to the rde_critcl files forming the low-level runtime + variable selfdir [file dirname [file normalize [info script]]] +} + +# # ## ### ##### ######## ############# +## Public API + +# Check that the proposed serialization of an abstract syntax tree is +# indeed such. + +proc ::pt::cparam::configuration::critcl::def {class pkg version cmd} { + # TODO :: See if we can consolidate the API for converters, + # TODO :: plugins, export manager, and container in some way. + # TODO :: Container may make exporter manager available through + # TODO :: public method. + + # class = The namespace/prefix for the generated commands. + # pkg = The name of the generated package / parser. + # version = The version of the generated package / parser. + + if {[string first :: $class] < 0} { + set cheader $class + set ctrailer $class + } else { + set cheader [namespace qualifier $class] + set ctrailer [namespace tail $class] + } + + lappend map @@RUNTIME@@ [GetRuntime] + lappend map @@PKG@@ $pkg + lappend map @@VERSION@@ $version + lappend map @@CLASS@@ $class + lappend map @@CHEAD@@ $cheader + lappend map @@CTAIL@@ $ctrailer + lappend map \n\t \n ;# undent the template + + {*}$cmd -main MAIN + {*}$cmd -indent 8 + {*}$cmd -template [string trim \ + [string map $map { + ## -*- tcl -*- + ## + ## Critcl-based C/PARAM implementation of the parsing + ## expression grammar + ## + ## @name@ + ## + ## Generated from file @file@ + ## for user @user@ + ## + # # ## ### ##### ######## ############# ##################### + ## Requirements + + package require Tcl 8.4 + package require critcl + # @sak notprovided @@PKG@@ + package provide @@PKG@@ @@VERSION@@ + + # Note: The implementation of the PARAM virtual machine + # underlying the C/PARAM code used below is inlined + # into the generated parser, allowing for direct access + # and manipulation of the RDE state, instead of having + # to dispatch through the Tcl interpreter. + + # # ## ### ##### ######## ############# ##################### + ## + + namespace eval ::@@CHEAD@@ { + # # ## ### ##### ######## ############# ##################### + ## Supporting code for the main command. + + catch { + #critcl::cflags -g + #critcl::debug memory symbols + } + + # # ## ### ###### ######## ############# + ## RDE runtime, inlined, and made static. + + # This is the C code for the RDE, i.e. the implementation + # of pt::rde. Only the low-level engine is imported, the + # Tcl interface layer is ignored. This generated parser + # provides its own layer for that. + + critcl::ccode { + /* -*- c -*- */ + + #include <string.h> + #define SCOPE static + +@@RUNTIME@@ + } + + # # ## ### ###### ######## ############# + ## BEGIN of GENERATED CODE. DO NOT EDIT. + + critcl::ccode { + /* -*- c -*- */ + +@code@ + } + + ## END of GENERATED CODE. DO NOT EDIT. + # # ## ### ###### ######## ############# + + # # ## ### ###### ######## ############# + ## Global PARSER management, per interp + + critcl::ccode { + /* -*- c -*- */ + + typedef struct PARSERg { + long int counter; + char buf [50]; + } PARSERg; + + static void + PARSERgRelease (ClientData cd, Tcl_Interp* interp) + { + ckfree((char*) cd); + } + + static const char* + PARSERnewName (Tcl_Interp* interp) + { +#define KEY "tcllib/parser/@@PKG@@/critcl" + + Tcl_InterpDeleteProc* proc = PARSERgRelease; + PARSERg* parserg; + + parserg = Tcl_GetAssocData (interp, KEY, &proc); + if (parserg == NULL) { + parserg = (PARSERg*) ckalloc (sizeof (PARSERg)); + parserg->counter = 0; + + Tcl_SetAssocData (interp, KEY, proc, + (ClientData) parserg); + } + + parserg->counter ++; + sprintf (parserg->buf, "@@CTAIL@@%ld", parserg->counter); + return parserg->buf; +#undef KEY + } + + static void + PARSERdeleteCmd (ClientData clientData) + { + /* + * Release the whole PARSER + * (Low-level engine only actually). + */ + rde_param_del ((RDE_PARAM) clientData); + } + } + + # # ## ### ##### ######## ############# + ## Functions implementing the object methods, and helper. + + critcl::ccode { + static int COMPLETE (RDE_PARAM p, Tcl_Interp* interp); + + static int parser_PARSE (RDE_PARAM p, Tcl_Interp* interp, int objc, Tcl_Obj* CONST* objv) + { + int mode; + Tcl_Channel chan; + + if (objc != 3) { + Tcl_WrongNumArgs (interp, 2, objv, "chan"); + return TCL_ERROR; + } + + chan = Tcl_GetChannel(interp, + Tcl_GetString (objv[2]), + &mode); + + if (!chan) { + return TCL_ERROR; + } + + rde_param_reset (p, chan); + MAIN (p) ; /* Entrypoint for the generated code. */ + return COMPLETE (p, interp); + } + + static int parser_PARSET (RDE_PARAM p, Tcl_Interp* interp, int objc, Tcl_Obj* CONST* objv) + { + char* buf; + int len; + + if (objc != 3) { + Tcl_WrongNumArgs (interp, 2, objv, "text"); + return TCL_ERROR; + } + + buf = Tcl_GetStringFromObj (objv[2], &len); + + rde_param_reset (p, NULL); + rde_param_data (p, buf, len); + MAIN (p) ; /* Entrypoint for the generated code. */ + return COMPLETE (p, interp); + } + + /* See also rde_critcl/m.c, param_COMPLETE() */ + static int COMPLETE (RDE_PARAM p, Tcl_Interp* interp) + { + if (rde_param_query_st (p)) { + long int ac; + Tcl_Obj** av; + + rde_param_query_ast (p, &ac, &av); + + if (ac > 1) { + Tcl_Obj** lv = NALLOC (3+ac, Tcl_Obj*); + + memcpy(lv + 3, av, ac * sizeof (Tcl_Obj*)); + lv [0] = Tcl_NewObj (); + lv [1] = Tcl_NewIntObj (1 + rde_param_query_lstop (p)); + lv [2] = Tcl_NewIntObj (rde_param_query_cl (p)); + + Tcl_SetObjResult (interp, Tcl_NewListObj (3, lv)); + ckfree ((char*) lv); + + } else if (ac == 0) { + /* + * Match, but no AST. This is possible if the grammar + * consists of only the start expression. + */ + Tcl_SetObjResult (interp, Tcl_NewStringObj ("",-1)); + } else { + Tcl_SetObjResult (interp, av [0]); + } + + return TCL_OK; + } else { + Tcl_Obj* xv [1]; + const ERROR_STATE* er = rde_param_query_er (p); + Tcl_Obj* res = rde_param_query_er_tcl (p, er); + /* res = list (location, list(msg)) */ + + /* Stick the exception type-tag before the existing elements */ + xv [0] = Tcl_NewStringObj ("pt::rde",-1); + Tcl_ListObjReplace(interp, res, 0, 0, 1, xv); + + Tcl_SetErrorCode (interp, "PT", "RDE", "SYNTAX", NULL); + Tcl_SetObjResult (interp, res); + return TCL_ERROR; + } + } + } + + # # ## ### ##### ######## ############# + ## Object command, method dispatch. + + critcl::ccode { + static int parser_objcmd (ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj* CONST* objv) + { + RDE_PARAM p = (RDE_PARAM) cd; + int m, res; + + static CONST char* methods [] = { + "destroy", "parse", "parset", NULL + }; + enum methods { + M_DESTROY, M_PARSE, M_PARSET + }; + + if (objc < 2) { + Tcl_WrongNumArgs (interp, objc, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } else if (Tcl_GetIndexFromObj (interp, objv [1], methods, "option", + 0, &m) != TCL_OK) { + return TCL_ERROR; + } + + /* Dispatch to methods. They check the #args in + * detail before performing the requested + * functionality + */ + + switch (m) { + case M_DESTROY: + if (objc != 2) { + Tcl_WrongNumArgs (interp, 2, objv, NULL); + return TCL_ERROR; + } + + Tcl_DeleteCommandFromToken(interp, (Tcl_Command) rde_param_query_clientdata (p)); + return TCL_OK; + + case M_PARSE: res = parser_PARSE (p, interp, objc, objv); break; + case M_PARSET: res = parser_PARSET (p, interp, objc, objv); break; + default: + /* Not coming to this place */ + ASSERT (0,"Reached unreachable location"); + } + + return res; + } + } + + # # ## ### ##### ######## ############# + # Class command, i.e. object construction. + + critcl::ccommand @@CTAIL@@_critcl {dummy interp objc objv} { + /* + * Syntax: No arguments beyond the name + */ + + RDE_PARAM parser; + CONST char* name; + Tcl_Obj* fqn; + Tcl_CmdInfo ci; + Tcl_Command c; + +#define USAGE "?name?" + + if ((objc != 2) && (objc != 1)) { + Tcl_WrongNumArgs (interp, 1, objv, USAGE); + return TCL_ERROR; + } + + if (objc < 2) { + name = PARSERnewName (interp); + } else { + name = Tcl_GetString (objv [1]); + } + + if (!Tcl_StringMatch (name, "::*")) { + /* Relative name. Prefix with current namespace */ + + Tcl_Eval (interp, "namespace current"); + fqn = Tcl_GetObjResult (interp); + fqn = Tcl_DuplicateObj (fqn); + Tcl_IncrRefCount (fqn); + + if (!Tcl_StringMatch (Tcl_GetString (fqn), "::")) { + Tcl_AppendToObj (fqn, "::", -1); + } + Tcl_AppendToObj (fqn, name, -1); + } else { + fqn = Tcl_NewStringObj (name, -1); + Tcl_IncrRefCount (fqn); + } + Tcl_ResetResult (interp); + + if (Tcl_GetCommandInfo (interp, + Tcl_GetString (fqn), + &ci)) { + Tcl_Obj* err; + + err = Tcl_NewObj (); + Tcl_AppendToObj (err, "command \"", -1); + Tcl_AppendObjToObj (err, fqn); + Tcl_AppendToObj (err, "\" already exists", -1); + + Tcl_DecrRefCount (fqn); + Tcl_SetObjResult (interp, err); + return TCL_ERROR; + } + + parser = rde_param_new (sizeof(p_string)/sizeof(char*), (char**) p_string); + c = Tcl_CreateObjCommand (interp, Tcl_GetString (fqn), + parser_objcmd, (ClientData) parser, + PARSERdeleteCmd); + rde_param_clientdata (parser, (ClientData) c); + Tcl_SetObjResult (interp, fqn); + Tcl_DecrRefCount (fqn); + return TCL_OK; + } + + ## + # # ## ### ##### ######## ############# + } + + # # ## ### ##### ######## ############# ##################### + ## Ready (Note: Our package provide is at the top). + return + }]] + + return +} + +proc ::pt::cparam::configuration::critcl::GetRuntime {} { + # This is the C code for the RDE, i.e. the implementation of + # pt::rde. Only the low-level engine is imported, the Tcl + # interface layer is ignored. This generated parser provides its + # own layer for that. + + # We are inlining the code (making the functions static) to + # prevent any conflict with the support for pt::rde, should both + # be put into the same shared library. + + variable selfdir + + set code {} + + foreach f { + rde_critcl/util.h + rde_critcl/stack.h + rde_critcl/tc.h + rde_critcl/param.h + rde_critcl/util.c + rde_critcl/stack.c + rde_critcl/tc.c + rde_critcl/param.c + } { + # Load C code. + set c [open $selfdir/$f] + set d [read $c] + close $c + + # Strip include directives and anything explicitly excluded. + set skip 0 + set n {} + foreach l [split $d \n] { + if {[string match {*#include*} $l]} { + continue + } + if {[string match {*SKIP START*} $l]} { + set skip 1 + continue + } + if {[string match {*SKIP END*} $l]} { + set skip 0 + continue + } + if {$skip} continue + lappend n $l + } + set d [join $n \n] + + # Strip comments, trailing whitespace, empty lines. + set d [regsub -all {/\*.*?\*/} $d {}] + set d [regsub -all {//.*?\n} $d {}] + set d [regsub -all {[ ]+$} $d {}] + while {1} { + set n [string map [list \n\n \n] $d] + if {$n eq $d} break + set d $n + } + + # Indent code. + lappend code "#line 1 \"$f\"" + foreach l [split $d \n] { + if {$l ne ""} { set l \t$l } + lappend code $l + } + } + + #lappend code "#line x \"X\"" + return [join $code \n] +} + +# # ## ### ##### ######## ############# + +namespace eval ::pt::cparam::configuration::critcl {} + +# # ## ### ##### ######## ############# ##################### +## Ready + +package provide pt::cparam::configuration::critcl 1.0.2 +return |