| /**************************************************************************** |
| * apps/interpreters/bas/bas_program.c |
| * Program storage. |
| * |
| * Copyright (c) 1999-2014 Michael Haardt |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| * Adapted to NuttX and re-released under a 3-clause BSD license: |
| * |
| * Copyright (C) 2014 Gregory Nutt. All rights reserved. |
| * Authors: Alan Carvalho de Assis <Alan Carvalho de Assis> |
| * Gregory Nutt <gnutt@nuttx.org> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * 3. Neither the name NuttX nor the names of its contributors may be |
| * used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "bas_auto.h" |
| #include "bas_error.h" |
| #include "bas_fs.h" |
| #include "bas_program.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define _(String) String |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* 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; |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| 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); |
| } |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| 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; |
| } |
| } |
| |
| /* jump from local block must go to local block */ |
| |
| else |
| { |
| 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; |
| } |
| else |
| { |
| 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; |
| int errcode = 0; |
| |
| l = 0; |
| while (String_new(&s), (errcode = 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 (errcode) |
| { |
| 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) |
| { |
| 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; |
| } |
| } |
| |
| 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_); |
| } |
| } |