5 * Created by Alyssa Milburn on Wed May 26 2004.
6 * Copyright (c) 2004 Alyssa Milburn. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
22 #include "exceptions.h"
23 #include "caosScript.h"
24 #include "caoslexer.h"
36 #include <boost/format.hpp>
37 #include <boost/scoped_ptr.hpp>
41 class unexpectedEOIexception
{ };
46 // resolve relocations into fixed addresses
48 ops
.push_back(caosOp(CAOS_STOP
, 0, -1));
50 // std::cout << "Pre-link:" << std::endl << dump();
52 for (unsigned int i
= 1; i
< relocations
.size(); i
++) {
53 // handle relocations-to-relocations
54 int p
= relocations
[i
];
59 for (unsigned int i
= 0; i
< ops
.size(); i
++) {
60 if (op_is_relocatable(ops
[i
].opcode
) && ops
[i
].argument
< 0)
61 ops
[i
].argument
= relocations
[-ops
[i
].argument
];
64 // std::cout << "Post-link:" << std::endl << dump();
68 script::script(const Dialect
*v
, const std::string
&fn
)
69 : fmly(-1), gnus(-1), spcs(-1), scrp(-1),
70 dialect(v
), filename(fn
)
72 // advance past reserved index 0
73 ops
.push_back(caosOp(CAOS_NOP
, 0, -1));
74 relocations
.push_back(0);
78 script::script(const Dialect
*v
, const std::string
&fn
,
79 int fmly_
, int gnus_
, int spcs_
, int scrp_
)
80 : fmly(fmly_
), gnus(gnus_
), spcs(spcs_
), scrp(scrp_
),
81 dialect(v
), filename(fn
)
83 ops
.push_back(caosOp(CAOS_NOP
, 0, -1));
84 relocations
.push_back(0);
88 std::string
script::dump() {
89 std::ostringstream oss
;
90 oss
<< "Relocations:" << std::endl
;
91 for (unsigned int i
= 1; i
< relocations
.size(); i
++) {
92 oss
<< boost::format("%08d -> %08d") % i
% relocations
[i
]
95 oss
<< "Code:" << std::endl
;
96 for (unsigned int i
= 0; i
< ops
.size(); i
++) {
97 oss
<< boost::format("%08d: ") % i
;
98 oss
<< dumpOp(dialect
, ops
[i
]);
104 caosScript::caosScript(const std::string
&dialect
, const std::string
&fn
) {
106 d
= dialects
[dialect
].get();
108 throw parseException(std::string("Unknown dialect ") + dialect
);
109 current
= installer
= shared_ptr
<script
> (new script(d
, fn
));
113 caosScript::~caosScript() {
114 // Nothing to do, yay shared_ptr!
117 void caosScript::installScripts() {
118 std::vector
<shared_ptr
<script
> >::iterator i
= scripts
.begin();
119 while (i
!= scripts
.end()) {
120 shared_ptr
<script
> s
= *i
;
121 world
.scriptorium
.addScript(s
->fmly
, s
->gnus
, s
->spcs
, s
->scrp
, s
);
126 void caosScript::installInstallScript(unsigned char family
, unsigned char genus
, unsigned short species
, unsigned short eventid
) {
127 assert((d
->name
== "c1") || (d
->name
== "c2"));
129 installer
->fmly
= family
;
130 installer
->gnus
= genus
;
131 installer
->spcs
= species
;
132 installer
->scrp
= eventid
;
134 world
.scriptorium
.addScript(installer
->fmly
, installer
->gnus
, installer
->spcs
, installer
->scrp
, installer
);
138 saveVisit::saveVisit(caosScript
*s
)
142 void saveVisit::operator()(const CAOSCmd
&cmd
) const {
143 scr
->errindex
= scr
->traceindex
= cmd
.traceidx
- 1;
144 if (cmd
.op
->rettype
!= CI_VARIABLE
) {
145 throw parseException(std::string("RValue ") + cmd
.op
->fullname
+ " used where LValue expected");
147 scr
->emitOp(CAOS_RESTORE_AUX
, cmd
.arguments
.size());
148 scr
->emitOp(CAOS_SAVE_CMD
, scr
->d
->cmd_index(cmd
.op
));
151 evalVisit::evalVisit(caosScript
*s
, bool save_here_
)
152 : scr(s
), save_here(save_here_
)
157 void evalVisit::operator()(const CAOSCmd
&cmd
) const {
158 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++) {
159 bool save_there
= (cmd
.op
->argtypes
[i
] == CI_VARIABLE
);
160 cmd
.arguments
[i
]->eval(scr
, save_there
);
162 scr
->traceindex
= cmd
.traceidx
- 1;
163 // If we're to be invoked to save our result later,
164 // stash our args for that time.
166 // Note: These indices refer to stack positions, with 0 being the top.
167 // We thus transfer the arguments in reverse order, as the order will again be
168 // reversed when the stack is restored.
169 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++)
170 scr
->emitOp(CAOS_PUSH_AUX
, i
);
172 scr
->emitOp(CAOS_CMD
, scr
->d
->cmd_index(cmd
.op
));
173 // If we emit variable-result arguments as well, we need to move our
174 // result down below them.
175 // This is theoretical at the moment - no expression-type commands also
176 // write back to their args.
178 if (cmd
.op
->rettype
!= CI_COMMAND
) {
180 for (size_t i
= 0; cmd
.op
->argtypes
[i
] != CI_END
; i
++) {
181 if (cmd
.op
->argtypes
[i
] == CI_VARIABLE
)
185 scr
->emitOp(CAOS_STACK_ROT
, rotcount
);
187 for (int i
= cmd
.arguments
.size() - 1; i
>= 0; i
--) {
188 if (cmd
.op
->argtypes
[i
] == CI_VARIABLE
)
189 cmd
.arguments
[i
]->save(scr
);
193 void evalVisit::operator()(const caosVar
&v
) const {
195 int val
= v
.getInt();
196 if (val
>= -(1 << 24) && val
< (1 << 24)) {
197 scr
->emitOp(CAOS_CONSTINT
, val
);
201 scr
->current
->consts
.push_back(v
);
202 scr
->emitOp(CAOS_CONST
, scr
->current
->consts
.size() - 1);
204 void evalVisit::operator()(const bytestring_t
&bs
) const {
205 scr
->current
->bytestrs
.push_back(bs
);
206 scr
->emitOp(CAOS_BYTESTR
, scr
->current
->bytestrs
.size() - 1);
209 int costVisit::operator()(const CAOSCmd
&cmd
) const {
210 int accum
= cmd
.op
->evalcost
;
211 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++)
212 accum
+= cmd
.arguments
[i
]->cost();
239 token
*caosScript::tokenPeek() {
240 if ((size_t)curindex
>= tokens
->size())
242 return &(*tokens
)[curindex
];
245 token
*caosScript::getToken(toktype expected
) {
246 token
*t
= tokenPeek();
248 token
&r
= (t
? *t
: dummy
);
252 if (expected
!= ANYTOKEN
&& r
.type() != expected
)
260 void caosScript::putBackToken(token
*) {
262 errindex
= curindex
- 1; // curindex refers to the /next/ token to be parsed
263 // so make sure we refer to the token before it
266 void caosScript::parse(std::istream
&in
) {
268 // slurp our input stream
269 std::string caostext
= readfile(in
);
270 // run the token parser
273 using_c2
= (d
->name
== "c1" || d
->name
== "c2");
275 tokens
= shared_ptr
<std::vector
<token
> >(new std::vector
<token
>());
276 lexcaos(*tokens
, caostext
.c_str(), using_c2
);
278 curindex
= errindex
= traceindex
= 0;
281 parseloop(ST_INSTALLER
, NULL
);
283 std::ostringstream oss
;
284 shared_ptr
<std::vector
<toktrace
> > tokinfo(new std::vector
<toktrace
>());
285 for (size_t p
= 0; p
< tokens
->size(); p
++) {
286 std::string tok
= (*tokens
)[p
].format();
287 int len
= tok
.size();
290 throw parseException("Overlong token");
293 tokinfo
->push_back(toktrace(len
, (*tokens
)[p
].lineno
));
295 shared_str
code(oss
.str());
296 installer
->code
= code
;
297 installer
->tokinfo
= tokinfo
;
301 removal
->tokinfo
= tokinfo
;
302 removal
->code
= code
;
304 std::vector
<shared_ptr
<script
> >::iterator i
= scripts
.begin();
305 while (i
!= scripts
.end()) {
306 (*i
)->tokinfo
= tokinfo
;
310 } catch (parseException
&e
) {
311 e
.filename
= filename
;
314 if (errindex
< 0 || (size_t)errindex
>= tokens
->size())
316 e
.lineno
= (*tokens
)[errindex
].lineno
;
317 e
.context
= boost::shared_ptr
<std::vector
<token
> >(new std::vector
<token
>());
318 /* We'd like to capture N tokens on each side of the target, but
319 * if we can't get all those from one side, get it from the other.
322 int leftct
= contextlen
;
323 int rightct
= contextlen
;
325 if (errindex
< leftct
) {
326 rightct
+= leftct
- errindex
;
329 if ((size_t)(errindex
+ rightct
+ 1) >= tokens
->size()) {
330 int overflow
= errindex
+ rightct
+ 1 - tokens
->size();
332 while (overflow
> 0 && errindex
> leftct
) {
337 assert(leftct
>= 0 && rightct
>= 0 && errindex
>= leftct
&& (size_t)(errindex
+ rightct
) < tokens
->size());
339 if (errindex
- leftct
!= 0) {
340 e
.context
->push_back(token());
341 e
.context
->back().payload
= std::string("...");
344 for (int i
= errindex
- leftct
; i
< errindex
+ rightct
; i
++) {
345 e
.context
->push_back((*tokens
)[i
]);
347 if (errindex
+ rightct
+ 1 < (int)tokens
->size()) {
348 e
.context
->push_back(token());
349 e
.context
->back().payload
= std::string("...");
351 e
.ctxoffset
= leftct
;
356 const cmdinfo
*caosScript::readCommand(token
*t
, const std::string
&prefix
) {
357 std::string fullname
= prefix
+ t
->word();
358 errindex
= t
->index
+ 1;
359 const cmdinfo
*ci
= d
->find_command(fullname
.c_str());
360 // See if there'{s a subcommand namespace
363 t2
= getToken(TOK_WORD
);
364 if (!t2
|| t2
->type() != TOK_WORD
)
365 throw parseException("dummy");
366 return readCommand(t2
, fullname
+ " ");
367 } catch (parseException
&e
) {
368 if (ci
->argtypes
&& ci
->argtypes
[0] == CI_SUBCOMMAND
)
376 void caosScript::emitOp(opcode_t op
, int argument
) {
377 if (op
== CAOS_YIELD
&& engine
.version
< 3 && enumdepth
> 0)
379 current
->ops
.push_back(caosOp(op
, argument
, traceindex
));
382 void caosScript::emitExpr(boost::shared_ptr
<CAOSExpression
> ce
) {
383 ce
->eval(this, false);
384 int cost
= ce
->cost();
386 emitOp(CAOS_YIELD
, cost
);
389 boost::shared_ptr
<CAOSExpression
> caosScript::readExpr(const enum ci_type xtype
) {
390 token
*t
= getToken();
391 traceindex
= errindex
= curindex
;
392 if (xtype
== CI_BAREWORD
) {
393 if (t
->type() == TOK_WORD
) {
394 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, caosVar(t
->word())));
395 } else if (t
->type() == TOK_CONST
) {
396 if (t
->constval().getType() != CAOSSTR
)
398 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->constval()));
402 assert(!"UNREACHABLE");
403 return boost::shared_ptr
<CAOSExpression
>();
407 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->constval()));
409 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->bytestr()));
410 case TOK_WORD
: break; // fall through to remainder of function
411 default: t
->unexpected();
414 std::string oldpayload
= t
->word();
415 if (t
->word() == "face" && xtype
!= CI_COMMAND
) {
416 // horrible hack, yay
417 if (xtype
== CI_NUMERIC
)
418 t
->payload
= std::string("face int");
420 t
->payload
= std::string("face string");
423 boost::shared_ptr
<CAOSExpression
> ce(new CAOSExpression(errindex
, CAOSCmd()));
424 CAOSCmd
*cmd
= boost::get
<CAOSCmd
>(&ce
->value
);
426 if (t
->word().size() == 4 && isdigit(t
->word()[2]) && isdigit(t
->word()[3])) {
427 if ( !strncmp(t
->word().c_str(), "va", 2)
428 || !strncmp(t
->word().c_str(), "ov", 2)
429 || !strncmp(t
->word().c_str(), "mv", 2)) {
430 int idx
= atoi(t
->word().c_str() + 2);
431 t
->payload
= t
->word().substr(0, 2) + "xx";
432 const cmdinfo
*op
= readCommand(t
, std::string("expr "));
433 t
->payload
= oldpayload
;
435 boost::shared_ptr
<CAOSExpression
> arg(new CAOSExpression(errindex
, caosVar(idx
)));
437 cmd
->arguments
.push_back(arg
);
442 if (t
->word().size() == 4 && isdigit(t
->word()[3]) && engine
.version
< 3) {
444 if ( !strncmp(t
->word().c_str(), "obv", 3)
445 || !strncmp(t
->word().c_str(), "var", 3)) {
446 int idx
= atoi(t
->word().c_str() + 3);
447 t
->payload
= t
->word().substr(0, 3) + "x";
448 const cmdinfo
*op
= readCommand(t
, std::string("expr "));
449 t
->payload
= oldpayload
;
451 boost::shared_ptr
<CAOSExpression
> arg(new CAOSExpression(errindex
, caosVar(idx
)));
453 cmd
->arguments
.push_back(arg
);
458 const cmdinfo
*ci
= readCommand(t
, std::string(xtype
== CI_COMMAND
? "cmd " : "expr "));
459 t
->payload
= oldpayload
;
461 for (int i
= 0; ci
->argtypes
[i
] != CI_END
; i
++) {
462 cmd
->arguments
.push_back(readExpr(ci
->argtypes
[i
]));
467 int caosScript::readCond() {
468 token
*t
= getToken(TOK_WORD
);
469 typedef struct { const char *n
; int cnd
; } cond_entry
;
470 const static cond_entry conds
[] = {
482 const cond_entry
*c
= conds
;
483 while (c
->n
!= NULL
) {
484 if (t
->word() == c
->n
)
488 throw parseException(std::string("Unexpected non-condition word: ") + t
->word());
491 void caosScript::parseCondition() {
492 emitOp(CAOS_CONSTINT
, 1);
494 bool nextIsAnd
= true;
496 boost::shared_ptr
<CAOSExpression
> a1
, a2
;
497 a1
= readExpr(CI_ANYVALUE
);
498 int cond
= readCond();
499 a2
= readExpr(CI_ANYVALUE
);
502 emitOp(CAOS_COND
, cond
| (nextIsAnd
? CAND
: COR
));
504 token
*peek
= tokenPeek();
506 if (peek
->type() != TOK_WORD
) break;
507 if (peek
->word() == "and") {
510 } else if (peek
->word() == "or") {
517 void caosScript::parseloop(int state
, void *info
) {
519 while ((t
= getToken(ANYTOKEN
))) {
520 traceindex
= errindex
;
521 if (t
->type() == EOI
) {
528 throw parseException("Unexpected end of input");
531 if (t
->type() != TOK_WORD
) {
532 throw parseException("Unexpected non-word token");
534 if (t
->word() == "scrp") {
535 if (state
!= ST_INSTALLER
&& state
!= ST_BODY
&& state
!= ST_REMOVAL
)
536 throw parseException("Unexpected SCRP");
540 for (int i
= 0; i
< 4; i
++) {
541 caosVar val
= getToken(TOK_CONST
)->constval();
542 if (!val
.getType() == CAOSINT
)
543 throw parseException("Expected integer constant");
544 bits
[i
] = val
.getInt();
550 scripts
.push_back(shared_ptr
<script
>(new script(d
, filename
, fmly
, gnus
, spcs
, scrp
)));
551 current
= scripts
.back();
552 } else if (t
->word() == "rscr") {
553 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
)
556 throw parseException("Unexpected RSCR");
558 removal
= shared_ptr
<script
>(new script(d
, filename
));
560 } else if (t
->word() == "iscr") {
561 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
)
562 state
= ST_INSTALLER
;
564 throw parseException("Unexpected RSCR");
567 } else if (t
->word() == "endm") {
568 emitOp(CAOS_STOP
, 0);
570 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
) {
572 state
= ST_INSTALLER
;
575 // I hate you. Die in a fire.
579 // No we will not emit c_ENDM() thankyouverymuch
581 } else if (t
->word() == "enum"
582 || t
->word() == "esee"
583 || t
->word() == "etch"
584 || t
->word() == "epas"
585 || t
->word() == "econ") {
586 int nextreloc
= current
->newRelocation();
590 emitExpr(readExpr(CI_COMMAND
));
591 emitOp(CAOS_JMP
, nextreloc
);
592 int startp
= current
->getNextIndex();
593 parseloop(ST_ENUM
, NULL
);
595 current
->fixRelocation(nextreloc
);
598 emitOp(CAOS_ENUMPOP
, startp
);
599 } else if (t
->word() == "next") {
600 if (state
!= ST_ENUM
) {
601 throw parseException("Unexpected NEXT");
605 } else if (t
->word() == "subr") {
606 // Yes, this will work in a doif or whatever. This is UB, it may
607 // be made to not compile later.
608 t
= getToken(TOK_WORD
);
609 std::string label
= t
->word();
610 emitOp(CAOS_STOP
, 0);
611 current
->affixLabel(label
);
612 } else if (t
->word() == "gsub") {
613 t
= getToken(TOK_WORD
);
614 std::string label
= t
->word();
615 emitOp(CAOS_GSUB
, current
->getLabel(label
));
617 } else if (t
->word() == "loop") {
618 int loop
= current
->getNextIndex();
620 parseloop(ST_LOOP
, (void *)&loop
);
621 } else if (t
->word() == "untl") {
622 if (state
!= ST_LOOP
)
623 throw parseException("Unexpected UNTL");
624 // TODO: zerocost logic inversion - do in c_UNTL()?
625 int loop
= *(int *)info
;
626 int out
= current
->newRelocation();
629 emitOp(CAOS_CJMP
, out
);
630 emitOp(CAOS_JMP
, loop
);
631 current
->fixRelocation(out
);
633 } else if (t
->word() == "ever") {
634 if (state
!= ST_LOOP
)
635 throw parseException("Unexpected EVER");
636 int loop
= *(int *)info
;
637 emitOp(CAOS_JMP
, loop
);
640 } else if (t
->word() == "reps") {
642 ri
.jnzreloc
= current
->newRelocation();
644 emitExpr(readExpr(CI_COMMAND
));
645 emitOp(CAOS_JMP
, ri
.jnzreloc
);
646 ri
.loopidx
= current
->getNextIndex();
647 parseloop(ST_REPS
, (void *)&ri
);
648 } else if (t
->word() == "repe") {
649 if (state
!= ST_REPS
)
650 throw parseException("Unexpected repe");
651 struct repsinfo
*ri
= (repsinfo
*)info
;
652 current
->fixRelocation(ri
->jnzreloc
);
653 emitOp(CAOS_DECJNZ
, ri
->loopidx
);
657 } else if (t
->word() == "doif") {
659 di
.donereloc
= current
->newRelocation();
660 di
.failreloc
= current
->newRelocation();
661 int okreloc
= current
->newRelocation();
665 emitOp(CAOS_CJMP
, okreloc
);
666 emitOp(CAOS_JMP
, di
.failreloc
);
667 current
->fixRelocation(okreloc
);
668 parseloop(ST_DOIF
, (void *)&di
);
670 current
->fixRelocation(di
.failreloc
);
671 current
->fixRelocation(di
.donereloc
);
673 } else if (t
->word() == "elif") {
674 if (state
!= ST_DOIF
) {
675 // XXX this is horrible
676 t
->payload
= std::string("doif");
679 struct doifinfo
*di
= (struct doifinfo
*)info
;
680 int okreloc
= current
->newRelocation();
682 emitOp(CAOS_JMP
, di
->donereloc
);
683 current
->fixRelocation(di
->failreloc
);
684 di
->failreloc
= current
->newRelocation();
687 emitOp(CAOS_CJMP
, okreloc
);
688 emitOp(CAOS_JMP
, di
->failreloc
);
689 current
->fixRelocation(okreloc
);
690 parseloop(ST_DOIF
, info
);
692 } else if (t
->word() == "else") {
693 if (state
!= ST_DOIF
)
694 throw parseException("Unexpected ELSE");
695 struct doifinfo
*di
= (struct doifinfo
*)info
;
697 throw parseException("Duplicate ELSE");
698 emitOp(CAOS_JMP
, di
->donereloc
);
699 current
->fixRelocation(di
->failreloc
);
702 } else if (t
->word() == "endi") {
703 if (state
!= ST_DOIF
) {
704 if (engine
.version
>= 3)
705 throw parseException("Unexpected ENDI");
706 // you are a horrible person if you get here
707 // damn you CL coders.
712 if (t
->word() == "dbg:") {
713 token
*t2
= tokenPeek();
714 if (t2
&& t2
->type() == TOK_WORD
&& t2
->word() == "asrt") {
716 emitOp(CAOS_CONSTINT
, 1);
718 int endreloc
= current
->newRelocation();
719 emitOp(CAOS_CJMP
, endreloc
);
720 emitCmd("cmd dbg: asrt");
721 current
->fixRelocation(endreloc
);
726 emitExpr(readExpr(CI_COMMAND
));
731 void caosScript::emitCmd(const char *name
) {
732 const cmdinfo
*ci
= d
->find_command(name
);
733 emitOp(CAOS_CMD
, d
->cmd_index(ci
));
735 emitOp(CAOS_YIELD
, ci
->evalcost
);
738 void CAOSExpression::eval(caosScript
*scr
, bool save_here
) const {
739 boost::apply_visitor(evalVisit(scr
, save_here
), value
);
742 void CAOSExpression::save(caosScript
*scr
) const {
743 boost::apply_visitor(saveVisit(scr
), value
);
746 int CAOSExpression::cost() const {
747 return boost::apply_visitor(costVisit(), value
);