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"
31 #undef yyFlexLexer // flex/C++ is horrrrible I should use the C interface instead probably
38 #include <boost/format.hpp>
39 #include <boost/scoped_ptr.hpp>
43 class unexpectedEOIexception
{ };
48 // resolve relocations into fixed addresses
50 ops
.push_back(caosOp(CAOS_STOP
, 0, -1));
52 // std::cout << "Pre-link:" << std::endl << dump();
54 for (unsigned int i
= 1; i
< relocations
.size(); i
++) {
55 // handle relocations-to-relocations
56 int p
= relocations
[i
];
61 for (unsigned int i
= 0; i
< ops
.size(); i
++) {
62 if (op_is_relocatable(ops
[i
].opcode
) && ops
[i
].argument
< 0)
63 ops
[i
].argument
= relocations
[-ops
[i
].argument
];
66 // std::cout << "Post-link:" << std::endl << dump();
70 script::script(const Dialect
*v
, const std::string
&fn
)
71 : fmly(-1), gnus(-1), spcs(-1), scrp(-1),
72 dialect(v
), filename(fn
)
74 // advance past reserved index 0
75 ops
.push_back(caosOp(CAOS_NOP
, 0, -1));
76 relocations
.push_back(0);
80 script::script(const Dialect
*v
, const std::string
&fn
,
81 int fmly_
, int gnus_
, int spcs_
, int scrp_
)
82 : fmly(fmly_
), gnus(gnus_
), spcs(spcs_
), scrp(scrp_
),
83 dialect(v
), filename(fn
)
85 ops
.push_back(caosOp(CAOS_NOP
, 0, -1));
86 relocations
.push_back(0);
90 std::string
script::dump() {
91 std::ostringstream oss
;
92 oss
<< "Relocations:" << std::endl
;
93 for (unsigned int i
= 1; i
< relocations
.size(); i
++) {
94 oss
<< boost::format("%08d -> %08d") % i
% relocations
[i
]
97 oss
<< "Code:" << std::endl
;
98 for (unsigned int i
= 0; i
< ops
.size(); i
++) {
99 oss
<< boost::format("%08d: ") % i
;
100 oss
<< dumpOp(dialect
, ops
[i
]);
106 caosScript::caosScript(const std::string
&dialect
, const std::string
&fn
) {
108 d
= dialects
[dialect
].get();
110 throw parseException(std::string("Unknown dialect ") + dialect
);
111 current
= installer
= shared_ptr
<script
> (new script(d
, fn
));
115 caosScript::~caosScript() {
116 // Nothing to do, yay shared_ptr!
119 void caosScript::installScripts() {
120 std::vector
<shared_ptr
<script
> >::iterator i
= scripts
.begin();
121 while (i
!= scripts
.end()) {
122 shared_ptr
<script
> s
= *i
;
123 world
.scriptorium
.addScript(s
->fmly
, s
->gnus
, s
->spcs
, s
->scrp
, s
);
128 void caosScript::installInstallScript(unsigned char family
, unsigned char genus
, unsigned short species
, unsigned short eventid
) {
129 assert((d
->name
== "c1") || (d
->name
== "c2"));
131 installer
->fmly
= family
;
132 installer
->gnus
= genus
;
133 installer
->spcs
= species
;
134 installer
->scrp
= eventid
;
136 world
.scriptorium
.addScript(installer
->fmly
, installer
->gnus
, installer
->spcs
, installer
->scrp
, installer
);
140 saveVisit::saveVisit(caosScript
*s
)
144 void saveVisit::operator()(const CAOSCmd
&cmd
) const {
145 scr
->errindex
= scr
->traceindex
= cmd
.traceidx
- 1;
146 if (cmd
.op
->rettype
!= CI_VARIABLE
) {
147 throw parseException(std::string("RValue ") + cmd
.op
->fullname
+ " used where LValue expected");
149 scr
->emitOp(CAOS_RESTORE_AUX
, cmd
.arguments
.size());
150 scr
->emitOp(CAOS_SAVE_CMD
, scr
->d
->cmd_index(cmd
.op
));
153 evalVisit::evalVisit(caosScript
*s
, bool save_here_
)
154 : scr(s
), save_here(save_here_
)
159 void evalVisit::operator()(const CAOSCmd
&cmd
) const {
160 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++) {
161 bool save_there
= (cmd
.op
->argtypes
[i
] == CI_VARIABLE
);
162 cmd
.arguments
[i
]->eval(scr
, save_there
);
164 scr
->traceindex
= cmd
.traceidx
- 1;
165 // If we're to be invoked to save our result later,
166 // stash our args for that time.
168 // Note: These indices refer to stack positions, with 0 being the top.
169 // We thus transfer the arguments in reverse order, as the order will again be
170 // reversed when the stack is restored.
171 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++)
172 scr
->emitOp(CAOS_PUSH_AUX
, i
);
174 scr
->emitOp(CAOS_CMD
, scr
->d
->cmd_index(cmd
.op
));
175 // If we emit variable-result arguments as well, we need to move our
176 // result down below them.
177 // This is theoretical at the moment - no expression-type commands also
178 // write back to their args.
180 if (cmd
.op
->rettype
!= CI_COMMAND
) {
182 for (size_t i
= 0; cmd
.op
->argtypes
[i
] != CI_END
; i
++) {
183 if (cmd
.op
->argtypes
[i
] == CI_VARIABLE
)
187 scr
->emitOp(CAOS_STACK_ROT
, rotcount
);
189 for (int i
= cmd
.arguments
.size() - 1; i
>= 0; i
--) {
190 if (cmd
.op
->argtypes
[i
] == CI_VARIABLE
)
191 cmd
.arguments
[i
]->save(scr
);
195 void evalVisit::operator()(const caosVar
&v
) const {
197 int val
= v
.getInt();
198 if (val
>= -(1 << 24) && val
< (1 << 24)) {
199 scr
->emitOp(CAOS_CONSTINT
, val
);
203 scr
->current
->consts
.push_back(v
);
204 scr
->emitOp(CAOS_CONST
, scr
->current
->consts
.size() - 1);
206 void evalVisit::operator()(const bytestring_t
&bs
) const {
207 scr
->current
->bytestrs
.push_back(bs
);
208 scr
->emitOp(CAOS_BYTESTR
, scr
->current
->bytestrs
.size() - 1);
211 int costVisit::operator()(const CAOSCmd
&cmd
) const {
212 int accum
= cmd
.op
->evalcost
;
213 for (size_t i
= 0; i
< cmd
.arguments
.size(); i
++)
214 accum
+= cmd
.arguments
[i
]->cost();
241 token
*caosScript::tokenPeek() {
242 if ((size_t)curindex
>= tokens
->size())
244 return &(*tokens
)[curindex
];
247 token
*caosScript::getToken(toktype expected
) {
248 token
*t
= tokenPeek();
250 token
&r
= (t
? *t
: dummy
);
254 if (expected
!= ANYTOKEN
&& r
.type() != expected
)
262 void caosScript::putBackToken(token
*) {
264 errindex
= curindex
- 1; // curindex refers to the /next/ token to be parsed
265 // so make sure we refer to the token before it
268 void caosScript::parse(std::istream
&in
) {
270 // run the token parser
272 extern int lex_lineno
;
273 extern bool using_c2
;
274 using_c2
= (d
->name
== "c1" || d
->name
== "c2");
276 boost::scoped_ptr
<FlexLexer
> l(
277 using_c2
? (FlexLexer
*)new c2FlexLexer()
278 : (FlexLexer
*)new c2eFlexLexer()
282 tokens
= shared_ptr
<std::vector
<token
> >(new std::vector
<token
>());
284 tokens
->push_back(lasttok
);
285 tokens
->back().lineno
= lex_lineno
;
286 tokens
->back().index
= tokens
->size() - 1;
288 tokens
->push_back(token()); // tokens default to being EOI tokens
289 tokens
->back().lineno
= lex_lineno
;
290 tokens
->back().index
= tokens
->size() - 1;
292 curindex
= errindex
= traceindex
= 0;
295 parseloop(ST_INSTALLER
, NULL
);
297 std::ostringstream oss
;
298 shared_ptr
<std::vector
<toktrace
> > tokinfo(new std::vector
<toktrace
>());
299 for (size_t p
= 0; p
< tokens
->size(); p
++) {
300 std::string tok
= (*tokens
)[p
].format();
301 int len
= tok
.size();
304 throw parseException("Overlong token");
307 tokinfo
->push_back(toktrace(len
, (*tokens
)[p
].lineno
));
309 shared_str
code(oss
.str());
310 installer
->code
= code
;
311 installer
->tokinfo
= tokinfo
;
315 removal
->tokinfo
= tokinfo
;
316 removal
->code
= code
;
318 std::vector
<shared_ptr
<script
> >::iterator i
= scripts
.begin();
319 while (i
!= scripts
.end()) {
320 (*i
)->tokinfo
= tokinfo
;
324 } catch (parseException
&e
) {
325 e
.filename
= filename
;
328 if (errindex
< 0 || (size_t)errindex
>= tokens
->size())
330 e
.lineno
= (*tokens
)[errindex
].lineno
;
331 e
.context
= boost::shared_ptr
<std::vector
<token
> >(new std::vector
<token
>());
332 /* We'd like to capture N tokens on each side of the target, but
333 * if we can't get all those from one side, get it from the other.
336 int leftct
= contextlen
;
337 int rightct
= contextlen
;
339 if (errindex
< leftct
) {
340 rightct
+= leftct
- errindex
;
343 if ((size_t)(errindex
+ rightct
+ 1) >= tokens
->size()) {
344 int overflow
= errindex
+ rightct
+ 1 - tokens
->size();
346 while (overflow
> 0 && errindex
> leftct
) {
351 assert(leftct
>= 0 && rightct
>= 0 && errindex
>= leftct
&& (size_t)(errindex
+ rightct
) < tokens
->size());
353 if (errindex
- leftct
!= 0) {
354 e
.context
->push_back(token());
355 e
.context
->back().payload
= std::string("...");
358 for (int i
= errindex
- leftct
; i
< errindex
+ rightct
; i
++) {
359 e
.context
->push_back((*tokens
)[i
]);
361 if (errindex
+ rightct
+ 1 < (int)tokens
->size()) {
362 e
.context
->push_back(token());
363 e
.context
->back().payload
= std::string("...");
365 e
.ctxoffset
= leftct
;
370 const cmdinfo
*caosScript::readCommand(token
*t
, const std::string
&prefix
) {
371 std::string fullname
= prefix
+ t
->word();
373 const cmdinfo
*ci
= d
->find_command(fullname
.c_str());
374 // See if there'{s a subcommand namespace
377 t2
= getToken(TOK_WORD
);
378 if (!t2
|| t2
->type() != TOK_WORD
)
379 throw parseException("dummy");
380 return readCommand(t2
, fullname
+ " ");
381 } catch (parseException
&e
) {
382 if (ci
->argtypes
&& ci
->argtypes
[0] == CI_SUBCOMMAND
)
390 void caosScript::emitOp(opcode_t op
, int argument
) {
391 if (op
== CAOS_YIELD
&& engine
.version
< 3 && enumdepth
> 0)
393 current
->ops
.push_back(caosOp(op
, argument
, traceindex
));
396 void caosScript::emitExpr(boost::shared_ptr
<CAOSExpression
> ce
) {
397 ce
->eval(this, false);
398 int cost
= ce
->cost();
400 emitOp(CAOS_YIELD
, cost
);
403 boost::shared_ptr
<CAOSExpression
> caosScript::readExpr(const enum ci_type xtype
) {
404 token
*t
= getToken();
405 traceindex
= errindex
= curindex
;
406 if (xtype
== CI_BAREWORD
) {
407 if (t
->type() == TOK_WORD
) {
408 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, caosVar(t
->word())));
409 } else if (t
->type() == TOK_CONST
) {
410 if (t
->constval().getType() != CAOSSTR
)
412 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->constval()));
416 assert(!"UNREACHABLE");
417 return boost::shared_ptr
<CAOSExpression
>();
421 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->constval()));
423 return boost::shared_ptr
<CAOSExpression
>(new CAOSExpression(errindex
, t
->bytestr()));
424 case TOK_WORD
: break; // fall through to remainder of function
425 default: t
->unexpected();
428 std::string oldpayload
= t
->word();
429 if (t
->word() == "face" && xtype
!= CI_COMMAND
) {
430 // horrible hack, yay
431 if (xtype
== CI_NUMERIC
)
432 t
->payload
= std::string("face int");
434 t
->payload
= std::string("face string");
437 boost::shared_ptr
<CAOSExpression
> ce(new CAOSExpression(errindex
, CAOSCmd()));
438 CAOSCmd
*cmd
= boost::get
<CAOSCmd
>(&ce
->value
);
440 if (t
->word().size() == 4 && isdigit(t
->word()[2]) && isdigit(t
->word()[3])) {
441 if ( !strncmp(t
->word().c_str(), "va", 2)
442 || !strncmp(t
->word().c_str(), "ov", 2)
443 || !strncmp(t
->word().c_str(), "mv", 2)) {
444 int idx
= atoi(t
->word().c_str() + 2);
445 t
->payload
= t
->word().substr(0, 2) + "xx";
446 const cmdinfo
*op
= readCommand(t
, std::string("expr "));
447 t
->payload
= oldpayload
;
449 boost::shared_ptr
<CAOSExpression
> arg(new CAOSExpression(errindex
, caosVar(idx
)));
451 cmd
->arguments
.push_back(arg
);
456 if (t
->word().size() == 4 && isdigit(t
->word()[3]) && engine
.version
< 3) {
458 if ( !strncmp(t
->word().c_str(), "obv", 3)
459 || !strncmp(t
->word().c_str(), "var", 3)) {
460 int idx
= atoi(t
->word().c_str() + 3);
461 t
->payload
= t
->word().substr(0, 3) + "x";
462 const cmdinfo
*op
= readCommand(t
, std::string("expr "));
463 t
->payload
= oldpayload
;
465 boost::shared_ptr
<CAOSExpression
> arg(new CAOSExpression(errindex
, caosVar(idx
)));
467 cmd
->arguments
.push_back(arg
);
472 const cmdinfo
*ci
= readCommand(t
, std::string(xtype
== CI_COMMAND
? "cmd " : "expr "));
473 t
->payload
= oldpayload
;
475 for (int i
= 0; ci
->argtypes
[i
] != CI_END
; i
++) {
476 cmd
->arguments
.push_back(readExpr(ci
->argtypes
[i
]));
481 int caosScript::readCond() {
482 token
*t
= getToken(TOK_WORD
);
483 typedef struct { const char *n
; int cnd
; } cond_entry
;
484 const static cond_entry conds
[] = {
496 const cond_entry
*c
= conds
;
497 while (c
->n
!= NULL
) {
498 if (t
->word() == c
->n
)
502 throw parseException(std::string("Unexpected non-condition word: ") + t
->word());
505 void caosScript::parseCondition() {
506 emitOp(CAOS_CONSTINT
, 1);
508 bool nextIsAnd
= true;
510 boost::shared_ptr
<CAOSExpression
> a1
, a2
;
511 a1
= readExpr(CI_ANYVALUE
);
512 int cond
= readCond();
513 a2
= readExpr(CI_ANYVALUE
);
516 emitOp(CAOS_COND
, cond
| (nextIsAnd
? CAND
: COR
));
518 token
*peek
= tokenPeek();
520 if (peek
->type() != TOK_WORD
) break;
521 if (peek
->word() == "and") {
524 } else if (peek
->word() == "or") {
531 void caosScript::parseloop(int state
, void *info
) {
533 while ((t
= getToken(ANYTOKEN
))) {
534 traceindex
= errindex
;
535 if (t
->type() == EOI
) {
542 throw parseException("Unexpected end of input");
545 if (t
->type() != TOK_WORD
) {
546 throw parseException("Unexpected non-word token");
548 if (t
->word() == "scrp") {
550 if (state
!= ST_INSTALLER
&& state
!= ST_BODY
&& state
!= ST_REMOVAL
)
551 throw parseException("Unexpected SCRP");
554 for (int i
= 0; i
< 4; i
++) {
555 caosVar val
= getToken(TOK_CONST
)->constval();
556 if (!val
.getType() == CAOSINT
)
557 throw parseException("Expected integer constant");
558 bits
[i
] = val
.getInt();
564 scripts
.push_back(shared_ptr
<script
>(new script(d
, filename
, fmly
, gnus
, spcs
, scrp
)));
565 current
= scripts
.back();
566 } else if (t
->word() == "rscr") {
567 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
)
570 throw parseException("Unexpected RSCR");
572 removal
= shared_ptr
<script
>(new script(d
, filename
));
574 } else if (t
->word() == "iscr") {
575 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
)
576 state
= ST_INSTALLER
;
578 throw parseException("Unexpected RSCR");
581 } else if (t
->word() == "endm") {
582 emitOp(CAOS_STOP
, 0);
584 if (state
== ST_INSTALLER
|| state
== ST_BODY
|| state
== ST_REMOVAL
) {
586 state
= ST_INSTALLER
;
589 // I hate you. Die in a fire.
593 // No we will not emit c_ENDM() thankyouverymuch
595 } else if (t
->word() == "enum"
596 || t
->word() == "esee"
597 || t
->word() == "etch"
598 || t
->word() == "epas"
599 || t
->word() == "econ") {
600 int nextreloc
= current
->newRelocation();
603 emitExpr(readExpr(CI_COMMAND
));
604 emitOp(CAOS_JMP
, nextreloc
);
605 int startp
= current
->getNextIndex();
607 parseloop(ST_ENUM
, NULL
);
609 current
->fixRelocation(nextreloc
);
612 emitOp(CAOS_ENUMPOP
, startp
);
613 } else if (t
->word() == "next") {
614 if (state
!= ST_ENUM
) {
615 throw parseException("Unexpected NEXT");
619 } else if (t
->word() == "subr") {
620 // Yes, this will work in a doif or whatever. This is UB, it may
621 // be made to not compile later.
622 t
= getToken(TOK_WORD
);
623 std::string label
= t
->word();
624 emitOp(CAOS_STOP
, 0);
625 current
->affixLabel(label
);
626 } else if (t
->word() == "gsub") {
627 t
= getToken(TOK_WORD
);
628 std::string label
= t
->word();
629 emitOp(CAOS_GSUB
, current
->getLabel(label
));
631 } else if (t
->word() == "loop") {
632 int loop
= current
->getNextIndex();
634 parseloop(ST_LOOP
, (void *)&loop
);
635 } else if (t
->word() == "untl") {
636 if (state
!= ST_LOOP
)
637 throw parseException("Unexpected UNTL");
638 // TODO: zerocost logic inversion - do in c_UNTL()?
639 int loop
= *(int *)info
;
640 int out
= current
->newRelocation();
643 emitOp(CAOS_CJMP
, out
);
644 emitOp(CAOS_JMP
, loop
);
645 current
->fixRelocation(out
);
647 } else if (t
->word() == "ever") {
648 if (state
!= ST_LOOP
)
649 throw parseException("Unexpected EVER");
650 int loop
= *(int *)info
;
651 emitOp(CAOS_JMP
, loop
);
654 } else if (t
->word() == "reps") {
656 ri
.jnzreloc
= current
->newRelocation();
658 emitExpr(readExpr(CI_COMMAND
));
659 emitOp(CAOS_JMP
, ri
.jnzreloc
);
660 ri
.loopidx
= current
->getNextIndex();
661 parseloop(ST_REPS
, (void *)&ri
);
662 } else if (t
->word() == "repe") {
663 if (state
!= ST_REPS
)
664 throw parseException("Unexpected repe");
665 struct repsinfo
*ri
= (repsinfo
*)info
;
666 current
->fixRelocation(ri
->jnzreloc
);
667 emitOp(CAOS_DECJNZ
, ri
->loopidx
);
671 } else if (t
->word() == "doif") {
673 di
.donereloc
= current
->newRelocation();
674 di
.failreloc
= current
->newRelocation();
675 int okreloc
= current
->newRelocation();
679 emitOp(CAOS_CJMP
, okreloc
);
680 emitOp(CAOS_JMP
, di
.failreloc
);
681 current
->fixRelocation(okreloc
);
682 parseloop(ST_DOIF
, (void *)&di
);
684 current
->fixRelocation(di
.failreloc
);
685 current
->fixRelocation(di
.donereloc
);
687 } else if (t
->word() == "elif") {
688 if (state
!= ST_DOIF
) {
689 // XXX this is horrible
690 t
->payload
= std::string("doif");
693 struct doifinfo
*di
= (struct doifinfo
*)info
;
694 int okreloc
= current
->newRelocation();
696 emitOp(CAOS_JMP
, di
->donereloc
);
697 current
->fixRelocation(di
->failreloc
);
698 di
->failreloc
= current
->newRelocation();
701 emitOp(CAOS_CJMP
, okreloc
);
702 emitOp(CAOS_JMP
, di
->failreloc
);
703 current
->fixRelocation(okreloc
);
704 parseloop(ST_DOIF
, info
);
706 } else if (t
->word() == "else") {
707 if (state
!= ST_DOIF
)
708 throw parseException("Unexpected ELSE");
709 struct doifinfo
*di
= (struct doifinfo
*)info
;
711 throw parseException("Duplicate ELSE");
712 emitOp(CAOS_JMP
, di
->donereloc
);
713 current
->fixRelocation(di
->failreloc
);
716 } else if (t
->word() == "endi") {
717 if (state
!= ST_DOIF
) {
718 if (engine
.version
>= 3)
719 throw parseException("Unexpected ENDI");
720 // you are a horrible person if you get here
721 // damn you CL coders.
726 if (t
->word() == "dbg:") {
727 token
*t2
= tokenPeek();
728 if (t2
&& t2
->type() == TOK_WORD
&& t2
->word() == "asrt") {
730 emitOp(CAOS_CONSTINT
, 1);
732 int endreloc
= current
->newRelocation();
733 emitOp(CAOS_CJMP
, endreloc
);
734 emitCmd("cmd dbg: asrt");
735 current
->fixRelocation(endreloc
);
740 emitExpr(readExpr(CI_COMMAND
));
745 void caosScript::emitCmd(const char *name
) {
746 const cmdinfo
*ci
= d
->find_command(name
);
747 emitOp(CAOS_CMD
, d
->cmd_index(ci
));
749 emitOp(CAOS_YIELD
, ci
->evalcost
);
752 void CAOSExpression::eval(caosScript
*scr
, bool save_here
) const {
753 boost::apply_visitor(evalVisit(scr
, save_here
), value
);
756 void CAOSExpression::save(caosScript
*scr
) const {
757 boost::apply_visitor(saveVisit(scr
), value
);
760 int CAOSExpression::cost() const {
761 return boost::apply_visitor(costVisit(), value
);