summaryrefslogtreecommitdiff
path: root/apps/interpreters/bas/program.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/interpreters/bas/program.c')
-rw-r--r--apps/interpreters/bas/program.c777
1 files changed, 777 insertions, 0 deletions
diff --git a/apps/interpreters/bas/program.c b/apps/interpreters/bas/program.c
new file mode 100644
index 000000000..a0e046a95
--- /dev/null
+++ b/apps/interpreters/bas/program.c
@@ -0,0 +1,777 @@
+/* Program storage. */
+/* #includes */ /*{{{C}}}*//*{{{*/
+#undef _POSIX_SOURCE
+#define _POSIX_SOURCE 1
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 2
+
+#include "config.h"
+
+#include <assert.h>
+#include <errno.h>
+#ifdef HAVE_GETTEXT
+#include <libintl.h>
+#define _(String) gettext(String)
+#else
+#define _(String) String
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "auto.h"
+#include "error.h"
+#include "fs.h"
+#include "program.h"
+/*}}}*/
+
+struct Program *Program_new(struct Program *this) /*{{{*/
+{
+ this->trace=0;
+ this->size=0;
+ this->numbered=1;
+ this->capacity=0;
+ this->runnable=0;
+ this->unsaved=0;
+ this->code=(struct Token**)0;
+ this->scope=(struct Scope*)0;
+ String_new(&this->name);
+ return this;
+}
+/*}}}*/
+void Program_destroy(struct Program *this) /*{{{*/
+{
+ while (this->size) Token_destroy(this->code[--this->size]);
+ if (this->capacity) free(this->code);
+ this->code=(struct Token**)0;
+ this->scope=(struct Scope*)0;
+ String_destroy(&this->name);
+}
+/*}}}*/
+void Program_norun(struct Program *this) /*{{{*/
+{
+ this->runnable=0;
+ this->scope=(struct Scope*)0;
+}
+/*}}}*/
+void Program_store(struct Program *this, struct Token *line, long int where) /*{{{*/
+{
+ int i;
+
+ assert(line->type==T_INTEGER || line->type==T_UNNUMBERED);
+ this->runnable=0;
+ this->unsaved=1;
+ if (line->type==T_UNNUMBERED) this->numbered=0;
+ if (where)
+ {
+ int last=-1;
+
+ for (i=0; i<this->size; ++i)
+ {
+ assert(this->code[i]->type==T_INTEGER || this->code[i]->type==T_UNNUMBERED);
+ if (where>last && where<this->code[i]->u.integer)
+ {
+ if ((this->size+1)>=this->capacity)
+ {
+ this->code=realloc(this->code,sizeof(struct Token*)*(this->capacity?(this->capacity*=2):(this->capacity=256)));
+ }
+ memmove(&this->code[i+1],&this->code[i],(this->size-i)*sizeof(struct Token*));
+ this->code[i]=line;
+ ++this->size;
+ return;
+ }
+ else if (where==this->code[i]->u.integer)
+ {
+ Token_destroy(this->code[i]);
+ this->code[i]=line;
+ return;
+ }
+ last=this->code[i]->u.integer;
+ }
+ }
+ else i=this->size;
+ if ((this->size+1)>=this->capacity)
+ {
+ this->code=realloc(this->code,sizeof(struct Token*)*(this->capacity?(this->capacity*=2):(this->capacity=256)));
+ }
+ this->code[i]=line;
+ ++this->size;
+}
+/*}}}*/
+void Program_delete(struct Program *this, const struct Pc *from, const struct Pc *to) /*{{{*/
+{
+ int i, first, last;
+
+ this->runnable=0;
+ this->unsaved=1;
+ first=from ? from->line : 0;
+ last=to ? to->line : this->size-1;
+ for (i=first; i<=last; ++i) Token_destroy(this->code[i]);
+ if ((last+1)!=this->size) memmove(&this->code[first],&this->code[last+1],(this->size-last+1)*sizeof(struct Token*));
+ this->size-=(last-first+1);
+}
+/*}}}*/
+void Program_addScope(struct Program *this, struct Scope *scope) /*{{{*/
+{
+ struct Scope *s;
+
+ s=this->scope;
+ this->scope=scope;
+ scope->next=s;
+}
+/*}}}*/
+struct Pc *Program_goLine(struct Program *this, long int line, struct Pc *pc) /*{{{*/
+{
+ int i;
+
+ for (i=0; i<this->size; ++i)
+ {
+ if (this->code[i]->type==T_INTEGER && line==this->code[i]->u.integer)
+ {
+ pc->line=i;
+ pc->token=this->code[i]+1;
+ return pc;
+ }
+ }
+ return (struct Pc*)0;
+}
+/*}}}*/
+struct Pc *Program_fromLine(struct Program *this, long int line, struct Pc *pc) /*{{{*/
+{
+ int i;
+
+ for (i=0; i<this->size; ++i)
+ {
+ if (this->code[i]->type==T_INTEGER && this->code[i]->u.integer>=line)
+ {
+ pc->line=i;
+ pc->token=this->code[i]+1;
+ return pc;
+ }
+ }
+ return (struct Pc*)0;
+}
+/*}}}*/
+struct Pc *Program_toLine(struct Program *this, long int line, struct Pc *pc) /*{{{*/
+{
+ int i;
+
+ for (i=this->size-1; i>=0; --i)
+ {
+ if (this->code[i]->type==T_INTEGER && this->code[i]->u.integer<=line)
+ {
+ pc->line=i;
+ pc->token=this->code[i]+1;
+ return pc;
+ }
+ }
+ return (struct Pc*)0;
+}
+/*}}}*/
+int Program_scopeCheck(struct Program *this, struct Pc *pc, struct Pc *fn) /*{{{*/
+{
+ struct Scope *scope;
+
+ if (fn==(struct Pc*)0) /* jump from global block must go to global pc */
+ {
+ for (scope=this->scope; scope; scope=scope->next)
+ {
+ if (pc->line<scope->begin.line) continue;
+ if (pc->line==scope->begin.line && pc->token<=scope->begin.token) continue;
+ if (pc->line>scope->end.line) continue;
+ if (pc->line==scope->end.line && pc->token>scope->end.token) continue;
+ return -1;
+ }
+ }
+ else /* jump from local block must go to local block */
+ {
+ scope=&(fn->token+1)->u.identifier->sym->u.sub.u.def.scope;
+ if (pc->line<scope->begin.line) return -1;
+ if (pc->line==scope->begin.line && pc->token<=scope->begin.token) return -1;
+ if (pc->line>scope->end.line) return -1;
+ if (pc->line==scope->end.line && pc->token>scope->end.token) return -1;
+ }
+ return 0;
+}
+/*}}}*/
+struct Pc *Program_dataLine(struct Program *this, long int line, struct Pc *pc) /*{{{*/
+{
+ if ((pc=Program_goLine(this,line,pc))==(struct Pc*)0) return (struct Pc*)0;
+ while (pc->token->type!=T_DATA)
+ {
+ if (pc->token->type==T_EOL) return (struct Pc*)0;
+ else ++pc->token;
+ }
+ return pc;
+}
+/*}}}*/
+struct Pc *Program_imageLine(struct Program *this, long int line, struct Pc *pc) /*{{{*/
+{
+ if ((pc=Program_goLine(this,line,pc))==(struct Pc*)0) return (struct Pc*)0;
+ while (pc->token->type!=T_IMAGE)
+ {
+ if (pc->token->type==T_EOL) return (struct Pc*)0;
+ else ++pc->token;
+ }
+ ++pc->token;
+ if (pc->token->type!=T_STRING) return (struct Pc*)0;
+ return pc;
+}
+/*}}}*/
+long int Program_lineNumber(const struct Program *this, const struct Pc *pc) /*{{{*/
+{
+ if (pc->line==-1) return 0;
+ if (this->numbered) return (this->code[pc->line]->u.integer);
+ else return (pc->line+1);
+}
+/*}}}*/
+struct Pc *Program_beginning(struct Program *this, struct Pc *pc) /*{{{*/
+{
+ if (this->size==0) return (struct Pc*)0;
+ else
+ {
+ pc->line=0;
+ pc->token=this->code[0]+1;
+ return pc;
+ }
+}
+/*}}}*/
+struct Pc *Program_end(struct Program *this, struct Pc *pc) /*{{{*/
+{
+ if (this->size==0) return (struct Pc*)0;
+ else
+ {
+ pc->line=this->size-1;
+ pc->token=this->code[this->size-1];
+ while (pc->token->type!=T_EOL) ++pc->token;
+ return pc;
+ }
+}
+/*}}}*/
+struct Pc *Program_nextLine(struct Program *this, struct Pc *pc) /*{{{*/
+{
+ if (pc->line+1==this->size) return (struct Pc*)0;
+ else
+ {
+ pc->token=this->code[++pc->line]+1;
+ return pc;
+ }
+}
+/*}}}*/
+int Program_skipEOL(struct Program *this, struct Pc *pc, int dev, int tr) /*{{{*/
+{
+ if (pc->token->type==T_EOL)
+ {
+ if (pc->line==-1 || pc->line+1==this->size) return 0;
+ {
+ pc->token=this->code[++pc->line]+1;
+ Program_trace(this,pc,dev,tr);
+ return 1;
+ }
+ }
+ else return 1;
+}
+/*}}}*/
+void Program_trace(struct Program *this, struct Pc *pc, int dev, int tr) /*{{{*/
+{
+ if (tr && this->trace && pc->line!=-1)
+ {
+ char buf[40];
+
+ sprintf(buf,"<%ld>\n",this->code[pc->line]->u.integer);
+ FS_putChars(dev,buf);
+ }
+}
+/*}}}*/
+void Program_PCtoError(struct Program *this, struct Pc *pc, struct Value *v) /*{{{*/
+{
+ struct String s;
+
+ String_new(&s);
+ if (pc->line>=0)
+ {
+ if (pc->line<(this->size-1) || pc->token->type!=T_EOL)
+ {
+ String_appendPrintf(&s,_(" in line %ld at:\n"),Program_lineNumber(this,pc));
+ Token_toString(this->code[pc->line],(struct Token*)0,&s,(int*)0,-1);
+ Token_toString(this->code[pc->line],pc->token,&s,(int*)0,-1);
+ String_appendPrintf(&s,"^\n");
+ }
+ else
+ {
+ String_appendPrintf(&s,_(" at: end of program\n"));
+ }
+ }
+ else
+ {
+ String_appendPrintf(&s,_(" at: "));
+ if (pc->token->type!=T_EOL) Token_toString(pc->token,(struct Token*)0,&s,(int*)0,-1);
+ else String_appendPrintf(&s,_("end of line\n"));
+ }
+ Value_errorSuffix(v,s.character);
+ String_destroy(&s);
+}
+/*}}}*/
+struct Value *Program_merge(struct Program *this, int dev, struct Value *value) /*{{{*/
+{
+ struct String s;
+ int l,err=0;
+
+ l=0;
+ while (String_new(&s),(err=FS_appendToString(dev,&s,1))!=-1 && s.length)
+ {
+ struct Token *line;
+
+ ++l;
+ if (l!=1 || s.character[0]!='#')
+ {
+ line=Token_newCode(s.character);
+ if (line->type==T_INTEGER && line->u.integer>0) Program_store(this,line,this->numbered?line->u.integer:0);
+ else if (line->type==T_UNNUMBERED) Program_store(this,line,0);
+ else
+ {
+ Token_destroy(line);
+ return Value_new_ERROR(value,INVALIDLINE,l);
+ }
+ }
+ String_destroy(&s);
+ }
+ String_destroy(&s);
+ if (err) return Value_new_ERROR(value,IOERROR,FS_errmsg);
+ return (struct Value*)0;
+}
+/*}}}*/
+int Program_lineNumberWidth(struct Program *this) /*{{{*/
+{
+ int i,w=0;
+
+ for (i=0; i<this->size; ++i) if (this->code[i]->type==T_INTEGER)
+ {
+ int nw,ln;
+ for (ln=this->code[i]->u.integer,nw=1; ln/=10; ++nw);
+ if (nw>w) w=nw;
+ }
+ return w;
+}
+/*}}}*/
+struct Value *Program_list(struct Program *this, int dev, int watchIntr, struct Pc *from, struct Pc *to, struct Value *value) /*{{{*/
+{
+ int i,w;
+ int indent=0;
+ struct String s;
+
+ w=Program_lineNumberWidth(this);
+ for (i=0; i<this->size; ++i)
+ {
+ String_new(&s);
+ Token_toString(this->code[i],(struct Token*)0,&s,&indent,w);
+ if ((from==(struct Pc *)0 || from->line<=i) && (to==(struct Pc*)0 || to->line>=i))
+ {
+ if (FS_putString(dev,&s)==-1) return Value_new_ERROR(value,IOERROR,FS_errmsg);
+ if (watchIntr && FS_intr) return Value_new_ERROR(value,BREAK);
+ }
+ String_destroy(&s);
+ }
+ return (struct Value*)0;
+}
+/*}}}*/
+struct Value *Program_analyse(struct Program *this, struct Pc *pc, struct Value *value) /*{{{*/
+{
+ int i;
+
+ for (i=0; i<this->size; ++i)
+ {
+ pc->token=this->code[i];
+ pc->line=i;
+ if (pc->token->type==T_INTEGER || pc->token->type==T_UNNUMBERED) ++pc->token;
+ for (;;)
+ {
+ if (pc->token->type==T_GOTO || pc->token->type==T_RESUME || pc->token->type==T_RETURN || pc->token->type==T_END || pc->token->type==T_STOP)
+ {
+ ++pc->token;
+ while (pc->token->type==T_INTEGER)
+ {
+ ++pc->token;
+ if (pc->token->type==T_COMMA) ++pc->token;
+ else break;
+ }
+ if (pc->token->type==T_COLON)
+ {
+ ++pc->token;
+ switch (pc->token->type)
+ {
+ case T_EOL:
+ case T_DEFPROC:
+ case T_SUB:
+ case T_DEFFN:
+ case T_FUNCTION:
+ case T_COLON:
+ case T_REM:
+ case T_QUOTE: break; /* those are fine to be unreachable */
+ default: return Value_new_ERROR(value,UNREACHABLE);
+ }
+ }
+ }
+ if (pc->token->type==T_EOL) break;
+ else ++pc->token;
+ }
+ }
+ return (struct Value*)0;
+}
+/*}}}*/
+void Program_renum(struct Program *this, int first, int inc) /*{{{*/
+{
+ int i;
+ struct Token *token;
+
+ for (i=0; i<this->size; ++i)
+ {
+ for (token=this->code[i]; token->type!=T_EOL; )
+ {
+ if (token->type==T_GOTO || token->type==T_GOSUB || token->type==T_RESTORE || token->type==T_RESUME || token->type==T_USING)
+ {
+ ++token;
+ while (token->type==T_INTEGER)
+ {
+ struct Pc dst;
+
+ if (Program_goLine(this,token->u.integer,&dst)) token->u.integer=first+dst.line*inc;
+ ++token;
+ if (token->type==T_COMMA) ++token;
+ else break;
+ }
+ }
+ else ++token;
+ }
+ }
+ for (i=0; i<this->size; ++i)
+ {
+ assert(this->code[i]->type==T_INTEGER || this->code[i]->type==T_UNNUMBERED);
+ this->code[i]->type=T_INTEGER;
+ this->code[i]->u.integer=first+i*inc;
+ }
+ this->numbered=1;
+ this->runnable=0;
+ this->unsaved=1;
+}
+/*}}}*/
+void Program_unnum(struct Program *this) /*{{{*/
+{
+ char *ref;
+ int i;
+ struct Token *token;
+
+ ref=malloc(this->size);
+ memset(ref,0,this->size);
+ for (i=0; i<this->size; ++i)
+ {
+ for (token=this->code[i]; token->type!=T_EOL; ++token)
+ {
+ if (token->type==T_GOTO || token->type==T_GOSUB || token->type==T_RESTORE || token->type==T_RESUME)
+ {
+ ++token;
+ while (token->type==T_INTEGER)
+ {
+ struct Pc dst;
+
+ if (Program_goLine(this,token->u.integer,&dst)) ref[dst.line]=1;
+ ++token;
+ if (token->type==T_COMMA) ++token;
+ else break;
+ }
+ }
+ }
+ }
+ for (i=0; i<this->size; ++i)
+ {
+ assert(this->code[i]->type==T_INTEGER || this->code[i]->type==T_UNNUMBERED);
+ if (!ref[i])
+ {
+ this->code[i]->type=T_UNNUMBERED;
+ this->numbered=0;
+ }
+ }
+ free(ref);
+ this->runnable=0;
+ this->unsaved=1;
+}
+/*}}}*/
+int Program_setname(struct Program *this, const char *filename) /*{{{*/
+{
+ if (this->name.length) String_delete(&this->name,0,this->name.length);
+ if (filename) return String_appendChars(&this->name,filename);
+ else return 0;
+}
+/*}}}*/
+
+/*
+
+The list of line numbers is circular, which avoids the need to have one
+extra pointer for the head (for ordered output). Instead only a pointer
+to the tail is needed. The tail's next element is the head of the list.
+
+tail --> last element <-- ... <-- first element <--,
+ \ /
+ \_________________________________/
+
+*/
+
+struct Xref
+{
+ const void *key;
+ struct LineNumber
+ {
+ struct Pc line;
+ struct LineNumber *next;
+ } *lines;
+ struct Xref *l,*r;
+};
+
+static void Xref_add(struct Xref **root, int (*cmp)(const void*,const void*), const void *key, struct Pc *line) /*{{{*/
+{
+ int res;
+ struct LineNumber **tail;
+ struct LineNumber *new;
+
+ while (*root && (res=cmp(key,(*root)->key))) root=(res<0)?&(*root)->l:&(*root)->r;
+ if (*root==(struct Xref*)0)
+ {
+ *root=malloc(sizeof(struct Xref));
+ (*root)->key=key;
+ (*root)->l=(*root)->r=(struct Xref*)0;
+ /* create new circular list */
+ (*root)->lines=new=malloc(sizeof(struct LineNumber));
+ new->line=*line;
+ new->next=new;
+ }
+ else
+ {
+ /* add to existing circular list */
+ tail=&(*root)->lines;
+ if ((*tail)->line.line!=line->line)
+ {
+ new=malloc(sizeof(struct LineNumber));
+ new->line=*line;
+ new->next=(*tail)->next;
+ (*tail)->next=new;
+ *tail=new;
+ }
+ }
+}
+/*}}}*/
+static void Xref_destroy(struct Xref *root) /*{{{*/
+{
+ if (root)
+ {
+ struct LineNumber *cur,*next,*tail;
+
+ Xref_destroy(root->l);
+ Xref_destroy(root->r);
+ cur=tail=root->lines;
+ do
+ {
+ next=cur->next;
+ free(cur);
+ cur=next;
+ } while (cur!=tail);
+ free(root);
+ }
+}
+/*}}}*/
+static void Xref_print(struct Xref *root, void (*print)(const void *key, struct Program *p, int chn), struct Program *p, int chn) /*{{{*/
+{
+ if (root)
+ {
+ const struct LineNumber *cur,*tail;
+
+ Xref_print(root->l,print,p,chn);
+ print(root->key,p,chn);
+ cur=tail=root->lines;
+ do
+ {
+ char buf[128];
+
+ cur=cur->next;
+ if (FS_charpos(chn)>72) FS_putChars(chn,"\n ");
+ sprintf(buf," %ld",Program_lineNumber(p,&cur->line));
+ FS_putChars(chn,buf);
+ } while (cur!=tail);
+ FS_putChar(chn,'\n');
+ Xref_print(root->r,print,p,chn);
+ }
+}
+/*}}}*/
+static int cmpLine(const void *a, const void *b) /*{{{*/
+{
+ const register struct Pc *pcA=(const struct Pc*)a,*pcB=(const struct Pc*)b;
+
+ return pcA->line-pcB->line;
+}
+/*}}}*/
+static void printLine(const void *k, struct Program *p, int chn) /*{{{*/
+{
+ char buf[80];
+
+ sprintf(buf,"%8ld",Program_lineNumber(p,(const struct Pc*)k));
+ FS_putChars(chn,buf);
+}
+/*}}}*/
+static int cmpName(const void *a, const void *b) /*{{{*/
+{
+ const register char *funcA=(const char*)a,*funcB=(const char*)b;
+
+ return strcmp(funcA,funcB);
+}
+/*}}}*/
+static void printName(const void *k, struct Program *p, int chn) /*{{{*/
+{
+ size_t len=strlen((const char*)k);
+
+ FS_putChars(chn,(const char*)k);
+ if (len<8) FS_putChars(chn," "+len);
+}
+/*}}}*/
+
+void Program_xref(struct Program *this, int chn) /*{{{*/
+{
+ struct Pc pc;
+ struct Xref *func,*var,*gosub,*goto_;
+ int nl=0;
+
+ assert(this->runnable);
+ func=(struct Xref*)0;
+ var=(struct Xref*)0;
+ gosub=(struct Xref*)0;
+ goto_=(struct Xref*)0;
+
+ for (pc.line=0; pc.line<this->size; ++pc.line)
+ {
+ struct On *on;
+
+ for (on=(struct On*)0,pc.token=this->code[pc.line]; pc.token->type!=T_EOL; ++pc.token)
+ {
+ switch (pc.token->type)
+ {
+ case T_ON: /*{{{*/
+ {
+ on=&pc.token->u.on;
+ break;
+ }
+ /*}}}*/
+ case T_GOTO: /*{{{*/
+ {
+ if (on)
+ {
+ int key;
+
+ for (key=0; key<on->pcLength; ++key) Xref_add(&goto_,cmpLine,&on->pc[key],&pc);
+ on=(struct On*)0;
+ }
+ else Xref_add(&goto_,cmpLine,&pc.token->u.gotopc,&pc);
+ break;
+ }
+ /*}}}*/
+ case T_GOSUB: /*{{{*/
+ {
+ if (on)
+ {
+ int key;
+
+ for (key=0; key<on->pcLength; ++key) Xref_add(&gosub,cmpLine,&on->pc[key],&pc);
+ on=(struct On*)0;
+ }
+ else Xref_add(&gosub,cmpLine,&pc.token->u.gosubpc,&pc);
+ break;
+ }
+ /*}}}*/
+ case T_DEFFN:
+ case T_DEFPROC:
+ case T_FUNCTION:
+ case T_SUB: /*{{{*/
+ {
+ ++pc.token;
+ Xref_add(&func,cmpName,&pc.token->u.identifier->name,&pc);
+ break;
+ }
+ /*}}}*/
+ default: break;
+ }
+ }
+ }
+
+ for (pc.line=0; pc.line<this->size; ++pc.line)
+ {
+ for (pc.token=this->code[pc.line]; pc.token->type!=T_EOL; ++pc.token)
+ {
+ switch (pc.token->type)
+ {
+ case T_DEFFN:
+ case T_DEFPROC:
+ case T_FUNCTION:
+ case T_SUB: /* skip identifier already added above */ /*{{{*/
+ {
+ ++pc.token;
+ break;
+ }
+ /*}}}*/
+ case T_IDENTIFIER: /*{{{*/
+ {
+ /* formal parameters have no assigned symbol */
+ if (pc.token->u.identifier->sym) switch (pc.token->u.identifier->sym->type)
+ {
+ case GLOBALVAR:
+ {
+ Xref_add(&var,cmpName,&pc.token->u.identifier->name,&pc);
+ break;
+ }
+ case USERFUNCTION:
+ {
+ Xref_add(&func,cmpName,&pc.token->u.identifier->name,&pc);
+ break;
+ }
+ default: break;
+ }
+ break;
+ }
+ /*}}}*/
+ default: break;
+ }
+ }
+ }
+
+ if (func)
+ {
+ FS_putChars(chn,_("Function Referenced in line\n"));
+ Xref_print(func,printName,this,chn);
+ Xref_destroy(func);
+ nl=1;
+ }
+
+ if (var)
+ {
+ if (nl) FS_putChar(chn,'\n');
+ FS_putChars(chn,_("Variable Referenced in line\n"));
+ Xref_print(var,printName,this,chn);
+ Xref_destroy(func);
+ nl=1;
+ }
+
+ if (gosub)
+ {
+ if (nl) FS_putChar(chn,'\n');
+ FS_putChars(chn,_("Gosub Referenced in line\n"));
+ Xref_print(gosub,printLine,this,chn);
+ Xref_destroy(gosub);
+ nl=1;
+ }
+
+ if (goto_)
+ {
+ if (nl) FS_putChar(chn,'\n');
+ FS_putChars(chn,_("Goto Referenced in line\n"));
+ Xref_print(goto_,printLine,this,chn);
+ Xref_destroy(goto_);
+ nl=1;
+ }
+}
+/*}}}*/