Java tutorial
/**************************************************************************************************** BASIC! is an implementation of the Basic programming language for Android devices. Copyright (C) 2010 - 2016 Paul Laughton This file is part of BASIC! for Android BASIC! is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. BASIC! is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BASIC!. If not, see <http://www.gnu.org/licenses/>. You may contact the author or current maintainers at http://rfobasic.freeforums.org Apache Commons Net Copyright 2001-2011 The Apache Software Foundation This product includes software developed by The Apache Software Foundation (http://www.apache.org/). *************************************************************************************************/ package com.rfo.basic; //Log.v(LOGTAG, "Line Buffer " + ExecutingLineBuffer); import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.Flushable; import java.io.InputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.BufferedReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.Set; import java.util.Stack; import java.util.TimeZone; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.regex.PatternSyntaxException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.nio.channels.FileChannel; import javax.crypto.Cipher; import org.apache.commons.net.ftp.*; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.apache.http.util.ByteArrayBuffer; import com.rfo.basic.Basic.TextStyle; import com.rfo.basic.GPS.GpsData; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; //import android.content.ClipData; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; //import android.content.ClipboardManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.SensorManager; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.media.SoundPool; import android.net.Uri; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.Vibrator; import android.os.SystemClock; import android.speech.RecognizerIntent; import android.speech.tts.TextToSpeech; import android.telephony.CellLocation; import android.telephony.PhoneStateListener; import android.telephony.SignalStrength; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaCellLocation; import android.telephony.gsm.GsmCellLocation; import android.text.format.Time; import android.text.ClipboardManager; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; /* Executes the Basic program. Run splits into two parts. * The UI thread part (console) and the background thread part (interpreter). * * The actual execution of the program occurs in the background * thread. UI activities must all be handled in the UI thread. * * This leads to a little complication. */ public class Run extends Activity { public static boolean isOld = false; private static final String LOGTAG = "Run"; // Log.v(LOGTAG, "Line Buffer " + ExecutingLineBuffer.line()); public static final Object LOCK = new Object(); public static boolean mWaitForLock; // semaphore for Input, TGet, and Dialogs // ********************* Message types for the Handler ********************* private static final int MESSAGE_GROUP_MASK = 0x0F00;// groups can be 0x1 through 0xF private static final int MESSAGE_DEFAULT_GROUP = 0x0000; private static final int MESSAGE_CONSOLE_GROUP = 0x0100; private static final int MESSAGE_DIALOG_GROUP = 0x0200; private static final int MESSAGE_BT_GROUP = 0x0300;// add this offset to messages from BlueTooth commands private static final int MESSAGE_HTML_GROUP = 0x0400;// add this offset to messages from HTML commands private static final int MESSAGE_DEBUG_GROUP = 0x0F00;// add this offset to messages from debug commands // message numbers < 256 are in "default" group 0 private static final int MESSAGE_CHECKPOINT = 1; // for checkpointMessage() method private static final int MESSAGE_UPDATE_CONSOLE = MESSAGE_CONSOLE_GROUP + 0; private static final int MESSAGE_CONSOLE_LINE_CHAR = MESSAGE_CONSOLE_GROUP + 2; // for CONSOLE.LINE.CHAR command private static final int MESSAGE_CLEAR_CONSOLE = MESSAGE_CONSOLE_GROUP + 3; // for CLS command private static final int MESSAGE_CONSOLE_TITLE = MESSAGE_CONSOLE_GROUP + 4; // for CONSOLE.TITLE command private static final int MESSAGE_TOAST = MESSAGE_DIALOG_GROUP + 0; // for POPUP command private static final int MESSAGE_INPUT_DIALOG = MESSAGE_DIALOG_GROUP + 1; // for INPUT command private static final int MESSAGE_ALERT_DIALOG = MESSAGE_DIALOG_GROUP + 2; // for DIALOG.* commands // ************************* ConsoleListView class ************************* // Custom ListView, just to handle the BACK key private final KeyboardManager.KeyboardChangeListener mKeyboardChangeListener = new KeyboardManager.KeyboardChangeListener() { public void kbChanged() { // required by KeyboardManager.Callbacks interface triggerInterrupt(Interrupt.KB_CHANGE_BIT); } }; public static class ConsoleListView extends ListView { private final static String LOGTAG = "ConsoleListView"; private KeyboardManager mKB; public ConsoleListView(Context context) { this(context, null); } public ConsoleListView(Context context, AttributeSet attrs) { super(context, attrs); } public ConsoleListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setKeyboardManager(KeyboardManager.KeyboardChangeListener listener) { mKB = new KeyboardManager(getContext(), this, listener); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { return (mKB != null) && mKB.onKeyPreIme(keyCode, event); // delegate to KeyboardManager } } // *************************** EventHolder class *************************** // Used to carry events between Activities. public static class EventHolder { public static final int KEY_DOWN = 1; public static final int KEY_UP = 2; public static final int BACK_KEY_PRESSED = 3; public static final int GR_BACK_KEY_PRESSED = 4; public static final int GR_KB_CHANGED = 5; public static final int GR_TOUCH = 6; public static final int GR_STATE = 7; public static final int WEB_STATE = 8; public static final int DATALINK_ADD = 9; public static final int ON_PAUSE = 1; public static final int ON_RESUME = 2; int mType; // one of the above constants int mCode; KeyEvent mEvent; String mStringData; public EventHolder(int type, int code) { mType = type; mCode = code; } public EventHolder(int type, String data) { mType = type; mStringData = data; } public EventHolder(int type, int code, KeyEvent event) { mType = type; mCode = code; mEvent = event; } } // *************************** ProgramLine class *************************** public static class ProgramLine { private String mText; // full text, after preprocessing private Command mCommand; // Command object, once known private int mKeywordLength; // skip past command keyword after Command is known private Command mSubCommand; // sub-Command object, if mCommand is a group private int mSubKeywordLength; // length of subcommand keyword(s) public Object mExtras; // command-specific extra data public ProgramLine(String text) { mText = text; mCommand = null; mKeywordLength = 0; mSubCommand = null; mSubKeywordLength = 0; mExtras = null; } // Getters. public String text() { return mText; } public int length() { return (mText == null) ? 0 : mText.length(); } public Command cmd() { return mCommand; } public Command subcmd() { return mSubCommand; } public int offset() { return mKeywordLength; } public int subOffset() { return mSubKeywordLength; } // Delegates. public boolean startsWith(String prefix) { return mText.startsWith(prefix); } public boolean startsWith(String prefix, int start) { return mText.startsWith(prefix, start); } // Replace the remembered command with a new one; look up its keyword length. public void cmd(Command command) { cmd(command, command.name.length()); } // Replace the remembered command with a new one; use the length provided. public void cmd(Command command, int length) { mCommand = command; mKeywordLength = length; mSubCommand = null; mSubKeywordLength = 0; } // Replace the remembered subcommand (one of a group) with a new one. // Add the subcommand keyword length to the remembered subcommand keyword length; // this allows getting a member of a subgroup. // Total keyword length is group command length plus sub-command(s) length. public void subcmd(Command sub) { mSubCommand = sub; mSubKeywordLength += (sub == null) ? 0 : sub.name.length(); } // Replace the remembered command with the remembered subcommand. // Add the subcommand keyword length to the current command keyword length. public void promoteSubCommand() { cmd(mSubCommand, mKeywordLength + mSubKeywordLength); } private Command searchCommands(Command[] commands, int start) { for (Command c : commands) { // loop through the command list if (mText.startsWith(c.name, start)) { return c; } } return null; // no keyword found } public Command findCommand(Command[] commands, int start) { if (mCommand == null) { Command c = searchCommands(commands, start); if (c != null) { cmd(c); } // remember the Command object } return mCommand; // return remembered command, or null if no keyword found } public Command findSubCommand(Command[] commands, int start) { if (mSubCommand == null) { Command c = searchCommands(commands, start); subcmd(c); // remember the Command object } return mSubCommand; // return remembered command, or null if no keyword found } } // class ProgramLine // ***************************** Command class ***************************** public static class Command { // Map a command keyword string to its execution function public final String name; // The command keyword public final int id; // Normally 0, may be set non-zero to indicate special case public Command(String name) { this(name, 0); } public Command(String name, int id) { this.name = name; this.id = id; } public boolean run() { return false; } // Run the command execution function public boolean run(int arg) { return false; } // Run the command execution function with an argument } // **************************** Interrupt class **************************** public static class Interrupt { public final static Integer ERROR_BIT = 0x0001; public final static Integer BACK_KEY_BIT = 0x0002; public final static Integer MENU_KEY_BIT = 0x0004; public final static Integer TIMER_BIT = 0x0008; public final static Integer KEY_BIT = 0x0010; public final static Integer GR_TOUCH_BIT = 0x0020; public final static Integer BT_READY_BIT = 0x0040; public final static Integer CONS_TOUCH_BIT = 0x0080; public final static Integer BACKGROUND_BIT = 0x0100; // background state change public final static Integer KB_CHANGE_BIT = 0x0200; public final static Integer LOW_MEM_BIT = 0x0400; public final static Integer FN_RTN_BIT = 0x1000; // for user-defined function, not an interrupt protected final int mLine; // ISR entry line public Interrupt(int line) { mLine = line; } public int line() { return mLine; } // ISR entry line } // class Interrupt // **************************** FileInfo class **************************** // Records information about an open file. Objects go in the FileTable. public enum FileType { FILE_TEXT, FILE_BYTE }; public static abstract class FileInfo { protected final int mMode; protected final boolean mIsText; protected boolean mIsEOF; protected boolean mIsClosed; protected long mPosition; protected long mMark; private int mMarkLimit; public FileInfo(int mode, boolean isText) { mMode = mode; mIsText = isText; mIsEOF = (mMode == FMW); // initially at bof if reading, eof if writing/appending mIsClosed = false; mPosition = 1; } public void mark(long pos, int limit) { mMark = pos; mMarkLimit = limit; } public void markCurrentPosition(int newLimit) { mMark = mPosition; mMarkLimit = newLimit; } public void eof(boolean isEOF) { mIsEOF = isEOF; } protected void closed() { mIsClosed = true; } public void position(long pos) { mPosition = pos; } public void incPosition() { ++mPosition; } public void incPosition(long delta) { mPosition += delta; } public int mode() { return mMode; } public boolean isText() { return mIsText; } public boolean isEOF() { return mIsEOF; } public boolean isClosed() { return mIsClosed; } public long position() { return mPosition; } public long mark() { return mMark; } public int markLimit() { return mMarkLimit; } // args: stream to flush, previous exception or null; // return: previous exception if any, else new exception if any, else null public static IOException flushStream(Flushable stream, IOException ex) { // flush a stream if (stream == null) { return ex; } try { stream.flush(); return ex; } catch (IOException e) { return (ex == null) ? e : ex; } } // args: stream to close, previous exception or null // return: previous exception if any, else new exception if any, else null public static IOException closeStream(Closeable stream, IOException ex) { // close a stream if (stream == null) { return ex; } try { stream.close(); return ex; } catch (IOException e) { return (ex == null) ? e : ex; } } protected IOException flush(IOException ex) { // default implementation is a no-op return ex; // can invoke on read types } protected abstract IOException close(IOException ex); } // class FileInfo public static class TextWriterInfo extends FileInfo { public FileWriter mTextWriter; public TextWriterInfo(int mode) { super(mode, true); } // true means it is text public IOException flush(IOException ex) { return flushStream(mTextWriter, ex); } public IOException close(IOException ex) { IOException e = closeStream(mTextWriter, ex); mTextWriter = null; closed(); return e; } } public static class TextReaderInfo extends FileInfo { public BufferedReader mTextReader; public TextReaderInfo(int mode) { super(mode, true); } // true means it is text public IOException close(IOException ex) { IOException e = closeStream(mTextReader, ex); mTextReader = null; closed(); return e; } } public static class ByteWriterInfo extends FileInfo { public FileOutputStream mByteWriter; // stream for almost all operations // Both of these are built from the FileOutputStream. // Only one can be used at a time. Once one is used, we close the stream // to keep the other from being used in parallel. private DataOutputStream mDOStream; private FileChannel mChannel; public ByteWriterInfo(int mode) { super(mode, false); } // false means it is byte, not text public DataOutputStream getDOS() { if (mDOStream == null) { mDOStream = new DataOutputStream(mByteWriter); } return mDOStream; } public void truncateFile(long length) throws IOException { IOException ex = null; if ((mByteWriter == null) || (mDOStream != null)) { ex = new IOException("Error getting FileChannel"); } else { long pnow = position(); if (length < 0) { length = 0; } if (length < (pnow - 1)) { mChannel = mByteWriter.getChannel(); try { mChannel.truncate(length); // truncate the file position(length + 1); } catch (IOException e) { ex = e; } eof(true); } } ex = close(flushChannel(ex)); if (ex != null) { throw ex; } } // arg: previous exception or null; // return: previous exception if any, else new exception if any, else null private IOException flushChannel(IOException ex) { // flush the channel FileChannel chnl = mChannel; mChannel = null; if (chnl == null) { return ex; } try { chnl.force(false); return ex; } catch (IOException e) { return (ex == null) ? e : ex; } } // arg: previous exception or null; // return: previous exception if any, else new exception if any, else null public IOException flush(IOException ex) { // flush the channel return (mChannel != null) ? flushChannel(ex) : flushStream(((mDOStream != null) ? mDOStream : mByteWriter), ex); } // args: stream to close, previous exception or null // return: previous exception if any, else new exception if any, else null public IOException close(IOException ex) { Closeable stream = (mChannel != null) ? mChannel : ((mDOStream != null) ? mDOStream : mByteWriter); mChannel = null; mDOStream = null; mByteWriter = null; closed(); return closeStream(stream, ex); } } public static class ByteReaderInfo extends FileInfo { public BufferedInputStream mByteReader; public ByteReaderInfo(int mode) { super(mode, false); } // false means it is byte, not text private DataInputStream mDIStream; public DataInputStream getDIS() { if (mDIStream == null) { mDIStream = new DataInputStream(mByteReader); } return mDIStream; } public IOException close(IOException ex) { IOException e = closeStream(mByteReader, ex); mByteReader = null; closed(); return e; } } // ***************** The constants for the Basic Keywords ***************** // First, an alphabetical list of all of the top-level keywords. // Every constant in this list must appear in both BasicKeyWords[] and BASIC_cmd[]. private static final String BKW_APP_GROUP = "app."; private static final String BKW_ARRAY_GROUP = "array."; private static final String BKW_AUDIO_GROUP = "audio."; private static final String BKW_BACK_RESUME = "back.resume"; private static final String BKW_BACKGROUND_RESUME = "background.resume"; private static final String BKW_BROWSE = "browse"; private static final String BKW_BT_GROUP = "bt."; private static final String BKW_BUNDLE_GROUP = "bundle."; private static final String BKW_BYTE_GROUP = "byte."; private static final String BKW_CALL = "call"; private static final String BKW_CLIPBOARD_GET = "clipboard.get"; private static final String BKW_CLIPBOARD_PUT = "clipboard.put"; private static final String BKW_CLS = "cls"; private static final String BKW_CONSOLE_GROUP = "console."; private static final String BKW_CONSOLETOUCH_RESUME = "consoletouch.resume"; private static final String BKW_D_U_BREAK = "d_u.break"; private static final String BKW_D_U_CONTINUE = "d_u.continue"; private static final String BKW_DEBUG_GROUP = "debug."; private static final String BKW_DECRYPT = "decrypt"; private static final String BKW_DEVICE = "device"; private static final String BKW_DIALOG_GROUP = "dialog."; private static final String BKW_DIM = "dim"; private static final String BKW_DIR = "dir"; // same as "file.dir" private static final String BKW_DO = "do"; private static final String BKW_ECHO_GROUP = "echo."; private static final String BKW_ELSE = "else"; private static final String BKW_ELSEIF = "elseif"; private static final String BKW_EMAIL_SEND = "email.send"; private static final String BKW_EMPTY_PROGRAM = "@@@"; private static final String BKW_ENCRYPT = "encrypt"; private static final String BKW_END = "end"; private static final String BKW_ENDIF = "endif"; private static final String BKW_EXIT = "exit"; private static final String BKW_F_N_BREAK = "f_n.break"; private static final String BKW_F_N_CONTINUE = "f_n.continue"; private static final String BKW_FILE_GROUP = "file."; private static final String BKW_FN_GROUP = "fn."; private static final String BKW_FONT_GROUP = "font."; private static final String BKW_FOR = "for"; private static final String BKW_FTP_GROUP = "ftp."; private static final String BKW_GOSUB = "gosub"; private static final String BKW_GOTO = "goto"; private static final String BKW_GPS_GROUP = "gps."; private static final String BKW_GR_GROUP = "gr."; private static final String BKW_GRABFILE = "grabfile"; private static final String BKW_GRABURL = "graburl"; private static final String BKW_HEADSET = "headset"; private static final String BKW_HOME = "home"; private static final String BKW_HTML_GROUP = "html."; private static final String BKW_HTTP_POST = "http.post"; private static final String BKW_IF = "if"; private static final String BKW_INCLUDE = "include"; private static final String BKW_INKEY = "inkey$"; private static final String BKW_INPUT = "input"; private static final String BKW_JOIN = "join"; private static final String BKW_JOIN_ALL = "join.all"; private static final String BKW_KB_GROUP = "kb."; private static final String BKW_KEY_RESUME = "key.resume"; private static final String BKW_LET = "let"; private static final String BKW_LIST_GROUP = "list."; private static final String BKW_LOWMEM_RESUME = "lowmemory.resume"; private static final String BKW_MENUKEY_RESUME = "menukey.resume"; private static final String BKW_MKDIR = "mkdir"; // same as "file.mkdir" private static final String BKW_MYPHONENUMBER = "myphonenumber"; private static final String BKW_NEXT = "next"; private static final String BKW_NOTIFY = "notify"; private static final String BKW_ONBACKGROUND = "onbackground:"; private static final String BKW_ONBACKKEY = "onbackkey:"; private static final String BKW_ONBTREADREADY = "onbtreadready:"; private static final String BKW_ONCONSOLETOUCH = "onconsoletouch:"; private static final String BKW_ONERROR = "onerror:"; private static final String BKW_ONGRTOUCH = "ongrtouch:"; private static final String BKW_ONKBCHANGE = "onkbchange:"; private static final String BKW_ONKEYPRESS = "onkeypress:"; private static final String BKW_ONLOWMEM = "onlowmemory:"; private static final String BKW_ONMENUKEY = "onmenukey:"; private static final String BKW_ONTIMER = "ontimer:"; private static final String BKW_PAUSE = "pause"; private static final String BKW_PHONE_GROUP = "phone."; private static final String BKW_POPUP = "popup"; private static final String BKW_PREDEC = "--"; // omit from BasicKeyWords[] private static final String BKW_PREINC = "++"; // omit from BasicKeyWords[] private static final String BKW_PRINT = "print"; private static final String BKW_PRINT_SHORTCUT = "?"; private static final String BKW_READ_GROUP = "read."; private static final String BKW_REM = "rem"; private static final String BKW_RENAME = "rename"; // same as "file.rename" private static final String BKW_REPEAT = "repeat"; private static final String BKW_RETURN = "return"; private static final String BKW_RINGER_GROUP = "ringer."; private static final String BKW_RUN = "run"; private static final String BKW_SCREEN_GROUP = "screen."; private static final String BKW_SELECT = "select"; private static final String BKW_SENSORS_GROUP = "sensors."; private static final String BKW_SMS_GROUP = "sms."; private static final String BKW_SOCKET_GROUP = "socket."; private static final String BKW_SOUNDPOOL_GROUP = "soundpool."; private static final String BKW_SPLIT = "split"; private static final String BKW_SPLIT_ALL = "split.all"; // split.all new/2013-07-25 gt private static final String BKW_SQL_GROUP = "sql."; private static final String BKW_STACK_GROUP = "stack."; private static final String BKW_STT_LISTEN = "stt.listen"; private static final String BKW_STT_RESULTS = "stt.results"; private static final String BKW_SU_GROUP = "su."; private static final String BKW_SW_GROUP = "sw."; private static final String BKW_SWAP = "swap"; private static final String BKW_SYSTEM_GROUP = "system."; private static final String BKW_TEXT_GROUP = "text."; private static final String BKW_TGET = "tget"; private static final String BKW_TIME = "time"; private static final String BKW_TIMER_GROUP = "timer."; private static final String BKW_TIMEZONE_GROUP = "timezone."; private static final String BKW_TONE = "tone"; private static final String BKW_TTS_GROUP = "tts."; private static final String BKW_UNDIM = "undim"; private static final String BKW_UNTIL = "until"; private static final String BKW_VIBRATE = "vibrate"; private static final String BKW_VOLKEYS_GROUP = "volkeys."; private static final String BKW_W_R_BREAK = "w_r.break"; private static final String BKW_W_R_CONTINUE = "w_r.continue"; private static final String BKW_WAKELOCK = "wakelock"; private static final String BKW_WHILE = "while"; private static final String BKW_WIFI_INFO = "wifi.info"; private static final String BKW_WIFILOCK = "wifilock"; // Some generic words used by multiple commands private static final String BKW_ON = "on"; private static final String BKW_OFF = "off"; private static final boolean ON = true; // true: enable something, turn it on private static final boolean OFF = false; // false: disable something, turn it off // This array lists all of the top-level keywords so Format can find them. // The order of this list determines the order Format uses for its linear search. // BKW_PREDEC and BKW_PREINC are omitted as they look like regular expressions (!). // BKW_PRINT_SHORTCUT, too. // This array is also used by DEBUG.COMMANDS. public static final String BasicKeyWords[] = { BKW_LET, BKW_PRINT, BKW_IF, BKW_ELSEIF, BKW_ELSE, BKW_ENDIF, BKW_FOR, BKW_NEXT, BKW_WHILE, BKW_REPEAT, BKW_DO, BKW_UNTIL, BKW_F_N_BREAK, BKW_W_R_BREAK, BKW_D_U_BREAK, BKW_F_N_CONTINUE, BKW_W_R_CONTINUE, BKW_D_U_CONTINUE, BKW_SW_GROUP, BKW_FN_GROUP, BKW_CALL, BKW_GOTO, BKW_GOSUB, BKW_RETURN, BKW_GR_GROUP, BKW_DIM, BKW_UNDIM, BKW_ARRAY_GROUP, BKW_BUNDLE_GROUP, BKW_LIST_GROUP, BKW_STACK_GROUP, // BKW_PREDEC, BKW_PREINC, // Format can't handle these and doesn't need them BKW_INKEY, BKW_INPUT, BKW_DIALOG_GROUP, BKW_SELECT, BKW_TGET, BKW_FILE_GROUP, BKW_TEXT_GROUP, BKW_BYTE_GROUP, BKW_READ_GROUP, BKW_DIR, BKW_MKDIR, BKW_RENAME, BKW_GRABFILE, BKW_GRABURL, BKW_BROWSE, BKW_BT_GROUP, BKW_FTP_GROUP, BKW_HTML_GROUP, BKW_HTTP_POST, BKW_SOCKET_GROUP, BKW_SQL_GROUP, BKW_GPS_GROUP, BKW_POPUP, BKW_SENSORS_GROUP, BKW_AUDIO_GROUP, BKW_SOUNDPOOL_GROUP, BKW_RINGER_GROUP, BKW_TONE, BKW_CLIPBOARD_GET, BKW_CLIPBOARD_PUT, BKW_ENCRYPT, BKW_DECRYPT, BKW_SWAP, BKW_SPLIT_ALL, BKW_SPLIT, BKW_JOIN_ALL, BKW_JOIN, BKW_CLS, BKW_FONT_GROUP, BKW_CONSOLE_GROUP, BKW_DEBUG_GROUP, BKW_ECHO_GROUP, BKW_KB_GROUP, BKW_NOTIFY, BKW_RUN, // BKW_EMPTY_PROGRAM, // Format does not need EMPTY_PROGRAM BKW_SU_GROUP, BKW_SYSTEM_GROUP, BKW_STT_LISTEN, BKW_STT_RESULTS, BKW_TTS_GROUP, BKW_TIMER_GROUP, BKW_TIMEZONE_GROUP, BKW_TIME, BKW_VIBRATE, BKW_WAKELOCK, BKW_WIFILOCK, BKW_END, BKW_EXIT, BKW_HOME, BKW_INCLUDE, BKW_PAUSE, BKW_REM, BKW_DEVICE, BKW_SCREEN_GROUP, BKW_WIFI_INFO, BKW_HEADSET, BKW_MYPHONENUMBER, BKW_EMAIL_SEND, BKW_PHONE_GROUP, BKW_SMS_GROUP, BKW_VOLKEYS_GROUP, BKW_APP_GROUP, BKW_BACK_RESUME, BKW_BACKGROUND_RESUME, BKW_CONSOLETOUCH_RESUME, BKW_KEY_RESUME, BKW_LOWMEM_RESUME, BKW_MENUKEY_RESUME, BKW_ONERROR, // Interrupt labels are listed for formatter, but have no Command objects BKW_ONBACKKEY, BKW_ONBACKGROUND, BKW_ONBTREADREADY, BKW_ONCONSOLETOUCH, BKW_ONGRTOUCH, BKW_ONKEYPRESS, BKW_ONKBCHANGE, BKW_ONLOWMEM, BKW_ONMENUKEY, BKW_ONTIMER, }; private static HashMap<String, String[]> keywordLists = null; // For Format: map associates a keyword group // with the prefix common to the group. public static HashMap<String, String[]> getKeywordLists() { if (keywordLists == null) { keywordLists = new HashMap<String, String[]>(); // If you add a new keyword group, add it to this list! keywordLists.put(BKW_APP_GROUP, app_KW); keywordLists.put(BKW_ARRAY_GROUP, Array_KW); keywordLists.put(BKW_AUDIO_GROUP, Audio_KW); keywordLists.put(BKW_BT_GROUP, bt_KW); keywordLists.put(BKW_BUNDLE_GROUP, Bundle_KW); keywordLists.put(BKW_BYTE_GROUP, byte_KW); keywordLists.put(BKW_CONSOLE_GROUP, Console_KW); keywordLists.put(BKW_DIALOG_GROUP, Dialog_KW); keywordLists.put(BKW_DEBUG_GROUP, debug_KW); keywordLists.put(BKW_ECHO_GROUP, echo_KW); keywordLists.put(BKW_FILE_GROUP, file_KW); keywordLists.put(BKW_FN_GROUP, fn_KW); keywordLists.put(BKW_FONT_GROUP, font_KW); keywordLists.put(BKW_FTP_GROUP, ftp_KW); keywordLists.put(BKW_GPS_GROUP, GPS_KW); keywordLists.put(BKW_GR_GROUP, GR_KW); keywordLists.put(BKW_HTML_GROUP, html_KW); keywordLists.put(BKW_KB_GROUP, KB_KW); keywordLists.put(BKW_LIST_GROUP, List_KW); keywordLists.put(BKW_PHONE_GROUP, phone_KW); keywordLists.put(BKW_READ_GROUP, read_KW); keywordLists.put(BKW_RINGER_GROUP, ringer_KW); keywordLists.put(BKW_SCREEN_GROUP, screen_KW); keywordLists.put(BKW_SENSORS_GROUP, Sensors_KW); keywordLists.put(BKW_SMS_GROUP, SMS_KW); keywordLists.put(BKW_SOCKET_GROUP, Socket_KW); keywordLists.put(BKW_SOUNDPOOL_GROUP, sp_KW); keywordLists.put(BKW_SQL_GROUP, SQL_KW); keywordLists.put(BKW_STACK_GROUP, Stack_KW); keywordLists.put(BKW_SU_GROUP, su_KW); keywordLists.put(BKW_SW_GROUP, sw_KW); keywordLists.put(BKW_SYSTEM_GROUP, System_KW); keywordLists.put(BKW_TEXT_GROUP, text_KW); keywordLists.put(BKW_TIMER_GROUP, Timer_KW); keywordLists.put(BKW_TIMEZONE_GROUP, TimeZone_KW); keywordLists.put(BKW_TTS_GROUP, tts_KW); keywordLists.put(BKW_VOLKEYS_GROUP, VolKeys_KW); } return keywordLists; } // **************** The variables for the math function names ************************ private static final String MF_ABS = "abs("; private static final String MF_ACOS = "acos("; private static final String MF_ASCII = "ascii("; private static final String MF_ASIN = "asin("; private static final String MF_ATAN = "atan("; private static final String MF_ATAN2 = "atan2("; private static final String MF_BACKGROUND = "background("; private static final String MF_BAND = "band("; private static final String MF_BIN = "bin("; private static final String MF_BNOT = "bnot("; private static final String MF_BOR = "bor("; private static final String MF_BXOR = "bxor("; private static final String MF_CBRT = "cbrt("; private static final String MF_CEIL = "ceil("; private static final String MF_CLOCK = "clock("; private static final String MF_COS = "cos("; private static final String MF_COSH = "cosh("; private static final String MF_ENDS_WITH = "ends_with("; private static final String MF_EXP = "exp("; private static final String MF_FLOOR = "floor("; private static final String MF_FRAC = "frac("; // new/2014-03-16 gt private static final String MF_GR_COLLISION = "gr_collision("; private static final String MF_HEX = "hex("; private static final String MF_HYPOT = "hypot("; private static final String MF_INT = "int("; // new/2014-03-16 gt private static final String MF_IS_IN = "is_in("; private static final String MF_IS_NUMBER = "is_number("; private static final String MF_LEN = "len("; private static final String MF_LOG = "log("; private static final String MF_LOG10 = "log10("; private static final String MF_MAX = "max("; // new/2013-07-29 gt private static final String MF_MIN = "min("; // new/2013-07-29 gt private static final String MF_MOD = "mod("; private static final String MF_OCT = "oct("; private static final String MF_PI = "pi("; // new/2013-07-29 gt private static final String MF_POW = "pow("; private static final String MF_RANDOMIZE = "randomize("; private static final String MF_RND = "rnd("; private static final String MF_ROUND = "round("; private static final String MF_SGN = "sgn("; // new/2014-03-16 gt private static final String MF_SHIFT = "shift("; private static final String MF_SIN = "sin("; private static final String MF_SINH = "sinh("; private static final String MF_SQR = "sqr("; private static final String MF_STARTS_WITH = "starts_with("; private static final String MF_TAN = "tan("; private static final String MF_TIME = "time("; private static final String MF_TODEGREES = "todegrees("; private static final String MF_TORADIANS = "toradians("; private static final String MF_UCODE = "ucode("; private static final String MF_VAL = "val("; public static final String MathFunctions[] = { // Command list for Format MF_ABS, MF_ACOS, MF_ASCII, MF_ASIN, MF_ATAN, MF_ATAN2, MF_BACKGROUND, MF_BAND, MF_BIN, MF_BNOT, MF_BOR, MF_BXOR, MF_CBRT, MF_CEIL, MF_CLOCK, MF_COS, MF_COSH, MF_ENDS_WITH, MF_EXP, MF_FLOOR, MF_FRAC, MF_GR_COLLISION, MF_HEX, MF_HYPOT, MF_INT, MF_IS_IN, MF_IS_NUMBER, MF_LEN, MF_LOG, MF_LOG10, MF_MAX, MF_MIN, MF_MOD, MF_OCT, MF_PI, MF_POW, MF_RANDOMIZE, MF_RND, MF_ROUND, MF_SGN, MF_SHIFT, MF_SIN, MF_SINH, MF_SQR, MF_STARTS_WITH, MF_TAN, MF_TIME, MF_TODEGREES, MF_TORADIANS, MF_UCODE, MF_VAL, }; private static final HashMap<String, Integer> mRoundingModeTable = new HashMap<String, Integer>(7) { private static final long serialVersionUID = 101L; { put("hd", BigDecimal.ROUND_HALF_DOWN); put("he", BigDecimal.ROUND_HALF_EVEN); put("hu", BigDecimal.ROUND_HALF_UP); put("d", BigDecimal.ROUND_DOWN); put("u", BigDecimal.ROUND_UP); put("f", BigDecimal.ROUND_FLOOR); put("c", BigDecimal.ROUND_CEILING); } }; //*********************** The variables for the Operators ************************ private static final String OP_INC = "++"; private static final String OP_DEC = "--"; public static final String OperatorString[] = { "<=", "<>", ">=", ">", "<", "=", "^", "*", "+", "-", "/", "!", "|", "&", "(", ")", "=", "+", "-", " ", "\n" }; private static final int LE = 0; // Enumerated names of operators private static final int NE = 1; private static final int GE = 2; private static final int GT = 3; private static final int LT = 4; private static final int LEQ = 5; private static final int EXP = 6; private static final int MUL = 7; private static final int PLUS = 8; private static final int MINUS = 9; private static final int DIV = 10; private static final int NOT = 11; private static final int OR = 12; private static final int AND = 13; private static final int LPRN = 14; private static final int RPRN = 15; private static final int ASSIGN = 16; private static final int UPLUS = 17; private static final int UMINUS = 18; private static final int SOE = 19; private static final int EOL = 20; private static final int FLPRN = 21; private static final int GoesOnPrecedence[] = { // Precedence for going onto stack 8, 8, 8, 8, 8, 8, 12, 10, 9, 9, 10, 7, 5, 6, 15, 2, 15, 13, 13, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 }; private static final int ComesOffPrecedence[] = { // Precedence for coming off stack 8, 8, 8, 8, 8, 8, 12, 10, 9, 9, 10, 7, 5, 6, 2, 14, 1, 13, 13, 0, 0, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 }; private int OperatorValue = 0; // Will hold enumerated operator name value //********************** The variables for the string functions ******************* private static final String SF_BIN = "bin$("; private static final String SF_CHR = "chr$("; private static final String SF_DECODE = "decode$("; private static final String SF_ENCODE = "encode$("; private static final String SF_FORMAT = "format$("; private static final String SF_FORMAT_USING = "format_using$("; private static final String SF_GETERROR = "geterror$("; private static final String SF_HEX = "hex$("; private static final String SF_INT = "int$("; private static final String SF_LEFT = "left$("; private static final String SF_LOWER = "lower$("; private static final String SF_LTRIM = "ltrim$("; private static final String SF_MID = "mid$("; private static final String SF_OCT = "oct$("; private static final String SF_REPLACE = "replace$("; private static final String SF_RIGHT = "right$("; private static final String SF_RTRIM = "rtrim$("; private static final String SF_STR = "str$("; private static final String SF_TRIM = "trim$("; private static final String SF_UPPER = "upper$("; private static final String SF_USING = "using$("; private static final String SF_VERSION = "version$("; private static final String SF_WORD = "word$("; public static final String StringFunctions[] = { // Command list for Format SF_BIN, SF_CHR, SF_DECODE, SF_ENCODE, SF_FORMAT, SF_FORMAT_USING, SF_GETERROR, SF_HEX, SF_INT, SF_LEFT, SF_LOWER, SF_LTRIM, SF_MID, SF_OCT, SF_REPLACE, SF_RIGHT, SF_RTRIM, SF_STR, SF_TRIM, SF_UPPER, SF_USING, SF_WORD, SF_VERSION, }; private static final int TLEFT = 1; // control bits for SF_TRIM private static final int TRIGHT = 2; private static final boolean ENCODE = true; // control bits for ENCODE/DECODE private static final boolean DECODE = false; private static final int ENCODING_ENCRYPT = 1; // encode/decode types private static final int ENCODING_BASE64 = 2; private static final int ENCODING_URL = 3; private static final int ENCODING_OTHER = 4; // simple string conversion // *************************** Various execution control variables **************************** public static final int BASIC_GENERAL_INTENT = 255; private Intent mConsoleIntent; // keep a reference to the Intent that started Run public static boolean Exit = false; // Exits program and signals caller to exit, too private boolean Stop = false; // Stops program from running private boolean mInterpreterRunning; private boolean RunPaused = false; // events from other Activities public static final ArrayList<EventHolder> mEventList = new ArrayList<EventHolder>(); private Interrupt mOnErrorInt = null; // OnError gets a named interrupt. The others are anonymous. private boolean ConsoleLongTouch = false; private int TouchedConsoleLine = 0; // first valid line number is 1 private int interruptResume = -1; /* TODO: move these to Interpreter */ // Constants for use in the IfElseStack private static final Integer IEskip1 = 1; // Skip statements until ELSE, ELSEIF or ENDIF private static final Integer IEskip2 = 2; // Skip to until ENDIF private static final Integer IEexec = 3; // Execute to ELSE, ELSEIF or ENDIF private static final Integer IEinterrupt = 4; // The output screen text lines final private static int MIN_CONSOLE_LINES = 100; // starting capacity of mConsole private final ArrayList<String> mOutput = new ArrayList<String>(MIN_CONSOLE_LINES); private final ArrayList<String> mConsoleBuffer = new ArrayList<String>(); // carries output from mInterpreter to UI public Basic.ColoredTextAdapter mConsole; // The output screen array adapter private ConsoleListView lv; // The output screen list view private Interpreter mInterpreter; // the program runner, runs in a separate Thread private boolean SyntaxError = false; // Set true when Syntax Error message has been output private boolean mMessagePending; // If true, may be messages pending // debugger dialog and ui thread vars private static final int MESSAGE_DEBUG_DIALOG = MESSAGE_DEBUG_GROUP + 1; private static final int MESSAGE_DEBUG_SWAP = MESSAGE_DEBUG_GROUP + 2; private static final int MESSAGE_DEBUG_SELECT = MESSAGE_DEBUG_GROUP + 3; private Object DB_LOCK = new Object(); private boolean mWaitForDebugLock = false; private boolean WaitForDebugResume = false; private boolean DebuggerStep = false; private boolean DebuggerHalt = false; private boolean WaitForSwap = false; private boolean WaitForSelect = false; private boolean dbSwap = false; private boolean dbSelect = false; private AlertDialog dbDialog; private AlertDialog dbSwapDialog; private AlertDialog dbSelectDialog; private boolean dbDialogScalars; private boolean dbDialogArray; private boolean dbDialogList; private boolean dbDialogStack; private boolean dbDialogBundle; private boolean dbDialogWatch; private boolean dbDialogProgram; private boolean dbDialogConsole; private String dbConsoleHistory; private String dbConsoleExecute; private int dbConsoleELBI; private ArrayList<Var> WatchedVars; private Var WatchedArray; private int WatchedList; private int WatchedStack; private int WatchedBundle; // end debugger ui vars private HashMap<String, Integer> Labels; // A list of all labels and associated line numbers private ClipboardManager clipboard; public static boolean Notified; // ******************** Command Keywords for User-defined Functions ******************** private static final String BKW_FN_DEF = "def"; private static final String BKW_FN_RTN = "rtn"; private static final String fn_KW[] = { // Command list for Format BKW_FN_DEF, BKW_FN_RTN, BKW_END }; // ********************************** SWITCH keywords ********************************** private static final String BKW_SW_BEGIN = "begin"; private static final String BKW_SW_CASE = "case"; private static final String BKW_SW_BREAK = "break"; private static final String BKW_SW_DEFAULT = "default"; private static final String sw_KW[] = { // Command list for Format BKW_SW_BEGIN, BKW_SW_CASE, BKW_SW_BREAK, BKW_SW_DEFAULT, BKW_END }; // ********************************* WAKELOCK keywords ********************************* private PowerManager.WakeLock theWakeLock; private static final int partial = 1; private static final int dim = 2; private static final int bright = 3; private static final int full = 4; private static final int release = 5; // ********************************* WIFILOCK keywords ********************************* private WifiManager.WifiLock theWifiLock; private static final int wifi_mode_scan = 1; private static final int wifi_mode_full = 2; private static final int wifi_mode_high = 3; private static final int wifi_release = 4; // ********************* File I/O operation keywords and variables ********************* // for both TEXT and BYTE private static final String BKW_OPEN = "open"; private static final String BKW_CLOSE = "close"; private static final String BKW_EOF = "eof"; private static final String BKW_POSITION_GET = "position.get"; private static final String BKW_POSITION_SET = "position.set"; private static final String BKW_POSITION_MARK = "position.mark"; // for FILE private static final String BKW_FILE_DELETE = "delete"; private static final String BKW_FILE_SIZE = "size"; private static final String BKW_FILE_DIR = "dir"; private static final String BKW_FILE_MKDIR = "mkdir"; private static final String BKW_FILE_RENAME = "rename"; private static final String BKW_FILE_ROOT = "root"; private static final String BKW_FILE_EXISTS = "exists"; private static final String BKW_FILE_TYPE = "type"; private static final String file_KW[] = { // Command list for Format BKW_FILE_DELETE, BKW_FILE_SIZE, BKW_FILE_DIR, BKW_FILE_MKDIR, BKW_FILE_RENAME, BKW_FILE_ROOT, BKW_FILE_EXISTS, BKW_FILE_TYPE }; private static final int FMR = 0; // File Mode Read private static final int FMW = 1; // File Mode Write // ********************************* TEXT I/O keywords ********************************* private static final String BKW_TEXT_READLN = "readln"; private static final String BKW_TEXT_WRITELN = "writeln"; private static final String BKW_TEXT_INPUT = "input"; private static final String text_KW[] = { // Command list for Format BKW_OPEN, BKW_CLOSE, BKW_EOF, BKW_TEXT_READLN, BKW_TEXT_WRITELN, BKW_TEXT_INPUT, BKW_POSITION_GET, BKW_POSITION_SET, BKW_POSITION_MARK, }; // ********************************* BYTE I/O keywords ********************************* private static final String BKW_BYTE_READ_BYTE = "read.byte"; private static final String BKW_BYTE_WRITE_BYTE = "write.byte"; private static final String BKW_BYTE_READ_BUFFER = "read.buffer"; private static final String BKW_BYTE_WRITE_BUFFER = "write.buffer"; private static final String BKW_BYTE_READ_NUMBER = "read.number"; private static final String BKW_BYTE_WRITE_NUMBER = "write.number"; private static final String BKW_BYTE_COPY = "copy"; private static final String BKW_BYTE_TRUNCATE = "truncate"; private static final String byte_KW[] = { // Command list for Format BKW_OPEN, BKW_CLOSE, BKW_EOF, BKW_BYTE_READ_BYTE, BKW_BYTE_WRITE_BYTE, BKW_BYTE_READ_BUFFER, BKW_BYTE_WRITE_BUFFER, BKW_BYTE_READ_NUMBER, BKW_BYTE_WRITE_NUMBER, BKW_BYTE_COPY, BKW_BYTE_TRUNCATE, BKW_POSITION_GET, BKW_POSITION_SET, BKW_POSITION_MARK, }; // *********************************** READ keywords *********************************** private static final String BKW_READ_DATA = "data"; private static final String BKW_READ_NEXT = "next"; private static final String BKW_READ_FROM = "from"; private static final String read_KW[] = { // Command list for Format BKW_READ_DATA, BKW_READ_NEXT, BKW_READ_FROM }; // ********************************** SCREEN keywords ********************************** private static final String BKW_SCREEN_ROTATION = "rotation"; private static final String BKW_SCREEN_SIZE = "size"; private static final String screen_KW[] = { // Command list for Format BKW_SCREEN_ROTATION, BKW_SCREEN_SIZE }; // *********************************** FONT keywords *********************************** private static final String BKW_FONT_LOAD = "load"; private static final String BKW_FONT_DELETE = "delete"; private static final String BKW_FONT_CLEAR = "clear"; private static final String font_KW[] = { // Font command list for Format BKW_FONT_LOAD, BKW_FONT_DELETE, BKW_FONT_CLEAR }; // ********************************* CONSOLE keywords ********************************** private static final String BKW_CONSOLE_FRONT = "front"; private static final String BKW_CONSOLE_SAVE = "save"; private static final String BKW_CONSOLE_TITLE = "title"; private static final String BKW_CONSOLE_LINE_COUNT = "line.count"; private static final String BKW_CONSOLE_LINE_TEXT = "line.text"; private static final String BKW_CONSOLE_LINE_TOUCHED = "line.touched"; private static final String BKW_CONSOLE_LINE_NEW = "line.new"; private static final String BKW_CONSOLE_LINE_CHAR = "line.char"; private static final String Console_KW[] = { // Console command list for Format BKW_CONSOLE_FRONT, BKW_CONSOLE_SAVE, BKW_CONSOLE_TITLE, BKW_CONSOLE_LINE_COUNT, BKW_CONSOLE_LINE_TEXT, BKW_CONSOLE_LINE_TOUCHED, BKW_CONSOLE_LINE_NEW, BKW_CONSOLE_LINE_CHAR }; // ************************************ KB keywords ************************************ private static final String BKW_KB_HIDE = "hide"; private static final String BKW_KB_RESUME = "resume"; private static final String BKW_KB_SHOW = "show"; private static final String BKW_KB_SHOWING = "showing"; private static final String BKW_KB_TOGGLE = "toggle"; private static final String KB_KW[] = { // KB command list for Format BKW_KB_HIDE, BKW_KB_RESUME, BKW_KB_SHOWING, BKW_KB_SHOW, BKW_KB_TOGGLE }; // ****************************** INPUT command variables ****************************** private boolean mInputCancelled = false; // Signal between Interpreter Thread and UI Thread // private boolean mInputDismissed = false; // This will be used only if we dismiss the dialog in onPause // *********************** DIALOG command keywords and variables *********************** private static final String BKW_DIALOG_MESSAGE = "message"; private static final String BKW_DIALOG_SELECT = "select"; private static final String Dialog_KW[] = { // Dialog command list for Format BKW_DIALOG_MESSAGE, BKW_DIALOG_SELECT }; private int mAlertItemID = 0; // index of button or list item // ***************************** SELECT Command variables ****************************** public static int SelectedItem; // The index of the selected item public static boolean SelectLongClick; // True if long click // *********************************** SQL keywords ************************************ private static final String BKW_SQL_OPEN = "open"; private static final String BKW_SQL_CLOSE = "close"; private static final String BKW_SQL_INSERT = "insert"; private static final String BKW_SQL_QUERY_LENGTH = "query.length"; private static final String BKW_SQL_QUERY_POSITION = "query.position"; private static final String BKW_SQL_QUERY = "query"; private static final String BKW_SQL_NEXT = "next"; private static final String BKW_SQL_DELETE = "delete"; private static final String BKW_SQL_UPDATE = "update"; private static final String BKW_SQL_EXEC = "exec"; private static final String BKW_SQL_RAW_QUERY = "raw_query"; private static final String BKW_SQL_DROP_TABLE = "drop_table"; private static final String BKW_SQL_NEW_TABLE = "new_table"; private static final String SQL_KW[] = { // SQL command list for Format BKW_SQL_OPEN, BKW_SQL_CLOSE, BKW_SQL_INSERT, BKW_SQL_QUERY_LENGTH, BKW_SQL_QUERY_POSITION, BKW_SQL_QUERY, BKW_SQL_NEXT, BKW_SQL_DELETE, BKW_SQL_UPDATE, BKW_SQL_EXEC, BKW_SQL_RAW_QUERY, BKW_SQL_DROP_TABLE, BKW_SQL_NEW_TABLE }; // ********************************* INKEY$ variables ********************************** public static final String Numbers = "0123456789"; // translations for key codes public static final String Chars = "abcdefghijklmnopqrstuvwxyz"; static ArrayList<String> InChar; // *************************** TEXT.INPUT command variables **************************** public static String TextInputString = ""; // ******************* Graphics (GR) command keywords and variables ******************** private boolean GRopen = false; private boolean GRFront; public static ArrayList<GR.BDraw> DisplayList; public static ArrayList<Integer> RealDisplayList; public static ArrayList<Paint> PaintList; public static ArrayList<Bitmap> BitmapList; public static double TouchX[] = new double[2]; public static double TouchY[] = new double[2]; public static boolean NewTouch[] = new boolean[2]; // Graphics command keywords private static final String BKW_GR_ARC = "arc"; private static final String BKW_GR_BOUNDED_TOUCH = "bounded.touch"; private static final String BKW_GR_BOUNDED_TOUCH2 = "bounded.touch2"; private static final String BKW_GR_BRIGHTNESS = "brightness"; private static final String BKW_GR_CIRCLE = "circle"; private static final String BKW_GR_CLIP = "clip"; private static final String BKW_GR_CLOSE = "close"; private static final String BKW_GR_CLS = "cls"; private static final String BKW_GR_COLOR = "color"; private static final String BKW_GR_FRONT = "front"; private static final String BKW_GR_GETDL = "getdl"; private static final String BKW_GR_GROUP_CMD = "group"; private static final String BKW_GR_HIDE = "hide"; private static final String BKW_GR_LINE = "line"; private static final String BKW_GR_MODIFY = "modify"; private static final String BKW_GR_MOVE = "move"; private static final String BKW_GR_NEWDL = "newdl"; private static final String BKW_GR_ONGRTOUCH_RESUME = "ongrtouch.resume"; private static final String BKW_GR_OPEN = "open"; private static final String BKW_GR_ORIENTATION = "orientation"; private static final String BKW_GR_OVAL = "oval"; private static final String BKW_GR_POINT = "point"; private static final String BKW_GR_POLY = "poly"; private static final String BKW_GR_RECT = "rect"; private static final String BKW_GR_RENDER = "render"; private static final String BKW_GR_ROTATE_END = "rotate.end"; private static final String BKW_GR_ROTATE_START = "rotate.start"; private static final String BKW_GR_SAVE = "save"; private static final String BKW_GR_SCALE = "scale"; private static final String BKW_GR_SCREEN = "screen"; private static final String BKW_GR_SCREEN_TO_BITMAP = "screen.to_bitmap"; private static final String BKW_GR_SET_ANTIALIAS = "set.antialias"; private static final String BKW_GR_SET_PIXELS = "set.pixels"; private static final String BKW_GR_SET_STROKE = "set.stroke"; private static final String BKW_GR_SHOW = "show"; private static final String BKW_GR_SHOW_TOGGLE = "show.toggle"; private static final String BKW_GR_STATUSBAR = "statusbar"; private static final String BKW_GR_STATUSBAR_SHOW = "statusbar.show"; private static final String BKW_GR_TOUCH = "touch"; private static final String BKW_GR_TOUCH2 = "touch2"; // gr bitmap group private static final String BKW_GR_BITMAP_GROUP = "bitmap."; private static final String BKW_GR_BITMAP_CREATE = "create"; private static final String BKW_GR_BITMAP_CROP = "crop"; private static final String BKW_GR_BITMAP_DELETE = "delete"; private static final String BKW_GR_BITMAP_DRAW = "draw"; private static final String BKW_GR_BITMAP_DRAWINTO_END = "drawinto.end"; private static final String BKW_GR_BITMAP_DRAWINTO_START = "drawinto.start"; private static final String BKW_GR_BITMAP_FILL = "fill"; private static final String BKW_GR_BITMAP_LOAD = "load"; private static final String BKW_GR_BITMAP_SAVE = "save"; private static final String BKW_GR_BITMAP_SCALE = "scale"; private static final String BKW_GR_BITMAP_SIZE = "size"; // gr camera group private static final String BKW_GR_CAMERA_GROUP = "camera."; private static final String BKW_GR_CAMERA_AUTOSHOOT = "autoshoot"; private static final String BKW_GR_CAMERA_BLINDSHOOT = "blindshoot"; private static final String BKW_GR_CAMERA_MANUALSHOOT = "manualshoot"; private static final String BKW_GR_CAMERA_SELECT = "select"; private static final String BKW_GR_CAMERA_SHOOT = "shoot"; // gr get group private static final String BKW_GR_GET_GROUP = "get."; private static final String BKW_GR_GET_BMPIXEL = "bmpixel"; private static final String BKW_GR_GET_PARAMS = "params"; private static final String BKW_GR_GET_PIXEL = "pixel"; private static final String BKW_GR_GET_POSITION = "position"; private static final String BKW_GR_GET_TEXTBOUNDS = "textbounds"; private static final String BKW_GR_GET_TYPE = "type"; private static final String BKW_GR_GET_VALUE = "value"; // gr group group private static final String BKW_GR_GROUP_GROUP = "group."; private static final String BKW_GR_GROUP_LIST = "list"; // use existing constants for getdl and newdl // gr paint group private static final String BKW_GR_PAINT_GROUP = "paint."; private static final String BKW_GR_PAINT_COPY = "copy"; private static final String BKW_GR_PAINT_GET = "get"; private static final String BKW_GR_PAINT_RESET = "reset"; // gr text group private static final String BKW_GR_TEXT_GROUP = "text."; private static final String BKW_GR_TEXT_ALIGN = "align"; private static final String BKW_GR_TEXT_BOLD = "bold"; private static final String BKW_GR_TEXT_DRAW = "draw"; private static final String BKW_GR_TEXT_HEIGHT = "height"; private static final String BKW_GR_TEXT_SIZE = "size"; private static final String BKW_GR_TEXT_SKEW = "skew"; private static final String BKW_GR_TEXT_STRIKE = "strike"; private static final String BKW_GR_TEXT_TYPEFACE = "typeface"; private static final String BKW_GR_TEXT_UNDERLINE = "underline"; private static final String BKW_GR_TEXT_WIDTH = "width"; private static final String BKW_GR_TEXT_SETFONT = "setfont"; private static final String GR_KW[] = { // Command list for Format BKW_GR_RENDER, BKW_GR_MODIFY, BKW_GR_MOVE, BKW_GR_BOUNDED_TOUCH2, BKW_GR_BOUNDED_TOUCH, BKW_GR_TOUCH2, BKW_GR_TOUCH, BKW_GR_ARC, BKW_GR_BRIGHTNESS, BKW_GR_CIRCLE, BKW_GR_CLIP, BKW_GR_CLOSE, BKW_GR_CLS, BKW_GR_COLOR, BKW_GR_FRONT, BKW_GR_GETDL, BKW_GR_NEWDL, BKW_GR_GROUP_CMD, BKW_GR_HIDE, BKW_GR_SHOW_TOGGLE, BKW_GR_SHOW, BKW_GR_LINE, BKW_GR_ONGRTOUCH_RESUME, BKW_GR_OPEN, BKW_GR_ORIENTATION, BKW_GR_OVAL, BKW_GR_POINT, BKW_GR_POLY, BKW_GR_RECT, BKW_GR_ROTATE_START, BKW_GR_ROTATE_END, BKW_GR_SAVE, BKW_GR_SCALE, BKW_GR_SCREEN, BKW_GR_SCREEN_TO_BITMAP, BKW_GR_SET_ANTIALIAS, BKW_GR_SET_PIXELS, BKW_GR_SET_STROKE, BKW_GR_STATUSBAR, BKW_GR_STATUSBAR_SHOW, // GR subgroups - Format can handle only one level of grouping BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_CREATE, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_CROP, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_DELETE, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_DRAWINTO_START, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_DRAWINTO_END, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_DRAW, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_FILL, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_LOAD, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_SAVE, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_SCALE, BKW_GR_BITMAP_GROUP + BKW_GR_BITMAP_SIZE, BKW_GR_CAMERA_GROUP + BKW_GR_CAMERA_AUTOSHOOT, // BKW_GR_CAMERA_GROUP + BKW_GR_CAMERA_BLINDSHOOT, BKW_GR_CAMERA_GROUP + BKW_GR_CAMERA_MANUALSHOOT, BKW_GR_CAMERA_GROUP + BKW_GR_CAMERA_SELECT, BKW_GR_CAMERA_GROUP + BKW_GR_CAMERA_SHOOT, BKW_GR_GET_GROUP + BKW_GR_GET_BMPIXEL, BKW_GR_GET_GROUP + BKW_GR_GET_PARAMS, BKW_GR_GET_GROUP + BKW_GR_GET_PIXEL, BKW_GR_GET_GROUP + BKW_GR_GET_POSITION, BKW_GR_GET_GROUP + BKW_GR_GET_TEXTBOUNDS, BKW_GR_GET_GROUP + BKW_GR_GET_TYPE, BKW_GR_GET_GROUP + BKW_GR_GET_VALUE, BKW_GR_GROUP_GROUP + BKW_GR_GROUP_LIST, BKW_GR_GROUP_GROUP + BKW_GR_GETDL, BKW_GR_GROUP_GROUP + BKW_GR_NEWDL, BKW_GR_PAINT_GROUP + BKW_GR_PAINT_COPY, BKW_GR_PAINT_GROUP + BKW_GR_PAINT_GET, BKW_GR_PAINT_GROUP + BKW_GR_PAINT_RESET, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_ALIGN, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_BOLD, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_DRAW, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_HEIGHT, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_SIZE, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_SKEW, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_STRIKE, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_TYPEFACE, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_UNDERLINE, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_WIDTH, BKW_GR_TEXT_GROUP + BKW_GR_TEXT_SETFONT, }; // ****************************** Audio command keywords ******************************* private static final String BKW_AUDIO_LOAD = "load"; private static final String BKW_AUDIO_PLAY = "play"; private static final String BKW_AUDIO_LOOP = "loop"; private static final String BKW_AUDIO_STOP = "stop"; private static final String BKW_AUDIO_VOLUME = "volume"; private static final String BKW_AUDIO_POSITION_CURRENT = "position.current"; private static final String BKW_AUDIO_POSITION_SEEK = "position.seek"; private static final String BKW_AUDIO_LENGTH = "length"; private static final String BKW_AUDIO_RELEASE = "release"; private static final String BKW_AUDIO_PAUSE = "pause"; private static final String BKW_AUDIO_ISDONE = "isdone"; private static final String BKW_AUDIO_RECORD_START = "record.start"; private static final String BKW_AUDIO_RECORD_STOP = "record.stop"; private static final String Audio_KW[] = { // Command list for Format BKW_AUDIO_LOAD, BKW_AUDIO_PLAY, BKW_AUDIO_LOOP, BKW_AUDIO_STOP, BKW_AUDIO_VOLUME, BKW_AUDIO_POSITION_CURRENT, BKW_AUDIO_POSITION_SEEK, BKW_AUDIO_LENGTH, BKW_AUDIO_RELEASE, BKW_AUDIO_PAUSE, BKW_AUDIO_ISDONE, BKW_AUDIO_RECORD_START, BKW_AUDIO_RECORD_STOP }; // ************************** SENSORS keywords and variables *************************** private static final String BKW_SENSORS_LIST = "list"; private static final String BKW_SENSORS_OPEN = "open"; private static final String BKW_SENSORS_READ = "read"; private static final String BKW_SENSORS_CLOSE = "close"; private static final String BKW_SENSORS_ROTATE = "rotate"; private static final String Sensors_KW[] = { // Command list for Format BKW_SENSORS_LIST, BKW_SENSORS_OPEN, BKW_SENSORS_READ, BKW_SENSORS_CLOSE, BKW_SENSORS_ROTATE }; private SensorActivity theSensors; // **************************** GPS keywords and variables ***************************** private static final String BKW_GPS_ALTITUDE = "altitude"; private static final String BKW_GPS_LATITUDE = "latitude"; private static final String BKW_GPS_LONGITUDE = "longitude"; private static final String BKW_GPS_BEARING = "bearing"; private static final String BKW_GPS_ACCURACY = "accuracy"; private static final String BKW_GPS_SPEED = "speed"; private static final String BKW_GPS_PROVIDER = "provider"; private static final String BKW_GPS_SATELLITES = "satellites"; private static final String BKW_GPS_TIME = "time"; private static final String BKW_GPS_LOCATION = "location"; private static final String BKW_GPS_STATUS = "status"; private static final String BKW_GPS_OPEN = "open"; private static final String BKW_GPS_CLOSE = "close"; private static final String GPS_KW[] = { // Command list for Format BKW_GPS_ALTITUDE, BKW_GPS_LATITUDE, BKW_GPS_LONGITUDE, BKW_GPS_BEARING, BKW_GPS_ACCURACY, BKW_GPS_SPEED, BKW_GPS_PROVIDER, BKW_GPS_SATELLITES, BKW_GPS_TIME, BKW_GPS_LOCATION, BKW_GPS_STATUS, BKW_GPS_OPEN, BKW_GPS_CLOSE, }; private GPS theGPS; // *********************** ARRAY command keywords and variables ************************ private enum ArrayOrderOps { DoSort, DoShuffle, DoReverse } private enum ArrayMathOps { DoSum, DoAverage, DoMin, DoMax, DoVariance, DoStdDev } private static final String BKW_ARRAY_DELETE = "delete"; private static final String BKW_ARRAY_DIMS = "dims"; private static final String BKW_ARRAY_FILL = "fill"; private static final String BKW_ARRAY_LENGTH = "length"; private static final String BKW_ARRAY_LOAD = "load"; private static final String BKW_ARRAY_SORT = "sort"; private static final String BKW_ARRAY_SUM = "sum"; private static final String BKW_ARRAY_AVERAGE = "average"; private static final String BKW_ARRAY_REVERSE = "reverse"; private static final String BKW_ARRAY_SHUFFLE = "shuffle"; private static final String BKW_ARRAY_MIN = "min"; private static final String BKW_ARRAY_MAX = "max"; private static final String BKW_ARRAY_VARIANCE = "variance"; private static final String BKW_ARRAY_STD_DEV = "std_dev"; private static final String BKW_ARRAY_COPY = "copy"; private static final String BKW_ARRAY_SEARCH = "search"; private static final String Array_KW[] = { // Command list for Format BKW_ARRAY_DELETE, BKW_ARRAY_DIMS, BKW_ARRAY_FILL, BKW_ARRAY_LENGTH, BKW_ARRAY_LOAD, BKW_ARRAY_SORT, BKW_ARRAY_SUM, BKW_ARRAY_AVERAGE, BKW_ARRAY_REVERSE, BKW_ARRAY_SHUFFLE, BKW_ARRAY_MIN, BKW_ARRAY_MAX, BKW_ARRAY_VARIANCE, BKW_ARRAY_STD_DEV, BKW_ARRAY_COPY, BKW_ARRAY_SEARCH }; // ******************************* LIST command keywords ******************************* private static final String BKW_LIST_CREATE = "create"; private static final String BKW_LIST_ADD_LIST = "add.list"; private static final String BKW_LIST_ADD_ARRAY = "add.array"; private static final String BKW_LIST_ADD = "add"; private static final String BKW_LIST_REPLACE = "replace"; private static final String BKW_LIST_TYPE = "type"; private static final String BKW_LIST_GET = "get"; private static final String BKW_LIST_CLEAR = "clear"; private static final String BKW_LIST_REMOVE = "remove"; private static final String BKW_LIST_INSERT = "insert"; private static final String BKW_LIST_SIZE = "size"; private static final String BKW_LIST_TOARRAY = "toarray"; private static final String BKW_LIST_SEARCH = "search"; private static final String List_KW[] = { // Command list for Format BKW_LIST_CREATE, BKW_LIST_ADD_LIST, BKW_LIST_ADD_ARRAY, BKW_LIST_ADD, BKW_LIST_REPLACE, BKW_LIST_TYPE, BKW_LIST_GET, BKW_LIST_CLEAR, BKW_LIST_REMOVE, BKW_LIST_INSERT, BKW_LIST_SIZE, BKW_LIST_TOARRAY, BKW_LIST_SEARCH }; // ****************************** BUNDLE command keywords ****************************** private static final String BKW_BUNDLE_CREATE = "create"; private static final String BKW_BUNDLE_PUT = "put"; private static final String BKW_BUNDLE_GET = "get"; private static final String BKW_BUNDLE_NEXT = "next"; private static final String BKW_BUNDLE_TYPE = "type"; private static final String BKW_BUNDLE_KEYS = "keys"; private static final String BKW_BUNDLE_COPY = "copy"; private static final String BKW_BUNDLE_CLEAR = "clear"; private static final String BKW_BUNDLE_CONTAIN = "contain"; private static final String BKW_BUNDLE_REMOVE = "remove"; private static final String Bundle_KW[] = { // Command list for Format BKW_BUNDLE_CREATE, BKW_BUNDLE_PUT, BKW_BUNDLE_GET, BKW_BUNDLE_NEXT, BKW_BUNDLE_TYPE, BKW_BUNDLE_KEYS, BKW_BUNDLE_COPY, BKW_BUNDLE_CLEAR, BKW_BUNDLE_CONTAIN, BKW_BUNDLE_REMOVE }; // ****************************** STACK command keywords ******************************* private static final String BKW_STACK_CREATE = "create"; private static final String BKW_STACK_PUSH = "push"; private static final String BKW_STACK_POP = "pop"; private static final String BKW_STACK_PEEK = "peek"; private static final String BKW_STACK_TYPE = "type"; private static final String BKW_STACK_ISEMPTY = "isempty"; private static final String BKW_STACK_CLEAR = "clear"; private static final String Stack_KW[] = { // Command list for Format BKW_STACK_CREATE, BKW_STACK_PUSH, BKW_STACK_POP, BKW_STACK_PEEK, BKW_STACK_TYPE, BKW_STACK_ISEMPTY, BKW_STACK_CLEAR }; // ************* SOCKET command keywords, classes, and variables ************* // socket commands private static final String BKW_SOCKET_MYIP = "myip"; private static final String BKW_SOCKET_CLIENT_GROUP = "client."; private static final String BKW_SOCKET_SERVER_GROUP = "server."; // socket subgroup commands private static final String BKW_SOCKET_CLIENT_IP = "client.ip"; private static final String BKW_SOCKET_CLOSE = "close"; private static final String BKW_SOCKET_CONNECT = "connect"; private static final String BKW_SOCKET_CREATE = "create"; private static final String BKW_SOCKET_DISCONNECT = "disconnect"; private static final String BKW_SOCKET_READ_FILE = "read.file"; private static final String BKW_SOCKET_READ_LINE = "read.line"; private static final String BKW_SOCKET_READ_READY = "read.ready"; private static final String BKW_SOCKET_SERVER_IP = "server.ip"; private static final String BKW_SOCKET_STATUS = "status"; private static final String BKW_SOCKET_WRITE_BYTES = "write.bytes"; private static final String BKW_SOCKET_WRITE_FILE = "write.file"; private static final String BKW_SOCKET_WRITE_LINE = "write.line"; private static final String Socket_KW[] = { // Command list for Format BKW_SOCKET_MYIP, // SOCKET subgroups - Format can handle only one level of grouping // SOCKET.CLIENT. subgroup BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_CONNECT, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_STATUS, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_READ_READY, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_READ_LINE, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_WRITE_LINE, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_WRITE_BYTES, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_CLOSE, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_SERVER_IP, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_READ_FILE, BKW_SOCKET_CLIENT_GROUP + BKW_SOCKET_WRITE_FILE, // SOCKET.SERVER. subgroup BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_CREATE, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_CONNECT, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_STATUS, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_READ_READY, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_READ_LINE, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_WRITE_LINE, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_WRITE_BYTES, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_DISCONNECT, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_CLOSE, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_CLIENT_IP, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_READ_FILE, BKW_SOCKET_SERVER_GROUP + BKW_SOCKET_WRITE_FILE, }; // Constants that indicate the current connection state public static final int STATE_NOT_ENABLED = -1; // channel is not enabled, or server socket not created public static final int STATE_NONE = 0; // channel is doing nothing (initial state) public static final int STATE_LISTENING = 1; // now listening for incoming connections public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection public static final int STATE_CONNECTED = 3; // now connected to a remote device public static final int STATE_READING = 4; // now reading from a remote device public static final int STATE_WRITING = 5; // now writing to a remote device private int clientSocketState; private int serverSocketState; private Socket theClientSocket; private ClientSocketConnectThread clientSocketConnectThread; private BufferedReader ClientBufferedReader; private PrintWriter ClientPrintWriter; private ServerSocket newSS; private ServerSocketConnectThread serverSocketConnectThread; private Socket theServerSocket; private BufferedReader ServerBufferedReader; private PrintWriter ServerPrintWriter; private class ServerSocketConnectThread extends Thread { public void run() { try { theServerSocket = newSS.accept(); ServerBufferedReader = new BufferedReader(new InputStreamReader(theServerSocket.getInputStream())); ServerPrintWriter = new PrintWriter( new BufferedWriter(new OutputStreamWriter(theServerSocket.getOutputStream())), true); serverSocketState = STATE_CONNECTED; } catch (Exception e) { serverSocketState = STATE_NONE; } finally { serverSocketConnectThread = null; // null global reference to itself } Log.d(LOGTAG, "serverSocketConnectThread exit, state " + serverSocketState); } @Override public void interrupt() { if (serverSocketState == STATE_LISTENING) { // in case SERVER_DISCONNECT interrupts thread serverSocketState = STATE_NONE; // change state or SERVER_STATUS will report LISTENING } super.interrupt(); } } private class ClientSocketConnectThread extends Thread { private final String mAddress; private final int mPort; public ClientSocketConnectThread(String address, int port) { super(); mAddress = address; mPort = port; } public void run() { try { theClientSocket = new Socket(mAddress, mPort); ClientBufferedReader = new BufferedReader(new InputStreamReader(theClientSocket.getInputStream())); ClientPrintWriter = new PrintWriter( new BufferedWriter(new OutputStreamWriter(theClientSocket.getOutputStream())), true); clientSocketState = STATE_CONNECTED; } catch (Exception e) { clientSocketState = STATE_NONE; } finally { clientSocketConnectThread = null; // null global reference to itself } Log.d(LOGTAG, "clientSocketConnectThread exit, state " + clientSocketState); } @Override public void interrupt() { if (clientSocketState == STATE_CONNECTING) { // in case CLIENT_CLOSE interrupts thread clientSocketState = STATE_NONE; // change state or CLIENT_STATUS will report CONNECTING } super.interrupt(); } } // ************************* DEBUG and ECHO keywords ************************* private static final String BKW_DEBUG_ECHO_ON = BKW_ECHO_GROUP + BKW_ON; private static final String BKW_DEBUG_ECHO_OFF = BKW_ECHO_GROUP + BKW_OFF; private static final String BKW_DEBUG_DUMP_SCALARS = "dump.scalars"; private static final String BKW_DEBUG_DUMP_ARRAY = "dump.array"; private static final String BKW_DEBUG_DUMP_LIST = "dump.list"; private static final String BKW_DEBUG_DUMP_STACK = "dump.stack"; private static final String BKW_DEBUG_DUMP_BUNDLE = "dump.bundle"; private static final String BKW_DEBUG_WATCH_CLEAR = "watch.clear"; private static final String BKW_DEBUG_WATCH = "watch"; private static final String BKW_DEBUG_SHOW_SCALARS = "show.scalars"; private static final String BKW_DEBUG_SHOW_ARRAY = "show.array"; private static final String BKW_DEBUG_SHOW_LIST = "show.list"; private static final String BKW_DEBUG_SHOW_STACK = "show.stack"; private static final String BKW_DEBUG_SHOW_BUNDLE = "show.bundle"; private static final String BKW_DEBUG_SHOW_WATCH = "show.watch"; private static final String BKW_DEBUG_SHOW_PROGRAM = "show.program"; private static final String BKW_DEBUG_SHOW = "show"; private static final String BKW_DEBUG_CONSOLE = "console"; private static final String BKW_DEBUG_COMMANDS = "commands"; private static final String BKW_DEBUG_STATS = "stats"; private static final String debug_KW[] = { // Command list for Format // Do not include BKW_PRINT_SHORTCUT BKW_ON, BKW_OFF, BKW_PRINT, BKW_DEBUG_ECHO_ON, BKW_DEBUG_ECHO_OFF, BKW_DEBUG_DUMP_SCALARS, BKW_DEBUG_DUMP_ARRAY, BKW_DEBUG_DUMP_LIST, BKW_DEBUG_DUMP_STACK, BKW_DEBUG_DUMP_BUNDLE, BKW_DEBUG_WATCH_CLEAR, BKW_DEBUG_WATCH, BKW_DEBUG_SHOW_SCALARS, BKW_DEBUG_SHOW_ARRAY, BKW_DEBUG_SHOW_LIST, BKW_DEBUG_SHOW_STACK, BKW_DEBUG_SHOW_BUNDLE, BKW_DEBUG_SHOW_WATCH, BKW_DEBUG_SHOW_PROGRAM, BKW_DEBUG_SHOW, BKW_DEBUG_CONSOLE, BKW_DEBUG_COMMANDS, BKW_DEBUG_STATS }; // Legacy: can use DEBUG.ECHO or just ECHO private static final String echo_KW[] = { // Command list for Format BKW_ON, BKW_OFF }; // ******************** FTP Client keywords and variables ******************** private static final String BKW_FTP_OPEN = "open"; private static final String BKW_FTP_CLOSE = "close"; private static final String BKW_FTP_DIR = "dir"; private static final String BKW_FTP_CD = "cd"; private static final String BKW_FTP_GET = "get"; private static final String BKW_FTP_PUT = "put"; private static final String BKW_FTP_DELETE = "delete"; private static final String BKW_FTP_RMDIR = "rmdir"; private static final String BKW_FTP_MKDIR = "mkdir"; private static final String BKW_FTP_RENAME = "rename"; private static final String ftp_KW[] = { // FTP command list for Format BKW_FTP_OPEN, BKW_FTP_CLOSE, BKW_FTP_DIR, BKW_FTP_CD, BKW_FTP_GET, BKW_FTP_PUT, BKW_FTP_DELETE, BKW_FTP_RMDIR, BKW_FTP_MKDIR, BKW_FTP_RENAME }; public FTPClient mFTPClient = null; public String FTPdir = null; // ************************ CAMERA command variables ************************* public static Bitmap CameraBitmap; public static boolean CameraDone; private int CameraNumber; private int NumberOfCameras; // -1 if we don't know yet // ****************** Bluetooth (BT) keywords and variables ****************** // Message types sent from the BluetoothChatService public static final int MESSAGE_STATE_CHANGE = MESSAGE_BT_GROUP + 1; public static final int MESSAGE_READ = MESSAGE_BT_GROUP + 2; public static final int MESSAGE_WRITE = MESSAGE_BT_GROUP + 3; public static final int MESSAGE_DEVICE_NAME = MESSAGE_BT_GROUP + 4; // Key names received from the BluetoothChatService public static final String DEVICE_NAME = "device_name"; public static final String TOAST = "toast"; // Intent request codes public static final int REQUEST_CONNECT_DEVICE_SECURE = 1; public static final int REQUEST_CONNECT_DEVICE_INSECURE = 2; public static final int REQUEST_ENABLE_BT = 3; public static UUID MY_UUID_SECURE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); public static UUID MY_UUID_INSECURE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); public static int bt_enabled = 0; private int bt_state; public static boolean bt_Secure; // Name of the connected device private String mConnectedDeviceName = null; // Array adapter for the conversation thread private StringBuffer mOutStringBuffer; // Local Bluetooth adapter public static BluetoothAdapter mBluetoothAdapter = null; // Member object for the chat services public static BluetoothChatService mChatService = null; // Input buffer - use this for synchronization lock private final ArrayList<String> BT_Read_Buffer = new ArrayList<String>(); public static BluetoothDevice btConnectDevice = null; private static final String BKW_BT_OPEN = "open"; private static final String BKW_BT_CLOSE = "close"; private static final String BKW_BT_STATUS = "status"; private static final String BKW_BT_CONNECT = "connect"; private static final String BKW_BT_DEVICE_NAME = "device.name"; private static final String BKW_BT_WRITE = "write"; private static final String BKW_BT_READ_READY = "read.ready"; private static final String BKW_BT_READ_BYTES = "read.bytes"; private static final String BKW_BT_SET_UUID = "set.uuid"; private static final String BKW_BT_LISTEN = "listen"; private static final String BKW_BT_RECONNECT = "reconnect"; private static final String BKW_BT_ONREADREADY_RESUME = "onreadready.resume"; private static final String BKW_BT_DISCONNECT = "disconnect"; private static final String bt_KW[] = { // Bluetooth command list for Format BKW_BT_OPEN, BKW_BT_CLOSE, BKW_BT_STATUS, BKW_BT_CONNECT, BKW_BT_DEVICE_NAME, BKW_BT_WRITE, BKW_BT_READ_READY, BKW_BT_READ_BYTES, BKW_BT_SET_UUID, BKW_BT_LISTEN, BKW_BT_RECONNECT, BKW_BT_ONREADREADY_RESUME, BKW_BT_DISCONNECT }; // *************** Superuser (SU) and SYSTEM command keywords **************** private static final String BKW_SU_OPEN = "open"; private static final String BKW_SU_WRITE = "write"; private static final String BKW_SU_READ_READY = "read.ready"; private static final String BKW_SU_READ_LINE = "read.line"; private static final String BKW_SU_CLOSE = "close"; private static final String su_KW[] = { // Command list for Format BKW_SU_OPEN, BKW_SU_WRITE, BKW_SU_READ_READY, BKW_SU_READ_LINE, BKW_SU_CLOSE }; private static final String[] System_KW = su_KW; // Command list for Format // *********************** SOUNDPOOL command keywords ************************ private static final String BKW_SOUNDPOOL_OPEN = "open"; private static final String BKW_SOUNDPOOL_LOAD = "load"; private static final String BKW_SOUNDPOOL_PLAY = "play"; private static final String BKW_SOUNDPOOL_STOP = "stop"; private static final String BKW_SOUNDPOOL_UNLOAD = "unload"; private static final String BKW_SOUNDPOOL_PAUSE = "pause"; private static final String BKW_SOUNDPOOL_RESUME = "resume"; private static final String BKW_SOUNDPOOL_RELEASE = "release"; private static final String BKW_SOUNDPOOL_SETVOLUME = "setvolume"; private static final String BKW_SOUNDPOOL_SETPRIORITY = "setpriority"; private static final String BKW_SOUNDPOOL_SETLOOP = "setloop"; private static final String BKW_SOUNDPOOL_SETRATE = "setrate"; private static final String sp_KW[] = { // Command list for Format BKW_SOUNDPOOL_OPEN, BKW_SOUNDPOOL_LOAD, BKW_SOUNDPOOL_PLAY, BKW_SOUNDPOOL_STOP, BKW_SOUNDPOOL_UNLOAD, BKW_SOUNDPOOL_PAUSE, BKW_SOUNDPOOL_RESUME, BKW_SOUNDPOOL_RELEASE, BKW_SOUNDPOOL_SETVOLUME, BKW_SOUNDPOOL_SETPRIORITY, BKW_SOUNDPOOL_SETLOOP, BKW_SOUNDPOOL_SETRATE }; // ****************** RINGER command keywords and variables ****************** private static final String BKW_RINGER_GET_MODE = "get.mode"; private static final String BKW_RINGER_SET_MODE = "set.mode"; private static final String BKW_RINGER_GET_VOLUME = "get.volume"; private static final String BKW_RINGER_SET_VOLUME = "set.volume"; private static final String ringer_KW[] = { // Command list for Format BKW_RINGER_GET_MODE, BKW_RINGER_SET_MODE, BKW_RINGER_GET_VOLUME, BKW_RINGER_SET_VOLUME }; private static final int RINGER_UNKNOWN = -1; private static final int RINGER_SILENT = 0; private static final int RINGER_VIBRATE = 1; private static final int RINGER_NORMAL = 2; // ****************** HEADSET command classes and variables ****************** private int headsetState; private String headsetName; private int headsetMic; public class BroadcastsHandler extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equalsIgnoreCase(Intent.ACTION_HEADSET_PLUG)) { // String data = intent.getDataString(); // Bundle extraData = intent.getExtras(); headsetState = intent.getIntExtra("state", -1); headsetName = intent.getStringExtra("name"); headsetMic = intent.getIntExtra("microphone", -1); } } } private BroadcastsHandler headsetBroadcastReceiver = null; // ******************* HTML command keywords and variables ******************* private static final String BKW_HTML_OPEN = "open"; private static final String BKW_HTML_ORIENTATION = "orientation"; private static final String BKW_HTML_LOAD_URL = "load.url"; private static final String BKW_HTML_LOAD_STRING = "load.string"; private static final String BKW_HTML_GET_DATALINK = "get.datalink"; private static final String BKW_HTML_CLOSE = "close"; private static final String BKW_HTML_GO_BACK = "go.back"; private static final String BKW_HTML_GO_FORWARD = "go.forward"; private static final String BKW_HTML_CLEAR_CACHE = "clear.cache"; private static final String BKW_HTML_CLEAR_HISTORY = "clear.history"; private static final String BKW_HTML_POST = "post"; private static final String html_KW[] = { // Command list for Format BKW_HTML_OPEN, BKW_HTML_ORIENTATION, BKW_HTML_LOAD_URL, BKW_HTML_LOAD_STRING, BKW_HTML_GET_DATALINK, BKW_HTML_CLOSE, BKW_HTML_GO_BACK, BKW_HTML_GO_FORWARD, BKW_HTML_CLEAR_CACHE, BKW_HTML_CLEAR_HISTORY, BKW_HTML_POST }; // Message types for the HTML commands private static final int MESSAGE_HTML_OPEN = MESSAGE_HTML_GROUP + 1; private static final int MESSAGE_GO_BACK = MESSAGE_HTML_GROUP + 2; private static final int MESSAGE_GO_FORWARD = MESSAGE_HTML_GROUP + 3; private static final int MESSAGE_CLEAR_CACHE = MESSAGE_HTML_GROUP + 4; private static final int MESSAGE_CLEAR_HISTORY = MESSAGE_HTML_GROUP + 5; private static final int MESSAGE_LOAD_URL = MESSAGE_HTML_GROUP + 6; private static final int MESSAGE_LOAD_STRING = MESSAGE_HTML_GROUP + 7; private static final int MESSAGE_POST = MESSAGE_HTML_GROUP + 8; private Intent htmlIntent; private boolean mWebFront; // ************************** SMS command variables ************************** private static final String BKW_SMS_RCV_INIT = "rcv.init"; private static final String BKW_SMS_RCV_NEXT = "rcv.next"; private static final String BKW_SMS_SEND = "send"; private static final String SMS_KW[] = { // Command list for Format BKW_SMS_RCV_INIT, BKW_SMS_RCV_NEXT, BKW_SMS_SEND }; // ********************* Speech to Text (STT) variables ********************** public static final int VOICE_RECOGNITION_REQUEST_CODE = 1234; private static String sttDefaultPrompt; private static String sttPrompt; public static ArrayList<String> sttResults; public static boolean sttListening; public static boolean sttDone; // ********************** Text to Speech (TTS) keywords ********************** private static final String BKW_TTS_INIT = "init"; private static final String BKW_TTS_SPEAK_TOFILE = "speak.tofile"; private static final String BKW_TTS_SPEAK = "speak"; private static final String BKW_TTS_STOP = "stop"; private static final String tts_KW[] = { // TTS command list for Format BKW_TTS_INIT, BKW_TTS_SPEAK_TOFILE, BKW_TTS_SPEAK, BKW_TTS_STOP }; // ************************* TIMER command variables ************************* private static final String BKW_TIMER_SET = "set"; private static final String BKW_TIMER_CLEAR = "clear"; private static final String BKW_TIMER_RESUME = "resume"; private static final String Timer_KW[] = { // Command list for Format BKW_TIMER_SET, BKW_TIMER_CLEAR, BKW_TIMER_RESUME }; // *********************** TIMEZONE command variables ************************ private static final String BKW_TIMEZONE_SET = "set"; private static final String BKW_TIMEZONE_GET = "get"; private static final String BKW_TIMEZONE_LIST = "list"; private static final String TimeZone_KW[] = { // Command list for Format BKW_TIMEZONE_SET, BKW_TIMEZONE_GET, BKW_TIMEZONE_LIST }; // ************************* PHONE command variables ************************* private static final String BKW_PHONE_CALL = "call"; private static final String BKW_PHONE_DIAL = "dial"; private static final String BKW_PHONE_RCV_INIT = "rcv.init"; private static final String BKW_PHONE_RCV_NEXT = "rcv.next"; private static final String BKW_PHONE_INFO = "info"; private static final String phone_KW[] = { // Command list for Format BKW_PHONE_CALL, BKW_PHONE_RCV_INIT, BKW_PHONE_RCV_NEXT, BKW_PHONE_INFO }; // ***************** VOLKEYS command keywords and variables ****************** private static final String VolKeys_KW[] = { // VolKeys command list for Format BKW_ON, BKW_OFF }; // If true, this flag blocks transmission of certain Key events to the system. // See onKeyDown() in Run.java and GR.java. public static boolean mBlockVolKeys = false; // ************************** APP command variables ************************** private static final String BKW_APP_BROADCAST = "broadcast"; private static final String BKW_APP_START = "start"; private static final String app_KW[] = { // Command list for Format BKW_APP_BROADCAST, BKW_APP_START }; // *************************************************************************** private Context getContext() { ContextManager cm = Basic.getContextManager(); return cm.getContext(); } // These sendMessage methods are used by mInterpreter to send messages to mHandler. // For convenience, there are several combinations of message parameters provided. private void sendMessage(int what) { mHandler.obtainMessage(what).sendToTarget(); } private void sendMessage(int what, Object obj) { // Use this to send a String or other Object mHandler.obtainMessage(what, obj).sendToTarget(); } private void sendMessage(int what, int arg1, int arg2) { // Use this to send one or two int arguments mHandler.obtainMessage(what, arg1, arg2).sendToTarget(); } // This Handler is in the UI (foreground) thread part of Run. // It gets control when the interpreter thread sends a Message. private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what & MESSAGE_GROUP_MASK) { case MESSAGE_DEFAULT_GROUP: switch (msg.what) { case MESSAGE_CHECKPOINT: // no more messages pending mMessagePending = false; break; } break; case MESSAGE_CONSOLE_GROUP: switch (msg.what) { case MESSAGE_UPDATE_CONSOLE: updateConsole((String[]) msg.obj); break; case MESSAGE_CONSOLE_LINE_CHAR: char c = (char) msg.arg1; synchronized (mConsoleBuffer) { int n = mOutput.size() - 1; String s = mOutput.get(n) + c; mOutput.set(n, s); mConsole.notifyDataSetChanged(); } break; case MESSAGE_CLEAR_CONSOLE: clearConsole(); break; case MESSAGE_CONSOLE_TITLE: String title = (String) msg.obj; setTitle((title != null) ? title : getResources().getString(R.string.run_name)); break; } break; case MESSAGE_DIALOG_GROUP: switch (msg.what) { case MESSAGE_INPUT_DIALOG: doInputDialog((DialogArgs) msg.obj); break; case MESSAGE_ALERT_DIALOG: doAlertDialog((DialogArgs) msg.obj); break; case MESSAGE_TOAST: String msgText = (String) msg.obj; Bundle b = msg.getData(); int duration = b.getInt("dur", Toast.LENGTH_SHORT); Toast toast = Toaster(msgText, duration); int x = msg.arg1; int y = msg.arg2; toast.setGravity(Gravity.CENTER, x, y); toast.show(); break; } break; case MESSAGE_BT_GROUP: handleBTMessage(msg); // handle Bluetooth Messages break; case MESSAGE_HTML_GROUP: handleHtmlMessage(msg); // handle HTML Messages break; case MESSAGE_DEBUG_GROUP: handleDebugMessage(msg); // handle debug Messages break; default: // unrecognized Message break; // ignore it } } }; // mHandler private Toast Toaster(CharSequence msg) { // default: short, high toast Toast toast = Toaster(msg, Toast.LENGTH_SHORT); toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 50); return toast; } private Toast Toaster(CharSequence msg, int duration) { Toast toast = Toast.makeText(Run.this, msg, duration); return toast; } private void updateConsole(String... strs) { synchronized (mConsoleBuffer) { if (mConsoleBuffer.size() != 0) { // if any lines mOutput.addAll(mConsoleBuffer); // write each line to screen mConsoleBuffer.clear(); } if ((strs != null) && (strs.length != 0)) { for (String str : strs) { mOutput.add(str); } } mConsole.notifyDataSetChanged(); } } private void errorToConsole(String str) { // conditionally write an error message to the console if (mOnErrorInt == null) { // if there is an OnError label, do not show the message. updateConsole(str); } } // TODO: Move this to Interpreter when Interpreter is put in its own class file public static Intent buildVoiceRecognitionIntent() { if (sttPrompt == null) { sttPrompt = sttDefaultPrompt; } Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); intent.putExtra(RecognizerIntent.EXTRA_PROMPT, sttPrompt); return intent; } // ************************************* Run Entry Point ************************************** // Called by Android runtime when Run is launched from Editor or AutoRun @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.v(LOGTAG, "onCreate " + this.toString()); if (Basic.lines == null) { Log.e(LOGTAG, "onCreate: Lost context. Bail out."); finish(); return; } // Log.v(LOGTAG, "isOld " + isOld); if (isOld) { if (theWakeLock != null) { theWakeLock.release(); } if (theWifiLock != null) { theWifiLock.release(); } } theWakeLock = null; theWifiLock = null; isOld = true; ContextManager cm = Basic.getContextManager(); cm.registerContext(ContextManager.ACTIVITY_RUN, this); cm.setCurrent(ContextManager.ACTIVITY_RUN); mConsoleIntent = getIntent(); // keep a reference to the Intent that started Run InitRunVars(); // Establish the output screen setContentView(R.layout.console); TextStyle style = new TextStyle(Basic.defaultTextStyle, Settings.getConsoleTypeface(this)); mConsole = new Basic.ColoredTextAdapter(this, mOutput, style); clearConsole(); lv = (ConsoleListView) findViewById(R.id.console); lv.setAdapter(mConsole); lv.setKeyboardManager(mKeyboardChangeListener); lv.setTextFilterEnabled(false); lv.setBackgroundColor(Settings.getEmptyConsoleColor(this).equals("line") ? mConsole.getLineColor() : mConsole.getBackgroundColor()); // default is "background" if (Settings.getLinedConsole(this)) { lv.setDivider(new ColorDrawable(mConsole.getLineColor())); // override default from theme, sometimes it's invisible if (lv.getDividerHeight() < 1) { lv.setDividerHeight(1); } // make sure the divider shows } else { lv.setDividerHeight(0); // don't show the divider } setVolumeControlStream(AudioManager.STREAM_MUSIC); setRequestedOrientation(Settings.getSreenOrientation(this)); headsetBroadcastReceiver = new BroadcastsHandler(); this.registerReceiver(headsetBroadcastReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); // Listeners for Console Touch lv.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { triggerInterrupt(Interrupt.CONS_TOUCH_BIT); TouchedConsoleLine = position + 1; ConsoleLongTouch = false; } }); lv.setOnItemLongClickListener(new OnItemLongClickListener() { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { triggerInterrupt(Interrupt.CONS_TOUCH_BIT); TouchedConsoleLine = position + 1; ConsoleLongTouch = true; return true; } }); mInterpreter = new Interpreter(); // start the interpreter in a background Thread mInterpreter.start(); } // end onCreate private void InitRunVars() { // init vars needed for Run clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mInterpreter = null; // reference to Interpreter Thread } // The following methods run in the foreground. The are called by asynchronous user events // caused by the user pressing a key. private boolean handleMenuKey(KeyEvent event) { boolean trapped = triggerInterrupt(Interrupt.MENU_KEY_BIT); if (trapped) return true; // trapped by onMenuKey: if (Basic.isAPK || // if MENU key hit in APK and not trapped by OnMenuKey (event == null)) // or relayed via EventList with null event object { return true; // then tell OS to ignore it } return false; // let Android create the Run Menu } public void GR_BackPressed() { boolean trapped = triggerInterrupt(Interrupt.BACK_KEY_BIT); if (trapped) return; // trapped by onBackKey if (mInterpreter != null) { mInterpreter.GRstop(); } // stop GR, don't wait for it doBackPressed(); // stop interpreter, too } @Override public void onBackPressed() { // system BACK key callback boolean trapped = triggerInterrupt(Interrupt.BACK_KEY_BIT); if (trapped) return; // trapped by onBackKey: doBackPressed(); // untrapped BACK key } private void doBackPressed() { // BACK key pressed in Run, interrupt check already done if (Basic.DoAutoRun) { Exit = true; // if AutoRun, untrapped BACK key always means exit } if (mInterpreterRunning) { Stop = true; // if running a program, stop it } else { finish(); // else already stopped, return to the Editor } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // The user hit a key // Log.v(LOGTAG, "onKeyDown" + keyCode); // Eat the MENU key Down event, will handle the Up event. if (keyCode == KeyEvent.KEYCODE_MENU) return true; // If event is null, we called this directly from runLoop(), // so the return value does not matter. if (event == null) return true; // Alternate: if the user program has set mBlockVolKeys, block everything but BACK // if (mBlockVolKeys && (keyCode != KeyEvent.KEYCODE_BACK)) return true; // If the user program has set mBlockVolKeys, block volume and headset keys. if (mBlockVolKeys && ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) || (keyCode == KeyEvent.KEYCODE_MUTE) || (keyCode == KeyEvent.KEYCODE_HEADSETHOOK))) { return true; } // Otherwise pass the KeyEvent up the chain. return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { // Log.v(LOGTAG, "onKeyUp" + keyCode); if (keyCode == KeyEvent.KEYCODE_MENU) { return handleMenuKey(event); } if (keyCode == KeyEvent.KEYCODE_BACK) { return super.onKeyUp(keyCode, event); } char c; String theKey = "@"; int n; if (keyCode >= 7 && keyCode <= 16) { n = keyCode - 7; c = Numbers.charAt(n); theKey = Character.toString(c); } else if (keyCode >= 29 && keyCode <= 54) { n = keyCode - 29; c = Chars.charAt(n); theKey = Character.toString(c); } else if (keyCode == 62) { c = ' '; theKey = Character.toString(c); } else if (keyCode >= 19 && keyCode <= 23) { switch (keyCode) { case 19: theKey = "up"; break; case 20: theKey = "down"; break; case 21: theKey = "left"; break; case 22: theKey = "right"; break; case 23: theKey = "go"; break; } } else { theKey = "key " + keyCode; } synchronized (this) { InChar.add(theKey); } triggerInterrupt(Interrupt.KEY_BIT); return true; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Called when the menu key is pressed. super.onCreateOptionsMenu(menu); if (!Settings.getConsoleMenu(this)) { return false; } MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.run, menu); MenuItem item = menu.getItem(1); if (Basic.DoAutoRun) { // If APK or shortcut, menu action is "Exit", not "Editor" item.setTitle(getString(R.string.exit)); } item.setEnabled(false); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { // Executed when Menu key is pressed (before onCreateOptionsMenu() above. super.onPrepareOptionsMenu(menu); MenuItem item; if (Stop) { // If program running display with Editor dimmed item = menu.getItem(0); // Other wise dim Stop and undim Editor item.setEnabled(false); item = menu.getItem(1); item.setEnabled(true); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // A menu item is selected switch (item.getItemId()) { case R.id.stop: // User wants to stop execution updateConsole("Stopped by user."); // tell user Stop = true; // signal main loop to stop disableInterrupt(Interrupt.BACK_KEY_BIT); // menu-selected stop is not trappable return true; case R.id.editor: // User pressed Editor if (!Basic.DoAutoRun && SyntaxError && (mInterpreter != null)) { Editor.SyntaxErrorDisplacement = mInterpreter.ExecutingLineIndex; } // TODO: This can't possibly be enough. Shouldn't we run cleanup() or equivalent? Basic.clearContextManager(); // unregister all Context references if (mChatService != null) { mChatService.stop(); mChatService = null; } finish(); } return true; } @Override protected void onStart() { Log.v(LOGTAG, "onStart " + this.toString()); super.onStart(); } @Override protected void onResume() { Log.v(LOGTAG, "onResume " + this.toString()); RunPaused = false; triggerInterrupt(Interrupt.BACKGROUND_BIT); Basic.getContextManager().onResume(ContextManager.ACTIVITY_RUN); // if (WaitForInput) { theAlertDialog = doInputDialog().show(); } maybe??? super.onResume(); } @Override protected void onPause() { // The Android OS wants me to dismiss dialog while paused so I will /* if (WaitForInput) { theAlertDialog.dismiss(); InputDismissed = true; } */ Log.v(LOGTAG, "onPause " + this.toString()); if (lv.mKB != null) { lv.mKB.forceHide(); } Basic.getContextManager().onPause(ContextManager.ACTIVITY_RUN); // If there is a Media Player running, pause it and hope // that it works. /* if (theMP != null) { try { theMP.pause(); } catch (IllegalStateException e) {} } */ RunPaused = true; super.onPause(); } @Override protected void onStop() { Log.v(LOGTAG, "onStop " + this.toString()); if (!GR.Running) { triggerInterrupt(Interrupt.BACKGROUND_BIT); } super.onStop(); } @Override protected void onRestart() { Log.v(LOGTAG, "onRestart " + this.toString()); super.onRestart(); } @Override protected void onDestroy() { Log.v(LOGTAG, "onDestroy " + this.toString()); if (theSensors != null) { theSensors.stop(); theSensors = null; } if (theGPS != null) { Log.d(LOGTAG, "Stopping GPS from onDestroy"); theGPS.stop(); theGPS = null; } if (theWakeLock != null) { theWakeLock.release(); theWakeLock = null; } if (theWifiLock != null) { theWifiLock.release(); theWifiLock = null; } if (headsetBroadcastReceiver != null) { unregisterReceiver(headsetBroadcastReceiver); } // BitmapListClear(); super.onDestroy(); } @Override public void onLowMemory() { Log.d(LOGTAG, "onLowMemory"); // If interpreter is running and OnLowMemory: label exists, set the Low Mem interrupt // otherwise notify the user via the Console. boolean trapped = triggerInterrupt(Interrupt.LOW_MEM_BIT); if (!trapped) { updateConsole("Warning: Low Memory"); } } public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CONNECT_DEVICE_SECURE: // When DeviceListActivity returns with a device to connect if ((resultCode == Activity.RESULT_OK) && (mInterpreter != null)) { mInterpreter.connectDevice(data, bt_Secure); } break; case REQUEST_CONNECT_DEVICE_INSECURE: // When DeviceListActivity returns with a device to connect if ((resultCode == Activity.RESULT_OK) && (mInterpreter != null)) { mInterpreter.connectDevice(data, false); } break; case REQUEST_ENABLE_BT: // When the request to enable Bluetooth returns if (resultCode == Activity.RESULT_OK) { // Bluetooth is now enabled, so set up a chat session bt_enabled = 1; } else { bt_enabled = -1; } break; case VOICE_RECOGNITION_REQUEST_CODE: if (resultCode == RESULT_OK) { sttResults = new ArrayList<String>(); sttResults = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); } sttDone = true; break; } } // ******************************************************************************************** // Methods used to communicate between Run and Interpreter // or utilities used in both threads private boolean triggerInterrupt(Integer bit) { // delegate to the interpreter if it exists return (mInterpreter != null) && (mInterpreter.triggerInterrupt(bit)); } private void disableInterrupt(Integer bit) { // delegate to the interpreter if it exists if (mInterpreter != null) { mInterpreter.disableInterrupt(bit); } } private void clearConsole() { synchronized (mConsoleBuffer) { mConsole.clear(); mOutput.trimToSize(); mOutput.ensureCapacity(MIN_CONSOLE_LINES); mConsole.notifyDataSetChanged(); } } private void checkpointMessage() { mMessagePending = true; sendMessage(MESSAGE_CHECKPOINT); } private static String chomp(String str) { return str.substring(0, str.length() - 1); } private static String quote(String str) { return '\"' + str + '\"'; } // ******************************************************************************************** // Static methods that would be attached to a class if we didn't nest everything. // When this file is broken up, these should be attached to appropriate top-level classes. // Get the absolute index of the point where name can be inserted into // a list of names to preserve alphabetical order within the list. // The name must not already be in names. // // Use of Collections.binarySearch for speed: thanks to Nicolas Mougin. public static int newVarIndex(ArrayList<String> names, String name) { int index = Collections.binarySearch(names, name); if (index >= 0) { throw new InvalidParameterException("newVarIndex: variable " + name + " already exists"); } return -(index + 1); // return the absolute index // Alternate ending to add log: // index = -(index + 1); // make the index absolute // Log.v(LOGTAG, CLASSTAG + " newVarIndex() create var " + name + " at index " + index + "/" + vNames.size() + "(start=" + vStart + ")"); // return index; } // ***************** Dialogs ***************** // Superset of fields needed by all Dialogs. private class DialogArgs { public String mTitle = null; public String mInputDefault = null; public String mMessage = null; public String mButton1 = null; public String mButton2 = null; public String mButton3 = null; public String[] mList = null; public Var.Val mReturnVal = null; public Var.Val mLongClickVal = null; } private void doInputDialog(DialogArgs args) { Context context = getContext(); Log.d(LOGTAG, "InputDialog context " + context); EditText text = new EditText(context); text.setText(args.mInputDefault); Var.Val returnVal = args.mReturnVal; if (returnVal.isNumeric()) { text.setInputType(0x00003002); // Limits keys to signed decimal numbers } String btnLabel = args.mButton1; if (btnLabel == null) { btnLabel = "Ok"; } AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setView(text).setCancelable(true).setTitle(args.mTitle) // default null, no title displayed if null or empty .setPositiveButton(btnLabel, null); // need to override default View click handler to prevent // auto-dismiss, but can't do it until after show() builder.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { Log.d(LOGTAG, "Input Dialog onCancel"); mInputCancelled = true; // signal read by executeINPUT } }); final AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { public void onDismiss(DialogInterface dialog) { Log.d(LOGTAG, "Input Dialog onDismiss"); releaseLOCK(); // release the lock that executeINPUT is waiting for } }); dialog.getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); dialog.show(); dialog.getButton(DialogInterface.BUTTON_POSITIVE) // now we can replace the View click listener .setOnClickListener(new InputDialogClickListener(dialog, text, args)); // to prevent auto-dismiss } private class InputDialogClickListener implements View.OnClickListener { // Use inner class members so we don't need as many outer class members. private final AlertDialog mmDialog; private final EditText mmText; private final boolean mmIsNumeric; private final Var.Val mmVal; public InputDialogClickListener(AlertDialog dialog, EditText text, DialogArgs args) { mmDialog = dialog; mmText = text; mmVal = args.mReturnVal; mmIsNumeric = mmVal.isNumeric(); } public void onClick(View view) { String theInput = mmText.getText().toString(); if (mmIsNumeric) { // Numeric Input Handling try { double d = Double.parseDouble(theInput.trim()); // have java parse it into a double mmVal.val(d); } catch (Exception e) { Log.d(LOGTAG, "Input Dialog bad input"); Toaster("Not a number. Try Again.").show(); return; } } else { // String Input Handling mmVal.val(theInput); } mmDialog.dismiss(); Log.d(LOGTAG, "Input Dialog done, positive"); } } private void doAlertDialog(DialogArgs args) { Context context = getContext(); Log.d(LOGTAG, "AlertDialog context " + context); AlertDialog.Builder builder = new AlertDialog.Builder(context); String positive = args.mButton1; String neutral = args.mButton2; String negative = args.mButton3; String[] list = args.mList; AlertDialogClickListener listener = new AlertDialogClickListener(); builder.setCancelable(true).setTitle(args.mTitle); // no title if null or empty if (positive != null) { builder.setPositiveButton(positive, listener); } if (neutral != null) { builder.setNeutralButton(neutral, listener); } if (negative != null) { builder.setNegativeButton(negative, listener); } if (list != null) { builder.setItems(list, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mAlertItemID = which + 1; // convert to 1-based index } }); } else { builder.setMessage(args.mMessage); // list and message are mutually exclusive } builder.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { Log.d(LOGTAG, "Alert Dialog onCancel"); mAlertItemID = 0; // no button clicked or item selected } }); final AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { public void onDismiss(DialogInterface dialog) { Log.d(LOGTAG, "Alert Dialog onDismiss"); releaseLOCK(); // release the lock that executeINPUT is waiting for } }); dialog.show(); } private class AlertDialogClickListener implements DialogInterface.OnClickListener { public void onClick(DialogInterface dialog, int buttonID) { Log.d(LOGTAG, "AlertDialog done, button " + buttonID); int id = 0; // default: no button switch (buttonID) { case DialogInterface.BUTTON_POSITIVE: id = 1; break; case DialogInterface.BUTTON_NEUTRAL: id = 2; break; case DialogInterface.BUTTON_NEGATIVE: id = 3; break; } mAlertItemID = id; } } private void releaseLOCK() { if (!mWaitForLock) return; synchronized (LOCK) { mWaitForLock = false; // semaphore used in waitForLOCK LOCK.notify(); // release the lock } } // ***************** Bluetooth message handler ***************** public boolean handleBTMessage(Message msg) { switch (msg.what) { case MESSAGE_STATE_CHANGE: bt_state = msg.arg1; break; case MESSAGE_WRITE: // byte[] writeBuf = (byte[]) msg.obj; // construct a string from the buffer // String writeMessage = new String(writeBuf); break; case MESSAGE_READ: byte[] readBuf = (byte[]) msg.obj; String readMessage = ""; // construct a string from the valid bytes in the buffer // String readMessage = new String(readBuf, 0, msg.arg1); try { readMessage = new String(readBuf, "ISO-8859-1"); } catch (Exception e) { errorToConsole("Error: " + e.getMessage()); } readMessage = readMessage.substring(0, msg.arg1); synchronized (BT_Read_Buffer) { if (BT_Read_Buffer.size() == 0) { triggerInterrupt(Interrupt.BT_READY_BIT); } BT_Read_Buffer.add(readMessage); } break; case MESSAGE_DEVICE_NAME: // save the connected device's name mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); break; default: return false; // message not recognized } return true; // message handled } // ***************** HTML message handler ***************** public boolean handleHtmlMessage(Message msg) { if ((msg.what == MESSAGE_HTML_OPEN) && (htmlIntent != null)) { startActivityForResult(htmlIntent, BASIC_GENERAL_INTENT); } else if (Web.aWebView != null) { switch (msg.what) { case MESSAGE_GO_BACK: Web.aWebView.goBack(); break; case MESSAGE_GO_FORWARD: Web.aWebView.goForward(); break; case MESSAGE_CLEAR_CACHE: Web.aWebView.clearCache(); break; case MESSAGE_CLEAR_HISTORY: Web.aWebView.clearHistory(); break; case MESSAGE_LOAD_URL: String url = (String) msg.obj; Web.aWebView.webLoadUrl(url); break; case MESSAGE_LOAD_STRING: String[] data = (String[]) msg.obj; Web.aWebView.webLoadString(data[0], data[1]); // baseURL and HTML.Load.String argument break; case MESSAGE_POST: String[] params = (String[]) msg.obj; Web.aWebView.webPost(params[0], params[1]); // URL and data for "POST" request break; default: return false; // message not recognized } } return true; // message handled } //=====================DEBUGGER DIALOG STUFF======================== private void DialogSelector(int selection) { dbDialogScalars = false; dbDialogArray = false; dbDialogList = false; dbDialogStack = false; dbDialogBundle = false; dbDialogWatch = false; dbDialogConsole = false; dbDialogProgram = false; switch (selection) { case 1: dbDialogScalars = true; break; case 2: dbDialogArray = true; break; case 3: dbDialogList = true; break; case 4: dbDialogStack = true; break; case 5: dbDialogBundle = true; break; case 6: dbDialogWatch = true; break; case 7: dbDialogConsole = true; break; default: dbDialogProgram = true; break; } } private void doDebugDialog() { int lineIndex = 0; String text = ""; if (mInterpreter != null) { lineIndex = mInterpreter.ExecutingLineIndex; text = mInterpreter.ExecutingLineBuffer.text(); } ArrayList<String> msg = new ArrayList<String>(); if (!dbDialogProgram) { msg = mInterpreter.dbDoFunc(); msg.add("Executable Line #: " + Integer.toString(lineIndex + 1) + '\n' + chomp(text)); } if (dbDialogScalars) msg.addAll(mInterpreter.dbDoScalars(" ")); if (dbDialogArray) msg.addAll(mInterpreter.dbDoArray(" ")); if (dbDialogList) msg.addAll(mInterpreter.dbDoList(" ")); if (dbDialogStack) msg.addAll(mInterpreter.dbDoStack(" ")); if (dbDialogBundle) msg.addAll(mInterpreter.dbDoBundle(" ")); if (dbDialogWatch) msg.addAll(mInterpreter.dbDoWatch(" ")); if (dbDialogProgram) { for (int i = 0; i < Basic.lines.size(); ++i) { msg.add(((i == lineIndex) ? " >>" : " ") // mark current line + (i + 1) + ": " // one-based line index + chomp(Basic.lines.get(i).text())); // remove newline } } LayoutInflater inflater = getLayoutInflater(); View dialogLayout = inflater.inflate(R.layout.debug_dialog_layout, null); ListView debugView = (ListView) dialogLayout.findViewById(R.id.debug_list); debugView.setAdapter(new ArrayAdapter<String>(Run.this, R.layout.debug_list_layout, msg)); debugView.setVerticalScrollBarEnabled(true); if (dbDialogProgram) { debugView.setSelection(lineIndex); } AlertDialog.Builder builder = new AlertDialog.Builder(Run.this).setCancelable(true) .setTitle(R.string.debug_name).setView(dialogLayout); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface arg0) { DebuggerHalt = true; WaitForDebugResume = false; releaseDebugLOCK(); } }); builder.setPositiveButton("Resume", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { WaitForDebugResume = false; releaseDebugLOCK(); } }); builder.setNeutralButton("Step", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { DebuggerStep = true; WaitForDebugResume = true; releaseDebugLOCK(); } }); builder.setNegativeButton("View Swap", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dbSwap = true; releaseDebugLOCK(); } }); dbDialog = builder.show(); dbDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); } private void doDebugSwapDialog() { ArrayList<String> msg = new ArrayList<String>(); msg.addAll(Arrays.asList("Program", "Scalars", "Array", "List", "Stack", "Bundle", "Watch")); final String[] names = { "View Program", "View Scalars", "View Array", "View List", "View Stack", "View Bundle", "View Watch", "View Console" }; LayoutInflater inflater = getLayoutInflater(); View dialogLayout = inflater.inflate(R.layout.debug_list_s_layout, null); ListView debugView = (ListView) dialogLayout.findViewById(R.id.debug_list_s); debugView.setAdapter(new ArrayAdapter<String>(Run.this, R.layout.simple_list_layout_1, msg)); debugView.setVerticalScrollBarEnabled(true); debugView.setClickable(true); debugView.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { DialogSelector(position); boolean dosel = (dbDialogArray && WatchedArray == null) || (dbDialogList && WatchedList == -1) || (dbDialogStack && WatchedStack == -1) || (dbDialogBundle && WatchedBundle == -1); if (dosel) { // if the element has not been defined ask if user wishes to do so. // or at least this is where it will go. // for now, default to view program. DialogSelector(0); position = 0; } String name = (position < names.length) ? names[position] : ""; Toaster(name).show(); } }); AlertDialog.Builder builder = new AlertDialog.Builder(Run.this).setCancelable(true).setTitle("Select View:") .setView(dialogLayout); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface arg0) { WaitForSwap = false; } }); builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { WaitForSwap = false; dbSwap = false; } }); /* // leave out until the element selector is done. builder.setNeutralButton("Choose Element", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int which) { WaitForSelect = true; } }); */ dbSwapDialog = builder.show(); dbSwapDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); } private void doDebugSelectDialog() { if (dbSelectDialog != null) { dbSelectDialog.dismiss(); } ArrayList<String> msg = new ArrayList<String>(); // TODO: What did Michael have in mind? } public boolean handleDebugMessage(Message msg) { switch (msg.what) { case MESSAGE_DEBUG_DIALOG: doDebugDialog(); break; case MESSAGE_DEBUG_SWAP: doDebugSwapDialog(); break; case MESSAGE_DEBUG_SELECT: doDebugSelectDialog(); break; default: return false; // message not recognized } return true; // message handled } private void releaseDebugLOCK() { if (!mWaitForDebugLock) return; synchronized (DB_LOCK) { mWaitForDebugLock = false; // semaphore used in waitForLOCK DB_LOCK.notify(); // release the lock } } /* ****************************** Start of Basic's run program code ******************************* * * BASIC! program runner -- the BASIC! interpreter -- runs in a background thread. * The code is organized (?) as follows: * * The first chunk is the code that initializes the interpreter and then controls * the running of the program by dispatching each line of code to be executed. * * The second chunk is a set of Command tables that holds the function and command names. * * The third chunk is made up of parsing methods. They identify the command on each line and call * its execution method. The execution methods call the parsing methods to identify variables and * evaluate expressions. * * The final and largest part of Interpreter is code that actually executes user program statements. * */ /* Implementation notes: 1. This class must eventually be moved to its own file. Perhaps to run as a service? Nested classes can't have static members, so until it moves, a few things that really belong to this class are owned by Run instead. 2. Command lines are in the Basic.lines array. Line 0 is valid. To jump to a new line, set ExecutingLineIndex to the line before. RunLoop() increments the index before loading ExecutingLineBuffer. 3. A command line is a ProgramLine object. The first time a line runs, it is fully parsed from the text to locate the right Command object. The ProgramLine stores the Command for future runs of the same line. Some commands use a second Command reference for a sub-command. The THEN and ELSE statements of a single-line IF are fully-parsed. 4. Interrupts: OnError is special, so it is in a named variable. The others are all anonymous. All, including OnError, are in mIntA for "armed" interrupts, i.e, those that have interrupt labels. All except OnError can be in mIntT, the triggered interrupts. Triggering copies the Interrupt object from mIntA to mIntT. Servicing removes it from mIntT. OnError is always serviced first. The rest are serviced in the order they occur. 5. Activity instances other than Run send events to the Interpreter through static Run.mEventList, and instance of Run.EventHoder. 6. Basic.mContextMgr is a static instance of ContextManager. The three major Activity instances (Run, GR, Web) are required to register actions with the ContextManager. The Interpreter uses these actions to track which context is active (if any). 7. Variables. The symbol table is two lists: VarNames and Vars. Each Var in Vars holds a reference to a Var.Val (for scalars) or a Var.ArrayDef or a Var.FnDef Both lists are internal, so index 0 is valid. 8. A scalar value is a Val object. Sometimes variables are named as if the value and the variable are the same, but they are not. 9. Paint objects are in the PaintList array. Object 0 cannot be attached to graphics objects, but is accessible otherwise. Objects 0 and 1 are initially opaque black, antialias on, style.FILL, stroke weight 0.0. 10. GR.OPEN adds another Paint the color of the background at index 2. GR.CLS reinitializes Paints 0 and 1 but does not add PaintList[2]. */ public class Interpreter extends Thread { // The execution of the basic program is done by this background Thread. // This is done to keep the UI responsive. // ***************************** ForNext class ***************************** // Records information about a For/Next loop. Objects go on the ForNextStack. private class ForNext { private int mLine; // loop return location private Var.Val mVal; // loop index private double mStep; // step value private double mLimit; // limit value public ForNext(int line, Var.Val val, double step, double limit) { mLine = line; mVal = val; mStep = step; mLimit = limit; } public int line() { return mLine; } public boolean doStep() { double idx = mVal.nval(); // get the loop index idx += mStep; // do the STEP mVal.val(idx); // update the loop index return (((mStep > 0) && (idx > mLimit)) || // test limit ((mStep <= 0) && (idx < mLimit))); // return true if stepped past limit } } // class ForNext // *************************** WhileRepeat class *************************** // Records information about a While/Repeat loop. Objects go on the WhileStack. private class WhileRepeat { private int mLine; // loop return location private int mArgOffset; // offset to "while" condition expression public WhileRepeat(int line, int offset) { mLine = line; mArgOffset = offset; } public int line() { return mLine; } public int offset() { return mArgOffset; } } // class ForNext // **************************** SwitchDef class **************************** // Fields record line numbers of the Sw.xxx commands. private class SwitchDef { public final ArrayList<Integer> mCases = new ArrayList<Integer>(); // Sw.Case public int mDefault = -1; // Sw.Default public int mEnd = -1; // Sw.End } // ************************* CallStackFrame class ************************* // System state saved across function calls. Objects go on the FunctionStack. private class CallStackFrame { private Var.FnDef mFnDef; // reference to called function private ProgramLine mPL; // reference to ProgramLine of function call // might not be Lines[mELI], could be substitute private int mELI; // record line number of function call private int mLI; // record LinIndex after closing paren private String mPKW; // record PossibleKeyWord public Var.FnDef fnDef() { return mFnDef; } public String name() { return mFnDef.name(); } public void store(Var.FnDef fn) { mFnDef = fn; mPL = ExecutingLineBuffer; mELI = ExecutingLineIndex; mPKW = PossibleKeyWord; } public void storeLI() { mLI = LineIndex; } public void restore() { ExecutingLineBuffer = mPL; ExecutingLineIndex = mELI; LineIndex = mLI; PossibleKeyWord = mPKW; } } // **************************** START OF VARIABLE DEFINITIONS ***************************** // ************************* Various execution control variables ************************** private int ExecutingLineIndex = 0; // Points to the current line in Basic.lines private ProgramLine ExecutingLineBuffer = null; // Holds the current line being executed private int LineIndex = 0; // Current displacement into ExecutingLineBuffer's line private Var.Table mGlobalSymbolTable; // The symbol table that contains all global Vars private Var.Table mSymbolTable; // The current symbol table, all non-global Vars in scope private Stack<Var.Table> mSymbolTables; // Stack of symbol tables to search // "Armed" interrupts and "Triggered" interrupts. "Triggered" are serviced in order. private final HashMap<Integer, Interrupt> mIntA = new HashMap<Integer, Interrupt>(); private final LinkedHashMap<Integer, Interrupt> mIntT = new LinkedHashMap<Integer, Interrupt>(); private Stack<Integer> GosubStack; // Stack used for Gosub/Return private Stack<ForNext> ForNextStack; // Stack used for For/Next private Stack<WhileRepeat> WhileStack; // Stack used for While/Repeat private Stack<Integer> DoStack; // Stack used for Do/Until private Stack<Integer> IfElseStack; // Stack for IF-ELSE-ENDIF operations private Stack<CallStackFrame> FunctionStack; // State saved through the currently executing functions private HashMap<String, Var.FnDef> mFunctionTable; // Globally-accessible table of user-defined functions private boolean fnRTN = false; // Set true by fn.rtn. Cause RunLoop() to return // Working Var objects for the parser, one of each type. // These are used to avoid creating a new Var every time a variable is parsed. private Var.ScalarVar mScalarVar; // Working Var for parser private Var.ArrayVar mArrayVar; // Working Var for parser private Var.FunctionVar mFunctionVar; // Working Var for parser private boolean VarIsInt = false; // temporary integer status used only by fprint private Var.Val mVal; // *TEMPORARY* bridge value set by getVar() and friends // TODO: this is a temporary stand-in for theValueIndex private String StringConstant = ""; // Storage for a string constant private boolean SEisLE = false; // If a String expression result is a logical expression private Double EvalNumericExpressionValue; // Return value from EvalNumericExprssion() private Long EvalNumericExpressionIntValue; // Integer copy of EvalNumericExpressionValue when VarIsInt is true private Double GetNumberValue; // Return value from GetNumber() private String PossibleKeyWord = ""; // Used when TO, STEP, THEN are expected private static final String NO_ERROR = "No error"; private String mErrorMsg = NO_ERROR; // *************************** Random number function variables *************************** private Random randomizer = null; // ******************************* Print command variables ******************************** private String PrintLine = ""; // Hold the Print line currently being built private String textPrintLine = ""; // Hold the TextPrint line currently being built private boolean PrintLineReady = false; // Signals a line is ready to print or write // ******************************** List command variables ******************************** private ArrayList<ArrayList> theLists; private ArrayList<Var.Type> theListsType; // ******************************* Bundle command variables ******************************* private ArrayList<Bundle> theBundles; // ******************************* Stack command variables ******************************** private ArrayList<Stack> theStacks; private ArrayList<Var.Type> theStacksType; // ******************************** Read command variables ******************************** private ArrayList<Var.Val> readData; private int readNext; // ********************************** File I/O variables ********************************** private ArrayList<FileInfo> FileTable; // File table list // ******************************** SQL command variables ********************************* private ArrayList<SQLiteDatabase> DataBases; // List of created databases private ArrayList<Cursor> Cursors; // List of database cursors // ******************************** Font command variables ******************************** private ArrayList<Typeface> FontList; // ***************************** Graphics commands variables ****************************** private Intent mGrIntent; // Graphics Activity (GR) Intent private boolean mShowStatusBar; private Canvas drawintoCanvas = null; // ******************************** HTML command variables ******************************** public ArrayList<String> htmlData_Buffer; private boolean htmlOpening; // ******************************* Audio command variables ******************************** private MediaPlayer theMP = null; private ArrayList<MediaPlayer> theMPList; private ArrayList<String> theMPNameList; private boolean PlayIsDone; private MediaRecorder mRecorder = null; // ***************************** SoundPool command variables ****************************** private SoundPool theSoundPool; // *************************** Text-to-Speech command variables *************************** private TextToSpeechActivity theTTS; // ****************************** Vibrate command variables ******************************* private Vibrator myVib; // ******************************* Phone command variables ******************************** public int phoneState = 0; public String phoneNumber = ""; public boolean phoneRcvInited = false; public TelephonyManager mTM; public SignalStrength mSignalStrength = null; // ******************************** SMS command variables ********************************* private ArrayList<String> smsRcvBuffer; // ******************************* Timer command variables ******************************** public Timer theTimer; // ***************************** TimeZone commands variables ****************************** public String theTimeZone = ""; // ******************************** Run command variables ********************************* private AutoRun mAutoRun; // used to load the new program // ************************ Superuser and System command variables ************************ private boolean mIsSU = true; // set true for SU commands, false for System commands private DataOutputStream SUoutputStream; private BufferedReader SUinputStream; private Process SUprocess; private ArrayList<String> SU_ReadBuffer; private SUReader theSUReader = null; // ******************************* Debug commands variables ******************************* private boolean Debug = false; private boolean Echo = false; // ****************************** START OF INTERPRETER CODE ******************************* private UncaughtExceptionHandler mDefaultExceptionHandler; private UncaughtExceptionHandler mUncaughtExceptionHandler = new UncaughtExceptionHandler() { private static final String INTERNAL_ERROR = "Internal error! Please notify developer."; public void uncaughtException(Thread thread, Throwable ex) { if (ex instanceof OutOfMemoryError) { handleHere("Out of memory"); } else if (ex instanceof NullPointerException) { PrintShow(INTERNAL_ERROR, Log.getStackTraceString(ex)); handleHere("Null pointer exception"); } else if (ex instanceof InvalidParameterException) { PrintShow(INTERNAL_ERROR, Log.getStackTraceString(ex)); handleHere("Invalid parameter exception"); } else { Log.e(LOGTAG, Log.getStackTraceString(ex)); mDefaultExceptionHandler.uncaughtException(thread, ex); } } private void handleHere(String err) { PrintShow(err + ", near line:", ExecutingLineBuffer.text()); SyntaxError = true; // This blocks "Program ended" checks in finishRun() mOnErrorInt = null; // Don't allow OnError to catch fatal exception disableInterrupt(Interrupt.ERROR_BIT); finishRun(); } }; @Override public void run() { InitVars(); // Basic.Echo = Settings.getEcho(getApplicationContext()); Echo = false; fnRTN = false; setVolumeControlStream(AudioManager.STREAM_MUSIC); mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(mUncaughtExceptionHandler); boolean ok = PreScan(); // The execution starts by scanning the source for labels and read.data if (!ok) { sendMessage(MESSAGE_UPDATE_CONSOLE); // PreScan found error or duplicate label } else { ExecutingLineIndex = 0; // just in case PreScan ever changes it ok = RunLoop(); // run the program in the interpreter } finishRun(); if (ok && mAutoRun != null) { // program executed a RUN command Intent runIntent = mAutoRun.load(); Run.this.startActivity(runIntent); // start new Run Exit = true; // and force this Run to finish } if (Exit) { finish(); // stop the Run Activity, too } } private void finishRun() { // Called from run() when done running, and from UncaughtExceptionHandler Stop = true; // If Stop is not already set, set it so that menu code can display the right thing PrintLine = ""; // Clear the Print Line buffer PrintLineReady = false; textPrintLine = ""; disableInterrupt(Interrupt.BACK_KEY_BIT); // For debugging loop errors: if debug is on and errors are not trapped // and there is no other error and no immediate exit, then check the loop stacks. if (Debug && (mOnErrorInt == null) && !SyntaxError && !Exit) { if (!ForNextStack.empty()) { PrintShow("Program ended with FOR without NEXT"); } if (!WhileStack.empty()) { PrintShow("Program ended with WHILE without REPEAT"); } if (!DoStack.empty()) { PrintShow("Program ended with DO without UNTIL"); } } if (mConsoleBuffer.size() != 0) { // somebody changed the console sendMessage(MESSAGE_UPDATE_CONSOLE); } cleanUp(); } // Scan the entire program. Find all the labels and read.data statements. // Must set ExecutingLineBuffer for use by called functions, but it will be reloaded when // RunLoop() starts. At present nobody downstream needs to have ExecutingLineIndex set. private boolean PreScan() { final String READ_DATA = BKW_READ_GROUP + BKW_READ_DATA; // "read.data" command keyword for (int LineNumber = 0; LineNumber < Basic.lines.size(); ++LineNumber) { ExecutingLineBuffer = Basic.lines.get(LineNumber); // scan one line at a time // ExecutingLineIndex = LineNumber; String text = ExecutingLineBuffer.text(); int li = text.indexOf(":"); // fast check if ((li <= 0) && (text.charAt(0) != 'r')) { continue; } // not label or READ.DATA, next line String word = getWord(text, 0, ""); LineIndex = word.length(); if (isNext(':')) { // if word really is a label, store it if (Labels.put(word, LineNumber) != null) { // if duplicate label Stop = true; // non-recoverable error return RunTimeError("Duplicate label"); } ExecutingLineBuffer.cmd(CMD_LABEL, LineIndex); if (!checkEOL()) { return false; } } else if (text.startsWith(READ_DATA)) { // Is not a label. If it is READ.DATA LineIndex = READ_DATA.length(); // set LineIndex just past READ.DATA ExecutingLineBuffer.cmd(CMD_READ_DATA, LineIndex); // store the command reference if (!executeREAD_DATA()) { return false; } // parse and store the data list if (!checkEOL()) { return false; } } } getInterruptLabels(); return true; } private void getInterruptLabels() { // check for interrupt labels mOnErrorInt = // OnError: gets a named Interrupt object getInterruptLabel(BKW_ONERROR, Interrupt.ERROR_BIT); getInterruptLabel(BKW_ONBACKKEY, Interrupt.BACK_KEY_BIT); // the rest can be anonymous getInterruptLabel(BKW_ONMENUKEY, Interrupt.MENU_KEY_BIT); getInterruptLabel(BKW_ONTIMER, Interrupt.TIMER_BIT); getInterruptLabel(BKW_ONKEYPRESS, Interrupt.KEY_BIT); getInterruptLabel(BKW_ONGRTOUCH, Interrupt.GR_TOUCH_BIT); getInterruptLabel(BKW_ONCONSOLETOUCH, Interrupt.CONS_TOUCH_BIT); getInterruptLabel(BKW_ONBTREADREADY, Interrupt.BT_READY_BIT); getInterruptLabel(BKW_ONBACKGROUND, Interrupt.BACKGROUND_BIT); getInterruptLabel(BKW_ONKBCHANGE, Interrupt.KB_CHANGE_BIT); getInterruptLabel(BKW_ONLOWMEM, Interrupt.LOW_MEM_BIT); } private Interrupt getInterruptLabel(String label, Integer bit) { Interrupt intrpt = null; Integer line = Labels.get(chomp(label)); // remove colon from input string if (line != null) { // if this interrupt's label exists intrpt = new Interrupt(line.intValue()); mIntA.put(bit, intrpt); // "arm" the interrupt } return intrpt; } // The RunLoop() drives the execution of the program. It is called from doInBackground and // recursively from doUserFunction. public boolean RunLoop() { boolean flag = true; while (ExecutingLineIndex < Basic.lines.size() && flag && !Stop) { // keep executing statements until end ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); // next program line // Log.d(LOGTAG, "RunLoop: " + ExecutingLineBuffer.line()); LineIndex = 0; flag = StatementExecuter(); // execute the next statement // returns true if no problems executing statement if (Exit) break; // if Exit skip all other processing if (!flag) { // error supersedes all other interrupt checking if (mOnErrorInt != null) { // if there is an OnError label ExecutingLineIndex = mOnErrorInt.line(); // go to the OnError line SyntaxError = false; // and clear the error status flag = true; } } else { readEventList(); // get events detected by other activities serviceInterrupt(); // sets ExecutingLineIndex if an interrupt triggered } if (mConsoleBuffer.size() != 0) { // somebody changed the console sendMessage(MESSAGE_UPDATE_CONSOLE); } if (fnRTN) { // fn_rtn signal fnRTN = false; // make RunLoop() return to doUserFunction break; } // Debugger control // Michael/paulon0n also defined a signal for "Alert dialog var not set called". TODO? while (WaitForDebugResume && flag && !Stop) { mWaitForDebugLock = true; sendMessage(MESSAGE_DEBUG_DIALOG); // signal UI to show debugger dialog waitForDebugLOCK(); // wait for user's selection if (DebuggerHalt) { PrintShow("Execution halted"); Stop = true; break; } if (DebuggerStep) { DebuggerStep = false; break; // let the next command run } if (dbSwap) { WaitForSwap = true; sendMessage(MESSAGE_DEBUG_SWAP); while (WaitForSwap) { Thread.yield(); /* if(dbSelect) { dbSelect = false; WaitForSelect = true; sendMessage(MESSAGE_DEBUG_SELECT); while (WaitForSelect) { Thread.yield(); } } */ } dbSwap = false; } } ++ExecutingLineIndex; // step to next line } return flag; } // end RunLoop() private boolean StatementExecuter() { // Execute one basic line (statement) Command c = ExecutingLineBuffer.findCommand(BASIC_cmd, LineIndex); if (c == null) { c = CMD_IMPLICIT; // no keyword, assume pseudo LET or CALL ExecutingLineBuffer.cmd(c); // and remember it } else { LineIndex += ExecutingLineBuffer.offset(); // skip keyword length } if (!IfElseStack.empty()) { // if inside IF-ELSE-ENDIF Integer q = IfElseStack.peek(); // decide if we should skip to ELSE or ENDIF if ((q == IEskip1) && (c.id != CID_SKIP_TO_ELSE) && (c.id != CID_SKIP_TO_ENDIF)) { return true; // skip unless IF, ELSEIF, ELSE, or ENDIF } else if ((q == IEskip2) && (c.id != CID_SKIP_TO_ENDIF)) { return true; // skip unless IF or ENDIF } } if (Echo) { String text = ExecutingLineBuffer.text(); PrintShow(text.substring(0, text.length() - 1)); } if (!c.run()) { SyntaxError(); return false; } return true; // Statement executed ok. Return to main looper. } // StatementExecuter() private void readEventList() { synchronized (mEventList) { while (mEventList.size() != 0) { EventHolder e = mEventList.remove(0); if (e != null) { switch (e.mType) { case EventHolder.KEY_DOWN: onKeyDown(e.mCode, e.mEvent); break; case EventHolder.KEY_UP: onKeyUp(e.mCode, e.mEvent); break; case EventHolder.GR_BACK_KEY_PRESSED: GR_BackPressed(); break; case EventHolder.BACK_KEY_PRESSED: onBackPressed(); break; case EventHolder.GR_KB_CHANGED: triggerInterrupt(Interrupt.KB_CHANGE_BIT); break; case EventHolder.GR_TOUCH: triggerInterrupt(Interrupt.GR_TOUCH_BIT); break; case EventHolder.GR_STATE: grStateChange(e.mCode); break; case EventHolder.WEB_STATE: webStateChange(e.mCode); break; case EventHolder.DATALINK_ADD: addDataLink(e.mStringData); break; default: break; } } } } } // readEventList() private boolean triggerInterrupt(Integer bit) { Interrupt intrpt = mIntA.get(bit); if (intrpt == null) return false; synchronized (mIntT) { mIntT.put(bit, intrpt); } return true; } private void serviceInterrupt() { if (interruptResume != -1) return; // if servicing another interrupt, do not service another now if (mIntT.size() != 0) { // if there are triggered interrupts Interrupt intrpt = null; // service the oldest synchronized (mIntT) { Iterator<Integer> it = mIntT.keySet().iterator(); while (it.hasNext()) { Integer key = it.next(); if (key == null) continue; // huh? should not happen intrpt = mIntT.remove(key); if (intrpt == null) continue; // huh? should not happen break; } } if (intrpt != null) { interruptResume = ExecutingLineIndex; // Set the resume Line Number ExecutingLineIndex = intrpt.line(); // Set the ISR line number IfElseStack.push(IEinterrupt); } } } // serviceInterrupt() private void disableInterrupt(Integer bit) { // permanently disable an interrupt if (mIntA.remove(bit) != null) { // remove from "armed" list mIntT.remove(bit); // and from triggered list } } private boolean doResume(String errMsg) { if (interruptResume == -1) { return RunTimeError(errMsg); } ExecutingLineIndex = interruptResume; interruptResume = -1; // Pull the IEinterrupt from the If Else stack // It is possible that IFs were executed in the interrupt code // so pop entries until we get to the IEinterrupt while (IfElseStack.peek() != IEinterrupt) { IfElseStack.pop(); } IfElseStack.pop(); // Top of stack is now IEInterrupt, pop it return true; } private void grStateChange(int newState) { switch (newState) { case EventHolder.ON_PAUSE: GRFront = false; break; case EventHolder.ON_RESUME: GRFront = true; break; default: return; } triggerInterrupt(Interrupt.BACKGROUND_BIT); } private void webStateChange(int newState) { switch (newState) { case EventHolder.ON_PAUSE: mWebFront = false; break; case EventHolder.ON_RESUME: mWebFront = true; break; default: return; } triggerInterrupt(Interrupt.BACKGROUND_BIT); } private void InitVars() { Log.d(LOGTAG, "InitVars() started"); // Owned by Run class mInterpreterRunning = true; RunPaused = false; Stop = false; // Stops program from running Exit = false; // Exits program and signals caller to exit, too mEventList.clear(); // events from other Activities ConsoleLongTouch = false; TouchedConsoleLine = 0; // first valid line number is 1 interruptResume = -1; SyntaxError = false; // Set true when Syntax Error message has been output mMessagePending = false; // If true, may be messages pending // debugger dialog and ui vars WatchedVars = new ArrayList<Var>(); // list of watched variables dbDialogScalars = false; dbDialogArray = false; dbDialogList = false; dbDialogStack = false; dbDialogBundle = false; dbDialogWatch = false; dbDialogProgram = true; dbConsoleHistory = ""; dbConsoleExecute = ""; dbConsoleELBI = 0; WatchedArray = null; WatchedList = -1; WatchedStack = -1; WatchedBundle = -1; dbSwap = false; dbDialog = null; dbSwapDialog = null; dbSelectDialog = null; // end debugger ui vars Labels = new HashMap<String, Integer>(); // A list of all labels and associated line numbers InChar = new ArrayList<String>(); TextInputString = ""; GRopen = false; // Graphics Open Flag GRFront = false; DisplayList = new ArrayList<GR.BDraw>(); RealDisplayList = new ArrayList<Integer>(); PaintList = new ArrayList<Paint>(); BitmapList = new ArrayList<Bitmap>(); initTouchVars(); theSensors = null; theGPS = null; clientSocketState = STATE_NONE; serverSocketState = STATE_NONE; theClientSocket = null; clientSocketConnectThread = null; ClientBufferedReader = null; ClientPrintWriter = null; newSS = null; serverSocketConnectThread = null; theServerSocket = null; ServerBufferedReader = null; ServerPrintWriter = null; mFTPClient = null; FTPdir = null; CameraBitmap = null; CameraDone = true; NumberOfCameras = -1; bt_enabled = 0; mConnectedDeviceName = null; mOutStringBuffer = null; mChatService = null; BT_Read_Buffer.clear(); headsetState = -1; headsetName = "NA"; headsetMic = -1; htmlIntent = null; sttDefaultPrompt = getString(R.string.stt_prompt); sttPrompt = null; sttListening = false; // Owned by Interpreter class // allocate objects ExecutingLineBuffer = new ProgramLine("\n"); // Holds the current line being executed GosubStack = new Stack<Integer>(); // Stack used for Gosub/Return ForNextStack = new Stack<ForNext>(); // Stack used for For/Next WhileStack = new Stack<WhileRepeat>(); // Stack used for While/Repeat DoStack = new Stack<Integer>(); // Stack used for Do/Until IfElseStack = new Stack<Integer>(); // Stack for IF-ELSE-ENDIF operations FunctionStack = new Stack<CallStackFrame>(); // State saved through the currently executing functions mFunctionTable = new HashMap<String, Var.FnDef>(); // Globally-accessible table of user-defined functions mGlobalSymbolTable = new Var.Table(); // The symbol table that contains all global Vars mSymbolTable = new Var.Table(); // The current symbol table, all non-global Vars in scope mSymbolTables = new Stack<Var.Table>(); // List of all local symbol tables mSymbolTables.push(mSymbolTable); // Initialize the parser's working Var objects. // These are used to avoid creating a new Var every time a variable is parsed. mScalarVar = new Var.ScalarVar("", Var.Type.NOVAR); mArrayVar = new Var.ArrayVar("", Var.Type.NOVAR); mFunctionVar = new Var.FunctionVar("", Var.Type.NOVAR); mErrorMsg = NO_ERROR; theLists = new ArrayList<ArrayList>(); theLists.add(new ArrayList<ArrayList>()); theListsType = new ArrayList<Var.Type>(); theListsType.add(Var.Type.NOVAR); theBundles = new ArrayList<Bundle>(); theBundles.add(new Bundle()); theStacks = new ArrayList<Stack>(); theStacks.add(new Stack()); theStacksType = new ArrayList<Var.Type>(); theStacksType.add(Var.Type.NOVAR); readData = new ArrayList<Var.Val>(); FileTable = new ArrayList<FileInfo>(); // File table list DataBases = new ArrayList<SQLiteDatabase>(); // List of created data bases Cursors = new ArrayList<Cursor>(); // List of database cursors FontList = new ArrayList<Typeface>(); clearFontList(); theMPList = new ArrayList<MediaPlayer>(); theMPNameList = new ArrayList<String>(); theMPList.add(null); // We don't use the [0] element of these Lists theMPNameList.add(null); Log.d(LOGTAG, "InitVars() done"); } // end InitVars public void cleanUp() { Log.d(LOGTAG, "cleanUp() started"); mEventList.clear(); // events from other Activities mIntT.clear(); // clear all pending interrupts mIntA.clear(); // disable all interrupts if ((lv != null) && (lv.mKB != null)) { lv.mKB.release(); lv.mKB = null; } if ((GR.drawView != null) && (GR.drawView.mKB != null)) { GR.drawView.mKB.release(); GR.drawView.mKB = null; } if (theMP != null) { try { theMP.stop(); } catch (IllegalStateException e) { } theMP.release(); theMP = null; } if (theSoundPool != null) { theSoundPool.release(); theSoundPool = null; } if (Web.aWebView != null) { Web.aWebView.webClose(); } if (htmlData_Buffer != null) { htmlData_Buffer.clear(); } cancelTimer(); ttsStop(); cancelVibrator(); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll(); if (theMP != null) { theMP.release(); theMP = null; } if (theMPList != null) { for (MediaPlayer mp : theMPList) { if (mp != null) { mp.release(); } } theMPList = null; theMPNameList = null; } if (theSensors != null) { theSensors.stop(); theSensors = null; } if (theServerSocket != null) { try { theServerSocket.close(); } catch (Exception e) { } theServerSocket = null; } if (clientSocketConnectThread != null) { clientSocketConnectThread.interrupt(); clientSocketConnectThread = null; } if (serverSocketConnectThread != null) { serverSocketConnectThread.interrupt(); serverSocketConnectThread = null; } if (newSS != null) { try { newSS.close(); } catch (Exception e) { } newSS = null; } if (theClientSocket != null) { try { theClientSocket.close(); } catch (Exception e) { } theClientSocket = null; } clientSocketState = STATE_NONE; serverSocketState = STATE_NONE; audioRecordStop(); Stop = true; // make sure the interpreter thread stops Basic.clearContextManager(); mMessagePending = false; if (theGPS != null) { Log.d(LOGTAG, "Stopping GPS from cleanUp"); theGPS.stop(); theGPS = null; } if (!SyntaxError || Basic.DoAutoRun) { // if no error or no Editor Editor.SyntaxErrorDisplacement = -1; // clear error highlighting, if any } BitmapListClear(); if (mChatService != null) { mChatService.stop(); mChatService = null; } if (theSUReader != null) { theSUReader.stop(); theSUReader = null; } if (SUprocess != null) { SUprocess.destroy(); SUprocess = null; } finishActivity(BASIC_GENERAL_INTENT); if (mTM != null) { Log.d(LOGTAG, "mTM: unlistening"); mTM.listen(PSL, PhoneStateListener.LISTEN_NONE); mTM = null; } mSignalStrength = null; mInterpreterRunning = false; Log.d(LOGTAG, "cleanUp() done"); } // end cleanup // ***************************************** UI Locks ***************************************** private void waitForLOCK() { synchronized (LOCK) { // Log.d(LOGTAG, "set LOCK wait"); while (mWaitForLock) { try { LOCK.wait(); } catch (InterruptedException e) { mWaitForLock = false; } } } } private void waitForGrLOCK() { synchronized (GR.LOCK) { // Log.d(LOGTAG, "set GR.LOCK wait"); while (GR.waitForLock) { try { GR.LOCK.wait(); } catch (InterruptedException e) { GR.waitForLock = false; } } } } private void waitForDebugLOCK() { synchronized (DB_LOCK) { // Log.d(LOGTAG, "set DB_LOCK wait"); while (mWaitForDebugLock) { try { DB_LOCK.wait(); } catch (InterruptedException e) { mWaitForDebugLock = false; } } } } // ************************************* Function Tables ************************************** private final Command[] MF_cmd = new Command[] { // Map math function names to their functions new Command(MF_ABS) { public boolean run() { return executeMF_ABS(); } }, new Command(MF_ACOS) { public boolean run() { return executeMF_ACOS(); } }, new Command(MF_ASCII) { public boolean run() { return executeMF_ASCII(); } }, new Command(MF_ASIN) { public boolean run() { return executeMF_ASIN(); } }, new Command(MF_ATAN) { public boolean run() { return executeMF_ATAN(); } }, new Command(MF_ATAN2) { public boolean run() { return executeMF_ATAN2(); } }, new Command(MF_BACKGROUND) { public boolean run() { return executeMF_BACKGROUND(); } }, new Command(MF_BAND) { public boolean run() { return executeMF_BAND(); } }, new Command(MF_BIN) { public boolean run() { return executeMF_base(2); } }, new Command(MF_BNOT) { public boolean run() { return executeMF_BNOT(); } }, new Command(MF_BOR) { public boolean run() { return executeMF_BOR(); } }, new Command(MF_BXOR) { public boolean run() { return executeMF_BXOR(); } }, new Command(MF_CBRT) { public boolean run() { return executeMF_CBRT(); } }, new Command(MF_CEIL) { public boolean run() { return executeMF_CEIL(); } }, new Command(MF_CLOCK) { public boolean run() { return executeMF_CLOCK(); } }, new Command(MF_COS) { public boolean run() { return executeMF_COS(); } }, new Command(MF_COSH) { public boolean run() { return executeMF_COSH(); } }, new Command(MF_ENDS_WITH) { public boolean run() { return executeMF_ENDS_WITH(); } }, new Command(MF_EXP) { public boolean run() { return executeMF_EXP(); } }, new Command(MF_FLOOR) { public boolean run() { return executeMF_FLOOR(); } }, new Command(MF_FRAC) { public boolean run() { return executeMF_FRAC(); } }, new Command(MF_GR_COLLISION) { public boolean run() { return executeMF_GR_COLLISION(); } }, new Command(MF_HEX) { public boolean run() { return executeMF_base(16); } }, new Command(MF_HYPOT) { public boolean run() { return executeMF_HYPOT(); } }, new Command(MF_INT) { public boolean run() { return executeMF_INT(); } }, new Command(MF_IS_IN) { public boolean run() { return executeMF_IS_IN(); } }, new Command(MF_IS_NUMBER) { public boolean run() { return executeMF_IS_NUMBER(); } }, new Command(MF_LEN) { public boolean run() { return executeMF_LEN(); } }, new Command(MF_LOG) { public boolean run() { return executeMF_LOG(); } }, new Command(MF_LOG10) { public boolean run() { return executeMF_LOG10(); } }, new Command(MF_MAX) { public boolean run() { return executeMF_MAX(); } }, new Command(MF_MIN) { public boolean run() { return executeMF_MIN(); } }, new Command(MF_MOD) { public boolean run() { return executeMF_MOD(); } }, new Command(MF_OCT) { public boolean run() { return executeMF_base(8); } }, new Command(MF_PI) { public boolean run() { return executeMF_PI(); } }, new Command(MF_POW) { public boolean run() { return executeMF_POW(); } }, new Command(MF_RANDOMIZE) { public boolean run() { return executeMF_RANDOMIZE(); } }, new Command(MF_RND) { public boolean run() { return executeMF_RND(); } }, new Command(MF_ROUND) { public boolean run() { return executeMF_ROUND(); } }, new Command(MF_SGN) { public boolean run() { return executeMF_SGN(); } }, new Command(MF_SHIFT) { public boolean run() { return executeMF_SHIFT(); } }, new Command(MF_SIN) { public boolean run() { return executeMF_SIN(); } }, new Command(MF_SINH) { public boolean run() { return executeMF_SINH(); } }, new Command(MF_SQR) { public boolean run() { return executeMF_SQR(); } }, new Command(MF_STARTS_WITH) { public boolean run() { return executeMF_STARTS_WITH(); } }, new Command(MF_TAN) { public boolean run() { return executeMF_TAN(); } }, new Command(MF_TIME) { public boolean run() { return executeMF_TIME(); } }, new Command(MF_TODEGREES) { public boolean run() { return executeMF_TODEGREES(); } }, new Command(MF_TORADIANS) { public boolean run() { return executeMF_TORADIANS(); } }, new Command(MF_UCODE) { public boolean run() { return executeMF_UCODE(); } }, new Command(MF_VAL) { public boolean run() { return executeMF_VAL(); } }, }; private final HashMap<String, Command> MF_map = new HashMap<String, Command>(64) { private static final long serialVersionUID = 102L; { for (Command c : MF_cmd) { put(c.name, c); } } }; private final Command[] SF_cmd = new Command[] { // Map string function names to their functions new Command(SF_BIN) { public boolean run() { return executeSF_BIN(); } }, new Command(SF_CHR) { public boolean run() { return executeSF_CHR(); } }, new Command(SF_DECODE) { public boolean run() { return executeSF_ENCODE(DECODE); } }, new Command(SF_ENCODE) { public boolean run() { return executeSF_ENCODE(ENCODE); } }, new Command(SF_FORMAT) { public boolean run() { return executeSF_FORMAT(); } }, new Command(SF_FORMAT_USING) { public boolean run() { return executeSF_USING(); } }, new Command(SF_GETERROR) { public boolean run() { return executeSF_GETERROR(); } }, new Command(SF_HEX) { public boolean run() { return executeSF_HEX(); } }, new Command(SF_INT) { public boolean run() { return executeSF_INT(); } }, new Command(SF_LEFT) { public boolean run() { return executeSF_LEFT(); } }, new Command(SF_LOWER) { public boolean run() { return executeSF_LOWER(); } }, new Command(SF_LTRIM) { public boolean run() { return executeSF_TRIM(TLEFT); } }, new Command(SF_MID) { public boolean run() { return executeSF_MID(); } }, new Command(SF_OCT) { public boolean run() { return executeSF_OCT(); } }, new Command(SF_REPLACE) { public boolean run() { return executeSF_REPLACE(); } }, new Command(SF_RIGHT) { public boolean run() { return executeSF_RIGHT(); } }, new Command(SF_RTRIM) { public boolean run() { return executeSF_TRIM(TRIGHT); } }, new Command(SF_STR) { public boolean run() { return executeSF_STR(); } }, new Command(SF_TRIM) { public boolean run() { return executeSF_TRIM(TLEFT | TRIGHT); } }, new Command(SF_UPPER) { public boolean run() { return executeSF_UPPER(); } }, new Command(SF_USING) { public boolean run() { return executeSF_USING(); } }, new Command(SF_VERSION) { public boolean run() { return executeSF_VERSION(); } }, new Command(SF_WORD) { public boolean run() { return executeSF_WORD(); } }, }; private final HashMap<String, Command> SF_map = new HashMap<String, Command>(64) { private static final long serialVersionUID = 103L; { for (Command c : SF_cmd) { put(c.name, c); } } }; // ************************************** Command Tables ************************************** /* Markers for IF, etc., to facilitate skipping them in StatementExecuter() */ private final int CID_SKIP_TO_ELSE = 1; // Ok to execute when skipping to ELSE or ENDIF private final int CID_SKIP_TO_ENDIF = 2; // Ok to execute when skipping to ENDIF /* Other markers to make special-case handling faster */ private final int CID_GROUP = 3; private final int CID_OPEN = 4; private final int CID_CLOSE = 5; private final int CID_EX = 6; // EXception within its command group private final int CID_READ = 7; private final int CID_WRITE = 8; private final int CID_STATUS = 9; private final int CID_DATALINK = 10; /* Special case: what to do if no command keyword at the beginning of the line. */ private final Command CMD_IMPLICIT = new Command("") { public boolean run() { return executeImplicitCommand(); } }; private final Command CMD_IMPL_LET = new Command("") { public boolean run() { return executeLET(0.0); } }; /* Label: length is variable, but irrelevant. No command name, so DO NOT put this in a searchable command table. */ private final Command CMD_LABEL = new Command("") { public boolean run() { return true; } }; /* Other special cases where we need a named Command and also a command table entry. */ private final Command CMD_CALL = new Command(BKW_CALL) { public boolean run() { return executeCALL(); } }; private final Command CMD_LET = new Command(BKW_LET) { public boolean run() { return executeLET(); } }; private final Command CMD_PREINC = new Command(BKW_PREINC) { public boolean run() { return executeLET(1.0); } }; private final Command CMD_PREDEC = new Command(BKW_PREDEC) { public boolean run() { return executeLET(-1.0); } }; private final Command CMD_FOR = new Command(BKW_FOR) { public boolean run() { return executeFOR(); } }; private final Command CMD_NEXT = new Command(BKW_NEXT) { public boolean run() { return executeNEXT(); } }; private final Command CMD_WHILE = new Command(BKW_WHILE) { public boolean run() { return executeWHILE(); } }; private final Command CMD_REPEAT = new Command(BKW_REPEAT) { public boolean run() { return executeREPEAT(); } }; private final Command CMD_DO = new Command(BKW_DO) { public boolean run() { return executeDO(); } }; private final Command CMD_UNTIL = new Command(BKW_UNTIL) { public boolean run() { return executeUNTIL(); } }; private final Command CMD_SW_GROUP = new Command(BKW_SW_GROUP, CID_GROUP) { public boolean run() { return executeSW(); } }; // Map BASIC! command keywords to their execution functions. // The order of this list determines the order of the linear keyword search, which affects performance. private final Command[] BASIC_cmd = new Command[] { CMD_LET, new Command(BKW_IF, CID_SKIP_TO_ENDIF) { public boolean run() { return executeIF(); } }, new Command(BKW_ENDIF, CID_SKIP_TO_ENDIF) { public boolean run() { return executeENDIF(); } }, new Command(BKW_ELSEIF, CID_SKIP_TO_ELSE) { public boolean run() { return executeELSEIF(); } }, new Command(BKW_ELSE, CID_SKIP_TO_ELSE) { public boolean run() { return executeELSE(); } }, new Command(BKW_PRINT) { public boolean run() { return executePRINT(); } }, new Command(BKW_PRINT_SHORTCUT) { public boolean run() { return executePRINT(); } }, CMD_FOR, CMD_NEXT, CMD_WHILE, CMD_REPEAT, CMD_DO, CMD_UNTIL, new Command(BKW_F_N_BREAK) { public boolean run() { return executeF_N_BREAK(); } }, new Command(BKW_W_R_BREAK) { public boolean run() { return executeW_R_BREAK(); } }, new Command(BKW_D_U_BREAK) { public boolean run() { return executeD_U_BREAK(); } }, new Command(BKW_F_N_CONTINUE) { public boolean run() { return executeF_N_CONTINUE(); } }, new Command(BKW_W_R_CONTINUE) { public boolean run() { return executeW_R_CONTINUE(); } }, new Command(BKW_D_U_CONTINUE) { public boolean run() { return executeD_U_CONTINUE(); } }, CMD_SW_GROUP, new Command(BKW_FN_GROUP, CID_GROUP) { public boolean run() { return executeFN(); } }, CMD_CALL, new Command(BKW_GOTO) { public boolean run() { return executeGOTO(); } }, new Command(BKW_GOSUB) { public boolean run() { return executeGOSUB(); } }, new Command(BKW_RETURN) { public boolean run() { return executeRETURN(); } }, new Command(BKW_GR_GROUP, CID_GROUP) { public boolean run() { return executeGR(); } }, new Command(BKW_DIM) { public boolean run() { return executeDIM(); } }, new Command(BKW_UNDIM) { public boolean run() { return executeUNDIM(); } }, new Command(BKW_ARRAY_GROUP, CID_GROUP) { public boolean run() { return executeARRAY(); } }, new Command(BKW_BUNDLE_GROUP, CID_GROUP) { public boolean run() { return executeBUNDLE(); } }, new Command(BKW_LIST_GROUP, CID_GROUP) { public boolean run() { return executeLIST(); } }, new Command(BKW_STACK_GROUP, CID_GROUP) { public boolean run() { return executeSTACK(); } }, CMD_PREINC, CMD_PREDEC, new Command(BKW_INKEY) { public boolean run() { return executeINKEY(); } }, new Command(BKW_INPUT) { public boolean run() { return executeINPUT(); } }, new Command(BKW_DIALOG_GROUP, CID_GROUP) { public boolean run() { return executeDIALOG(); } }, new Command(BKW_SELECT) { public boolean run() { return executeSELECT(); } }, new Command(BKW_TGET) { public boolean run() { return executeTGET(); } }, new Command(BKW_FILE_GROUP, CID_GROUP) { public boolean run() { return executeFILE(); } }, new Command(BKW_TEXT_GROUP, CID_GROUP) { public boolean run() { return executeTEXT(); } }, new Command(BKW_BYTE_GROUP, CID_GROUP) { public boolean run() { return executeBYTE(); } }, new Command(BKW_READ_GROUP, CID_GROUP) { public boolean run() { return executeREAD(); } }, new Command(BKW_DIR) { public boolean run() { return executeDIR(); } }, new Command(BKW_MKDIR) { public boolean run() { return executeMKDIR(); } }, new Command(BKW_RENAME) { public boolean run() { return executeRENAME(); } }, new Command(BKW_GRABFILE) { public boolean run() { return executeGRABFILE(); } }, new Command(BKW_GRABURL) { public boolean run() { return executeGRABURL(); } }, new Command(BKW_BROWSE) { public boolean run() { return executeBROWSE(); } }, new Command(BKW_BT_GROUP, CID_GROUP) { public boolean run() { return executeBT(); } }, new Command(BKW_FTP_GROUP, CID_GROUP) { public boolean run() { return executeFTP(); } }, new Command(BKW_HTML_GROUP, CID_GROUP) { public boolean run() { return executeHTML(); } }, new Command(BKW_HTTP_POST) { public boolean run() { return executeHTTP_POST(); } }, new Command(BKW_SOCKET_GROUP, CID_GROUP) { public boolean run() { return executeSOCKET(); } }, new Command(BKW_SQL_GROUP, CID_GROUP) { public boolean run() { return executeSQL(); } }, new Command(BKW_GPS_GROUP, CID_GROUP) { public boolean run() { return executeGPS(); } }, new Command(BKW_POPUP) { public boolean run() { return executePOPUP(); } }, new Command(BKW_SENSORS_GROUP, CID_GROUP) { public boolean run() { return executeSENSORS(); } }, new Command(BKW_AUDIO_GROUP, CID_GROUP) { public boolean run() { return executeAUDIO(); } }, new Command(BKW_SOUNDPOOL_GROUP, CID_GROUP) { public boolean run() { return executeSOUNDPOOL(); } }, new Command(BKW_RINGER_GROUP, CID_GROUP) { public boolean run() { return executeRINGER(); } }, new Command(BKW_TONE) { public boolean run() { return executeTONE(); } }, new Command(BKW_CLIPBOARD_GET) { public boolean run() { return executeCLIPBOARD_GET(); } }, new Command(BKW_CLIPBOARD_PUT) { public boolean run() { return executeCLIPBOARD_PUT(); } }, new Command(BKW_ENCRYPT) { public boolean run() { return executeENCRYPT(ENCODE); } }, new Command(BKW_DECRYPT) { public boolean run() { return executeENCRYPT(DECODE); } }, new Command(BKW_SWAP) { public boolean run() { return executeSWAP(); } }, new Command(BKW_SPLIT_ALL) { public boolean run() { return executeSPLIT(-1); } }, new Command(BKW_SPLIT) { public boolean run() { return executeSPLIT(0); } }, new Command(BKW_JOIN_ALL) { public boolean run() { return executeJOIN(true); } }, new Command(BKW_JOIN) { public boolean run() { return executeJOIN(false); } }, new Command(BKW_CLS) { public boolean run() { return executeCLS(); } }, new Command(BKW_FONT_GROUP, CID_GROUP) { public boolean run() { return executeFONT(); } }, new Command(BKW_CONSOLE_GROUP, CID_GROUP) { public boolean run() { return executeCONSOLE(); } }, new Command(BKW_DEBUG_GROUP, CID_GROUP) { public boolean run() { return executeDEBUG(); } }, new Command(BKW_ECHO_GROUP, CID_GROUP) { public boolean run() { return executeECHO(); } }, new Command(BKW_KB_GROUP, CID_GROUP) { public boolean run() { return executeKB(); } }, new Command(BKW_NOTIFY) { public boolean run() { return executeNOTIFY(); } }, new Command(BKW_RUN) { public boolean run() { return executeRUN(); } }, new Command(BKW_EMPTY_PROGRAM) { public boolean run() { return executeEMPTY_PROGRAM(); } }, new Command(BKW_SU_GROUP, CID_GROUP) { public boolean run() { return executeSU(true); } }, new Command(BKW_SYSTEM_GROUP, CID_GROUP) { public boolean run() { return executeSU(false); } }, new Command(BKW_STT_LISTEN) { public boolean run() { return executeSTT_LISTEN(); } }, new Command(BKW_STT_RESULTS) { public boolean run() { return executeSTT_RESULTS(); } }, new Command(BKW_TTS_GROUP, CID_GROUP) { public boolean run() { return executeTTS(); } }, new Command(BKW_TIMER_GROUP, CID_GROUP) { public boolean run() { return executeTIMER(); } }, new Command(BKW_TIMEZONE_GROUP, CID_GROUP) { public boolean run() { return executeTIMEZONE(); } }, new Command(BKW_TIME) { public boolean run() { return executeTIME(); } }, new Command(BKW_VIBRATE) { public boolean run() { return executeVIBRATE(); } }, new Command(BKW_WAKELOCK) { public boolean run() { return executeWAKELOCK(); } }, new Command(BKW_WIFILOCK) { public boolean run() { return executeWIFILOCK(); } }, new Command(BKW_END) { public boolean run() { return executeEND(); } }, new Command(BKW_EXIT) { public boolean run() { Stop = Exit = true; return true; } }, new Command(BKW_HOME) { public boolean run() { return executeHOME(); } }, new Command(BKW_INCLUDE) { public boolean run() { return true; } }, new Command(BKW_PAUSE) { public boolean run() { return executePAUSE(); } }, new Command(BKW_REM) { public boolean run() { return true; } }, new Command(BKW_DEVICE) { public boolean run() { return executeDEVICE(); } }, new Command(BKW_SCREEN_GROUP, CID_GROUP) { public boolean run() { return executeSCREEN(); } }, new Command(BKW_WIFI_INFO) { public boolean run() { return executeWIFI_INFO(); } }, new Command(BKW_HEADSET) { public boolean run() { return executeHEADSET(); } }, new Command(BKW_MYPHONENUMBER) { public boolean run() { return executeMYPHONENUMBER(); } }, new Command(BKW_EMAIL_SEND) { public boolean run() { return executeEMAIL_SEND(); } }, new Command(BKW_PHONE_GROUP, CID_GROUP) { public boolean run() { return executePHONE(); } }, new Command(BKW_SMS_GROUP, CID_GROUP) { public boolean run() { return executeSMS(); } }, new Command(BKW_VOLKEYS_GROUP, CID_GROUP) { public boolean run() { return executeVOLKEYS(); } }, new Command(BKW_APP_GROUP, CID_GROUP) { public boolean run() { return executeAPP(); } }, new Command(BKW_BACK_RESUME) { public boolean run() { return executeBACK_RESUME(); } }, new Command(BKW_BACKGROUND_RESUME) { public boolean run() { return executeBACKGROUND_RESUME(); } }, new Command(BKW_CONSOLETOUCH_RESUME) { public boolean run() { return executeCONSOLETOUCH_RESUME(); } }, new Command(BKW_KEY_RESUME) { public boolean run() { return executeKEY_RESUME(); } }, new Command(BKW_LOWMEM_RESUME) { public boolean run() { return executeLOWMEM_RESUME(); } }, new Command(BKW_MENUKEY_RESUME) { public boolean run() { return executeMENUKEY_RESUME(); } } }; // BASIC_cmd // **************** FN Group - user-defined functions private final Command[] fn_cmd = new Command[] { // Map user function command keywords to their execution functions new Command(BKW_FN_DEF) { public boolean run() { return executeFN_DEF(); } }, new Command(BKW_FN_RTN) { public boolean run() { return executeFN_RTN(); } }, new Command(BKW_END) { public boolean run() { return executeFN_END(); } }, }; // **************** SW Group - switch statements private final Command CMD_SW_BEGIN = new Command(BKW_SW_BEGIN) { public boolean run() { return executeSW_BEGIN(); } }; private final Command CMD_SW_CASE = new Command(BKW_SW_CASE) { public boolean run() { return executeSW_CASE(); } }; private final Command CMD_SW_BREAK = new Command(BKW_SW_BREAK) { public boolean run() { return executeSW_BREAK(); } }; private final Command CMD_SW_DEFAULT = new Command(BKW_SW_DEFAULT) { public boolean run() { return executeSW_DEFAULT(); } }; private final Command CMD_SW_END = new Command(BKW_END) { public boolean run() { return executeSW_END(); } }; private final Command[] sw_cmd = new Command[] { // Map sw (switch) command keywords to their execution functions CMD_SW_BEGIN, CMD_SW_CASE, CMD_SW_BREAK, CMD_SW_DEFAULT, CMD_SW_END }; // **************** FILE Group private final Command[] file_cmd = new Command[] { // Map File command keywords to their execution functions new Command(BKW_FILE_DELETE) { public boolean run() { return executeDELETE(); } }, new Command(BKW_FILE_DIR) { public boolean run() { return executeDIR(); } }, new Command(BKW_FILE_EXISTS) { public boolean run() { return executeFILE_EXISTS(); } }, new Command(BKW_FILE_MKDIR) { public boolean run() { return executeMKDIR(); } }, new Command(BKW_FILE_RENAME) { public boolean run() { return executeRENAME(); } }, new Command(BKW_FILE_ROOT) { public boolean run() { return executeFILE_ROOT(); } }, new Command(BKW_FILE_SIZE) { public boolean run() { return executeFILE_SIZE(); } }, new Command(BKW_FILE_TYPE) { public boolean run() { return executeFILE_TYPE(); } } }; // **************** TEXT Group - text file operations private final Command[] text_cmd = new Command[] { // Map Text I/O command keywords to their execution functions new Command(BKW_OPEN, CID_EX) { public boolean run() { return executeTEXT_OPEN(); } }, new Command(BKW_CLOSE, CID_EX) { public boolean run() { return executeCLOSE(FileType.FILE_TEXT); } }, new Command(BKW_TEXT_READLN, CID_READ) { public boolean run(int fId) { return executeTEXT_READLN(fId); } }, new Command(BKW_TEXT_WRITELN, CID_WRITE) { public boolean run(int fId) { return executeTEXT_WRITELN(fId); } }, new Command(BKW_EOF, CID_WRITE) { public boolean run(int fId) { return executeEOF(fId, FileType.FILE_TEXT); } }, new Command(BKW_TEXT_INPUT, CID_EX) { public boolean run() { return executeTEXT_INPUT(); } }, new Command(BKW_POSITION_GET, CID_READ) { public boolean run(int fId) { return executePOSITION_GET(fId, FileType.FILE_TEXT); } }, new Command(BKW_POSITION_SET, CID_READ) { public boolean run(int fId) { return executeTEXT_POSITION_SET(fId); } }, new Command(BKW_POSITION_MARK, CID_EX) { public boolean run() { return executePOSITION_MARK(FileType.FILE_TEXT); } }, }; // **************** BYTE Group - binary file operations private final Command[] byte_cmd = new Command[] { // Map Byte I/O command keywords to their execution functions new Command(BKW_OPEN, CID_EX) { public boolean run() { return executeBYTE_OPEN(); } }, new Command(BKW_CLOSE, CID_EX) { public boolean run() { return executeCLOSE(FileType.FILE_BYTE); } }, new Command(BKW_BYTE_READ_BYTE, CID_READ) { public boolean run(int fId) { return executeBYTE_READ_BYTE(fId); } }, new Command(BKW_BYTE_WRITE_BYTE, CID_WRITE) { public boolean run(int fId) { return executeBYTE_WRITE_BYTE(fId); } }, new Command(BKW_BYTE_READ_BUFFER, CID_READ) { public boolean run(int fId) { return executeBYTE_READ_BUFFER(fId); } }, new Command(BKW_BYTE_WRITE_BUFFER, CID_WRITE) { public boolean run(int fId) { return executeBYTE_WRITE_BUFFER(fId); } }, new Command(BKW_BYTE_READ_NUMBER) { public boolean run(int fId) { return executeBYTE_READ_NUMBER(fId); } }, new Command(BKW_BYTE_WRITE_NUMBER) { public boolean run(int fId) { return executeBYTE_WRITE_NUMBER(fId); } }, new Command(BKW_EOF, CID_WRITE) { public boolean run(int fId) { return executeEOF(fId, FileType.FILE_BYTE); } }, new Command(BKW_BYTE_COPY, CID_READ) { public boolean run(int fId) { return executeBYTE_COPY(fId); } }, new Command(BKW_BYTE_TRUNCATE, CID_WRITE) { public boolean run(int fId) { return executeBYTE_TRUNCATE(fId); } }, new Command(BKW_POSITION_GET, CID_READ) { public boolean run(int fId) { return executePOSITION_GET(fId, FileType.FILE_BYTE); } }, new Command(BKW_POSITION_SET, CID_READ) { public boolean run(int fId) { return executeBYTE_POSITION_SET(fId); } }, new Command(BKW_POSITION_MARK, CID_EX) { public boolean run() { return executePOSITION_MARK(FileType.FILE_BYTE); } }, }; // **************** READ Group - READ.DATA private final Command CMD_READ_DATA = new Command(BKW_READ_DATA) { public boolean run() { return true; } }; private final Command[] read_cmd = new Command[] { // Map Read command keywords to their execution functions // Do NOT call executeREAD_DATA, that was done in PreScan CMD_READ_DATA, new Command(BKW_READ_NEXT) { public boolean run() { return executeREAD_NEXT(); } }, new Command(BKW_READ_FROM) { public boolean run() { return executeREAD_FROM(); } }, }; // **************** SCREEN Group private final Command[] screen_cmd = new Command[] { // Map screen command keywords to their execution functions new Command(BKW_SCREEN_ROTATION) { public boolean run() { return executeSCREEN_ROTATION(); } }, new Command(BKW_SCREEN_SIZE) { public boolean run() { return executeSCREEN_SIZE(); } }, }; // **************** FONT Group private final Command[] font_cmd = new Command[] { // Map font command keywords to their execution functions new Command(BKW_FONT_LOAD) { public boolean run() { return executeFONT_LOAD(); } }, new Command(BKW_FONT_DELETE) { public boolean run() { return executeFONT_DELETE(); } }, new Command(BKW_FONT_CLEAR) { public boolean run() { return executeFONT_CLEAR(); } }, }; // **************** CONSOLE Group private final Command[] Console_cmd = new Command[] { // Map console command keywords to their execution functions new Command(BKW_CONSOLE_FRONT) { public boolean run() { return executeCONSOLE_FRONT(); } }, new Command(BKW_CONSOLE_SAVE) { public boolean run() { return executeCONSOLE_DUMP(); } }, new Command(BKW_CONSOLE_TITLE) { public boolean run() { return executeCONSOLE_TITLE(); } }, new Command(BKW_CONSOLE_LINE_COUNT) { public boolean run() { return executeCONSOLE_LINE_COUNT(); } }, new Command(BKW_CONSOLE_LINE_TEXT) { public boolean run() { return executeCONSOLE_LINE_TEXT(); } }, new Command(BKW_CONSOLE_LINE_TOUCHED) { public boolean run() { return executeCONSOLE_LINE_TOUCHED(); } }, new Command(BKW_CONSOLE_LINE_NEW) { public boolean run() { return executeCONSOLE_LINE_NEW(); } }, new Command(BKW_CONSOLE_LINE_CHAR) { public boolean run() { return executeCONSOLE_LINE_CHAR(); } } }; // **************** DIALOG Group private final Command[] Dialog_cmd = new Command[] { // Map dialog command keywords to their execution functions new Command(BKW_DIALOG_MESSAGE) { public boolean run() { return executeDIALOG_MESSAGE(); } }, new Command(BKW_DIALOG_SELECT) { public boolean run() { return executeDIALOG_SELECT(); } }, }; // **************** KB Group private final Command[] KB_cmd = new Command[] { // Map KB command keywords to their execution functions new Command(BKW_KB_HIDE) { public boolean run() { return executeKB_HIDE(); } }, new Command(BKW_KB_RESUME) { public boolean run() { return executeKB_RESUME(); } }, new Command(BKW_KB_SHOWING) { public boolean run() { return executeKB_SHOWING(); } }, new Command(BKW_KB_SHOW) { public boolean run() { return executeKB_SHOW(); } }, new Command(BKW_KB_TOGGLE) { public boolean run() { return executeKB_TOGGLE(); } }, }; // **************** SQL Group - SQLite database operations private final Command[] SQL_cmd = new Command[] { // Map SQL command keywords to their execution functions new Command(BKW_SQL_OPEN) { public boolean run() { return execute_sql_open(); } }, new Command(BKW_SQL_CLOSE) { public boolean run() { return execute_sql_close(); } }, new Command(BKW_SQL_INSERT) { public boolean run() { return execute_sql_insert(); } }, new Command(BKW_SQL_QUERY_LENGTH) { public boolean run() { return execute_sql_query_length(); } }, new Command(BKW_SQL_QUERY_POSITION) { public boolean run() { return execute_sql_query_position(); } }, new Command(BKW_SQL_QUERY) { public boolean run() { return execute_sql_query(); } }, new Command(BKW_SQL_NEXT) { public boolean run() { return execute_sql_next(); } }, new Command(BKW_SQL_DELETE) { public boolean run() { return execute_sql_delete(); } }, new Command(BKW_SQL_UPDATE) { public boolean run() { return execute_sql_update(); } }, new Command(BKW_SQL_EXEC) { public boolean run() { return execute_sql_exec(); } }, new Command(BKW_SQL_RAW_QUERY) { public boolean run() { return execute_sql_raw_query(); } }, new Command(BKW_SQL_DROP_TABLE) { public boolean run() { return execute_sql_drop_table(); } }, new Command(BKW_SQL_NEW_TABLE) { public boolean run() { return execute_sql_new_table(); } } }; // **************** GR Group - graphics mode commands private final Command[] GR_cmd = new Command[] { // Map GR command keywords to their execution functions new Command(BKW_GR_RENDER) { public boolean run() { return execute_gr_render(); } }, new Command(BKW_GR_MODIFY) { public boolean run() { return execute_gr_modify(); } }, new Command(BKW_GR_MOVE) { public boolean run() { return execute_gr_move(); } }, new Command(BKW_GR_BOUNDED_TOUCH2) { public boolean run() { return execute_gr_bound_touch(1); } }, new Command(BKW_GR_BOUNDED_TOUCH) { public boolean run() { return execute_gr_bound_touch(0); } }, new Command(BKW_GR_TOUCH2) { public boolean run() { return execute_gr_touch(1); } }, new Command(BKW_GR_TOUCH) { public boolean run() { return execute_gr_touch(0); } }, new Command(BKW_GR_BITMAP_GROUP, CID_GROUP) { public boolean run() { return executeGR_BITMAP(); } }, new Command(BKW_GR_CAMERA_GROUP, CID_GROUP) { public boolean run() { return executeGR_CAMERA(); } }, new Command(BKW_GR_GET_GROUP, CID_GROUP) { public boolean run() { return executeGR_GET(); } }, new Command(BKW_GR_GROUP_GROUP, CID_GROUP) { public boolean run() { return executeGR_GROUP(); } }, new Command(BKW_GR_PAINT_GROUP, CID_GROUP) { public boolean run() { return executeGR_PAINT(); } }, new Command(BKW_GR_TEXT_GROUP, CID_GROUP) { public boolean run() { return executeGR_TEXT(); } }, new Command(BKW_GR_ARC) { public boolean run() { return execute_gr_arc(); } }, new Command(BKW_GR_BRIGHTNESS) { public boolean run() { return execute_brightness(); } }, new Command(BKW_GR_CIRCLE) { public boolean run() { return execute_gr_circle(); } }, new Command(BKW_GR_CLIP) { public boolean run() { return execute_gr_clip(); } }, new Command(BKW_GR_CLOSE) { public boolean run() { return execute_gr_close(); } }, new Command(BKW_GR_CLS) { public boolean run() { return execute_gr_cls(); } }, new Command(BKW_GR_COLOR) { public boolean run() { return execute_gr_color(); } }, new Command(BKW_GR_FRONT) { public boolean run() { return execute_gr_front(); } }, new Command(BKW_GR_GETDL) { public boolean run() { return execute_gr_getdl(); } }, new Command(BKW_GR_NEWDL) { public boolean run() { return execute_gr_newdl(); } }, new Command(BKW_GR_GROUP_CMD) { public boolean run() { return execute_gr_group_objs(); } }, new Command(BKW_GR_HIDE) { public boolean run() { return execute_gr_show(GR.VISIBLE.HIDE); } }, new Command(BKW_GR_LINE) { public boolean run() { return execute_gr_line(); } }, new Command(BKW_GR_ONGRTOUCH_RESUME) { public boolean run() { return execute_gr_touch_resume(); } }, new Command(BKW_GR_OPEN, CID_OPEN) { public boolean run() { return execute_gr_open(); } }, new Command(BKW_GR_ORIENTATION) { public boolean run() { return execute_gr_orientation(); } }, new Command(BKW_GR_OVAL) { public boolean run() { return execute_gr_oval(); } }, new Command(BKW_GR_POINT) { public boolean run() { return execute_gr_point(); } }, new Command(BKW_GR_POLY) { public boolean run() { return execute_gr_poly(); } }, new Command(BKW_GR_RECT) { public boolean run() { return execute_gr_rect(); } }, new Command(BKW_GR_ROTATE_END) { public boolean run() { return execute_gr_rotate_end(); } }, new Command(BKW_GR_ROTATE_START) { public boolean run() { return execute_gr_rotate_start(); } }, new Command(BKW_GR_SAVE) { public boolean run() { return execute_gr_save(); } }, new Command(BKW_GR_SCALE) { public boolean run() { return execute_gr_scale(); } }, new Command(BKW_GR_SCREEN_TO_BITMAP) { public boolean run() { return execute_screen_to_bitmap(); } }, new Command(BKW_GR_SCREEN) { public boolean run() { return execute_gr_screen(); } }, new Command(BKW_GR_SET_ANTIALIAS) { public boolean run() { return execute_gr_antialias(); } }, new Command(BKW_GR_SET_PIXELS) { public boolean run() { return execute_gr_set_pixels(); } }, new Command(BKW_GR_SET_STROKE) { public boolean run() { return execute_gr_stroke_width(); } }, new Command(BKW_GR_SHOW_TOGGLE) { public boolean run() { return execute_gr_show(GR.VISIBLE.TOGGLE); } }, new Command(BKW_GR_SHOW) { public boolean run() { return execute_gr_show(GR.VISIBLE.SHOW); } }, new Command(BKW_GR_STATUSBAR_SHOW) { public boolean run() { return execute_statusbar_show(); } }, new Command(BKW_GR_STATUSBAR) { public boolean run() { return execute_gr_statusbar(); } }, }; private final Command[] GrBitmap_cmd = new Command[] { // Map GR.bitmap command keywords to their execution functions new Command(BKW_GR_BITMAP_CREATE) { public boolean run() { return execute_gr_bitmap_create(); } }, new Command(BKW_GR_BITMAP_CROP) { public boolean run() { return execute_gr_bitmap_crop(); } }, new Command(BKW_GR_BITMAP_DELETE) { public boolean run() { return execute_gr_bitmap_delete(); } }, new Command(BKW_GR_BITMAP_DRAWINTO_START) { public boolean run() { return execute_gr_bitmap_drawinto_start(); } }, new Command(BKW_GR_BITMAP_DRAWINTO_END) { public boolean run() { return execute_gr_bitmap_drawinto_end(); } }, new Command(BKW_GR_BITMAP_DRAW) { public boolean run() { return execute_gr_bitmap_draw(); } }, new Command(BKW_GR_BITMAP_FILL) { public boolean run() { return execute_gr_bitmap_fill(); } }, new Command(BKW_GR_BITMAP_LOAD) { public boolean run() { return execute_gr_bitmap_load(); } }, new Command(BKW_GR_BITMAP_SAVE) { public boolean run() { return execute_bitmap_save(); } }, new Command(BKW_GR_BITMAP_SCALE) { public boolean run() { return execute_gr_bitmap_scale(); } }, new Command(BKW_GR_BITMAP_SIZE) { public boolean run() { return execute_gr_bitmap_size(); } }, }; private final Command[] GrCamera_cmd = new Command[] { // Map GR.camera command keywords to their execution functions new Command(BKW_GR_CAMERA_AUTOSHOOT) { public boolean run() { return execute_camera_shoot(CameraView.PICTURE_MODE_AUTO); } }, // new Command(BKW_GR_CAMERA_BLINDSHOOT) { public boolean run() { return execute_camera_shoot(CameraView.PICTURE_MODE_BLIND); } }, new Command(BKW_GR_CAMERA_MANUALSHOOT) { public boolean run() { return execute_camera_shoot(CameraView.PICTURE_MODE_MANUAL); } }, new Command(BKW_GR_CAMERA_SELECT) { public boolean run() { return execute_gr_camera_select(); } }, new Command(BKW_GR_CAMERA_SHOOT) { public boolean run() { return execute_camera_shoot(CameraView.PICTURE_MODE_USE_UI); } }, }; private final Command[] GrGet_cmd = new Command[] { // Map GR.get command keywords to their execution functions new Command(BKW_GR_GET_BMPIXEL) { public boolean run() { return execute_gr_get_bmpixel(); } }, new Command(BKW_GR_GET_PARAMS) { public boolean run() { return execute_gr_get_params(); } }, new Command(BKW_GR_GET_PIXEL) { public boolean run() { return execute_gr_get_pixel(); } }, new Command(BKW_GR_GET_POSITION) { public boolean run() { return execute_gr_get_position(); } }, new Command(BKW_GR_GET_TEXTBOUNDS) { public boolean run() { return execute_gr_get_textbounds(); } }, new Command(BKW_GR_GET_TYPE) { public boolean run() { return execute_gr_get_type(); } }, new Command(BKW_GR_GET_VALUE) { public boolean run() { return execute_gr_get_value(); } }, }; private final Command[] GrGroup_cmd = new Command[] { // Map GR.group command keywords to their execution functions new Command(BKW_GR_GROUP_LIST) { public boolean run() { return execute_gr_group_list(); } }, new Command(BKW_GR_GETDL) { public boolean run() { return execute_gr_group_getdl(); } }, new Command(BKW_GR_NEWDL) { public boolean run() { return execute_gr_group_newdl(); } }, }; private final Command[] GrPaint_cmd = new Command[] { // Map GR.paint command keywords to their execution functions new Command(BKW_GR_PAINT_COPY) { public boolean run() { return execute_gr_paint_copy(); } }, new Command(BKW_GR_PAINT_GET) { public boolean run() { return execute_gr_paint_get(); } }, new Command(BKW_GR_PAINT_RESET) { public boolean run() { return execute_gr_paint_reset(); } }, }; private final Command[] GrText_cmd = new Command[] { // Map GR.text command keywords to their execution functions new Command(BKW_GR_TEXT_ALIGN) { public boolean run() { return execute_gr_text_align(); } }, new Command(BKW_GR_TEXT_BOLD) { public boolean run() { return execute_gr_text_bold(); } }, new Command(BKW_GR_TEXT_DRAW) { public boolean run() { return execute_gr_text_draw(); } }, new Command(BKW_GR_TEXT_HEIGHT) { public boolean run() { return execute_gr_text_height(); } }, new Command(BKW_GR_TEXT_SETFONT) { public boolean run() { return execute_gr_text_setfont(); } }, new Command(BKW_GR_TEXT_SIZE) { public boolean run() { return execute_gr_text_size(); } }, new Command(BKW_GR_TEXT_SKEW) { public boolean run() { return execute_gr_text_skew(); } }, new Command(BKW_GR_TEXT_STRIKE) { public boolean run() { return execute_gr_text_strike(); } }, new Command(BKW_GR_TEXT_TYPEFACE) { public boolean run() { return execute_gr_text_typeface(); } }, new Command(BKW_GR_TEXT_UNDERLINE) { public boolean run() { return execute_gr_text_underline(); } }, new Command(BKW_GR_TEXT_WIDTH) { public boolean run() { return execute_gr_text_width(); } }, }; // **************** AUDIO Group private final Command[] audio_cmd = new Command[] { // Map audio command keywords to their execution functions new Command(BKW_AUDIO_LOAD) { public boolean run() { return execute_audio_load(); } }, new Command(BKW_AUDIO_PLAY) { public boolean run() { return execute_audio_play(); } }, new Command(BKW_AUDIO_LOOP) { public boolean run() { return execute_audio_loop(); } }, new Command(BKW_AUDIO_STOP) { public boolean run() { return execute_audio_stop(); } }, new Command(BKW_AUDIO_VOLUME) { public boolean run() { return execute_audio_volume(); } }, new Command(BKW_AUDIO_POSITION_CURRENT) { public boolean run() { return execute_audio_pcurrent(); } }, new Command(BKW_AUDIO_POSITION_SEEK) { public boolean run() { return execute_audio_pseek(); } }, new Command(BKW_AUDIO_LENGTH) { public boolean run() { return execute_audio_length(); } }, new Command(BKW_AUDIO_RELEASE) { public boolean run() { return execute_audio_release(); } }, new Command(BKW_AUDIO_PAUSE) { public boolean run() { return execute_audio_pause(); } }, new Command(BKW_AUDIO_ISDONE) { public boolean run() { return execute_audio_isdone(); } }, new Command(BKW_AUDIO_RECORD_START) { public boolean run() { return execute_audio_record_start(); } }, new Command(BKW_AUDIO_RECORD_STOP) { public boolean run() { return execute_audio_record_stop(); } }, }; // **************** SENSORS Group private final Command[] sensors_cmd = new Command[] { // Map sensor command keywords to their execution functions new Command(BKW_SENSORS_LIST) { public boolean run() { return execute_sensors_list(); } }, new Command(BKW_SENSORS_OPEN) { public boolean run() { return execute_sensors_open(); } }, new Command(BKW_SENSORS_READ) { public boolean run() { return execute_sensors_read(); } }, new Command(BKW_SENSORS_CLOSE) { public boolean run() { return execute_sensors_close(); } }, new Command(BKW_SENSORS_ROTATE) { public boolean run() { return execute_sensors_rotate(); } }, }; // **************** GPS Group private final Command[] GPS_cmd = new Command[] { // Map GPS command keywords to their execution functions new Command(BKW_GPS_ALTITUDE) { public boolean run() { return execute_gps_num(GpsData.ALTITUDE); } }, new Command(BKW_GPS_LATITUDE) { public boolean run() { return execute_gps_num(GpsData.LATITUDE); } }, new Command(BKW_GPS_LONGITUDE) { public boolean run() { return execute_gps_num(GpsData.LONGITUDE); } }, new Command(BKW_GPS_BEARING) { public boolean run() { return execute_gps_num(GpsData.BEARING); } }, new Command(BKW_GPS_ACCURACY) { public boolean run() { return execute_gps_num(GpsData.ACCURACY); } }, new Command(BKW_GPS_SPEED) { public boolean run() { return execute_gps_num(GpsData.SPEED); } }, new Command(BKW_GPS_PROVIDER) { public boolean run() { return execute_gps_string(GpsData.PROVIDER); } }, new Command(BKW_GPS_SATELLITES) { public boolean run() { return execute_gps_satellites(); } }, new Command(BKW_GPS_TIME) { public boolean run() { return execute_gps_num(GpsData.TIME); } }, new Command(BKW_GPS_LOCATION) { public boolean run() { return execute_gps_location(); } }, new Command(BKW_GPS_STATUS) { public boolean run() { return execute_gps_status(); } }, new Command(BKW_GPS_OPEN, CID_OPEN) { public boolean run() { return execute_gps_open(); } }, new Command(BKW_GPS_CLOSE) { public boolean run() { return execute_gps_close(); } }, }; // **************** ARRAY Group private final Command[] array_cmd = new Command[] { // Map array command keywords to their execution functions new Command(BKW_ARRAY_DELETE) { public boolean run() { return executeUNDIM(); } }, new Command(BKW_ARRAY_DIMS) { public boolean run() { return execute_array_dims(); } }, new Command(BKW_ARRAY_FILL) { public boolean run() { return execute_array_fill(); } }, new Command(BKW_ARRAY_LENGTH) { public boolean run() { return execute_array_length(); } }, new Command(BKW_ARRAY_LOAD) { public boolean run() { return execute_array_load(); } }, new Command(BKW_ARRAY_REVERSE) { public boolean run() { return execute_array_collection(ArrayOrderOps.DoReverse); } }, new Command(BKW_ARRAY_SHUFFLE) { public boolean run() { return execute_array_collection(ArrayOrderOps.DoShuffle); } }, new Command(BKW_ARRAY_SORT) { public boolean run() { return execute_array_collection(ArrayOrderOps.DoSort); } }, new Command(BKW_ARRAY_SUM) { public boolean run() { return execute_array_sum(ArrayMathOps.DoSum); } }, new Command(BKW_ARRAY_AVERAGE) { public boolean run() { return execute_array_sum(ArrayMathOps.DoAverage); } }, new Command(BKW_ARRAY_MIN) { public boolean run() { return execute_array_sum(ArrayMathOps.DoMin); } }, new Command(BKW_ARRAY_MAX) { public boolean run() { return execute_array_sum(ArrayMathOps.DoMax); } }, new Command(BKW_ARRAY_VARIANCE) { public boolean run() { return execute_array_sum(ArrayMathOps.DoVariance); } }, new Command(BKW_ARRAY_STD_DEV) { public boolean run() { return execute_array_sum(ArrayMathOps.DoStdDev); } }, new Command(BKW_ARRAY_COPY) { public boolean run() { return execute_array_copy(); } }, new Command(BKW_ARRAY_SEARCH) { public boolean run() { return execute_array_search(); } }, }; // **************** LIST Group private final Command[] list_cmd = new Command[] { // Map list command keywords to their execution functions new Command(BKW_LIST_CREATE) { public boolean run() { return execute_LIST_NEW(); } }, new Command(BKW_LIST_ADD_LIST) { public boolean run() { return execute_LIST_ADDLIST(); } }, new Command(BKW_LIST_ADD_ARRAY) { public boolean run() { return execute_LIST_ADDARRAY(); } }, new Command(BKW_LIST_ADD) { public boolean run() { return execute_LIST_ADD(); } }, new Command(BKW_LIST_REPLACE) { public boolean run() { return execute_LIST_SET(); } }, new Command(BKW_LIST_TYPE) { public boolean run() { return execute_LIST_GETTYPE(); } }, new Command(BKW_LIST_GET) { public boolean run() { return execute_LIST_GET(); } }, new Command(BKW_LIST_CLEAR) { public boolean run() { return execute_LIST_CLEAR(); } }, new Command(BKW_LIST_REMOVE) { public boolean run() { return execute_LIST_REMOVE(); } }, new Command(BKW_LIST_INSERT) { public boolean run() { return execute_LIST_INSERT(); } }, new Command(BKW_LIST_SIZE) { public boolean run() { return execute_LIST_SIZE(); } }, new Command(BKW_LIST_TOARRAY) { public boolean run() { return execute_LIST_TOARRAY(); } }, new Command(BKW_LIST_SEARCH) { public boolean run() { return execute_LIST_SEARCH(); } }, }; // **************** BUNDLE Group private final Command[] bundle_cmd = new Command[] { // Map bundle command keywords to their execution functions new Command(BKW_BUNDLE_CREATE) { public boolean run() { return execute_BUNDLE_CREATE(); } }, new Command(BKW_BUNDLE_PUT) { public boolean run() { return execute_BUNDLE_PUT(); } }, new Command(BKW_BUNDLE_GET) { public boolean run() { return execute_BUNDLE_GET(); } }, new Command(BKW_BUNDLE_NEXT) { public boolean run() { return execute_BUNDLE_NEXT(); } }, new Command(BKW_BUNDLE_TYPE) { public boolean run() { return execute_BUNDLE_TYPE(); } }, new Command(BKW_BUNDLE_KEYS) { public boolean run() { return execute_BUNDLE_KEYSET(); } }, new Command(BKW_BUNDLE_COPY) { public boolean run() { return execute_BUNDLE_COPY(); } }, new Command(BKW_BUNDLE_CLEAR) { public boolean run() { return execute_BUNDLE_CLEAR(); } }, new Command(BKW_BUNDLE_CONTAIN) { public boolean run() { return execute_BUNDLE_CONTAIN(); } }, new Command(BKW_BUNDLE_REMOVE) { public boolean run() { return execute_BUNDLE_REMOVE(); } }, }; // **************** STACK Group private final Command[] stack_cmd = new Command[] { // Map stack command keywords to their execution functions new Command(BKW_STACK_CREATE) { public boolean run() { return execute_STACK_CREATE(); } }, new Command(BKW_STACK_PUSH) { public boolean run() { return execute_STACK_PUSH(); } }, new Command(BKW_STACK_POP) { public boolean run() { return execute_STACK_POP(); } }, new Command(BKW_STACK_PEEK) { public boolean run() { return execute_STACK_PEEK(); } }, new Command(BKW_STACK_TYPE) { public boolean run() { return execute_STACK_TYPE(); } }, new Command(BKW_STACK_ISEMPTY) { public boolean run() { return execute_STACK_ISEMPTY(); } }, new Command(BKW_STACK_CLEAR) { public boolean run() { return execute_STACK_CLEAR(); } }, }; // **************** SOCKET Group private final Command[] Socket_cmd = new Command[] { // Map Socket command keywords to their execution functions new Command(BKW_SOCKET_CLIENT_GROUP, CID_GROUP) { public boolean run() { return executeSocketClient(); } }, new Command(BKW_SOCKET_SERVER_GROUP, CID_GROUP) { public boolean run() { return executeSocketServer(); } }, new Command(BKW_SOCKET_MYIP) { public boolean run() { return executeMYIP(); } } }; private final Command[] SocketClient_cmd = new Command[] { // Map Socket.client command keywords to their execution functions new Command(BKW_SOCKET_CONNECT) { public boolean run() { return executeCLIENT_CONNECT(); } }, new Command(BKW_SOCKET_STATUS) { public boolean run() { return executeCLIENT_STATUS(); } }, new Command(BKW_SOCKET_READ_READY) { public boolean run() { return executeCLIENT_READ_READY(); } }, new Command(BKW_SOCKET_READ_LINE) { public boolean run() { return executeCLIENT_READ_LINE(); } }, new Command(BKW_SOCKET_WRITE_LINE) { public boolean run() { return executeCLIENT_WRITE_LINE(); } }, new Command(BKW_SOCKET_WRITE_BYTES) { public boolean run() { return executeCLIENT_WRITE_BYTES(); } }, new Command(BKW_SOCKET_CLOSE) { public boolean run() { return executeCLIENT_CLOSE(); } }, new Command(BKW_SOCKET_SERVER_IP) { public boolean run() { return executeCLIENT_SERVER_IP(); } }, new Command(BKW_SOCKET_READ_FILE) { public boolean run() { return executeCLIENT_GETFILE(); } }, new Command(BKW_SOCKET_WRITE_FILE) { public boolean run() { return executeCLIENT_PUTFILE(); } } }; private final Command[] SocketServer_cmd = new Command[] { // Map Socket.server command keywords to their execution functions new Command(BKW_SOCKET_CREATE) { public boolean run() { return executeSERVER_CREATE(); } }, new Command(BKW_SOCKET_CONNECT) { public boolean run() { return executeSERVER_ACCEPT(); } }, new Command(BKW_SOCKET_STATUS) { public boolean run() { return executeSERVER_STATUS(); } }, new Command(BKW_SOCKET_READ_READY) { public boolean run() { return executeSERVER_READ_READY(); } }, new Command(BKW_SOCKET_READ_LINE) { public boolean run() { return executeSERVER_READ_LINE(); } }, new Command(BKW_SOCKET_WRITE_LINE) { public boolean run() { return executeSERVER_WRITE_LINE(); } }, new Command(BKW_SOCKET_WRITE_BYTES) { public boolean run() { return executeSERVER_WRITE_BYTES(); } }, new Command(BKW_SOCKET_DISCONNECT) { public boolean run() { return executeSERVER_DISCONNECT(); } }, new Command(BKW_SOCKET_CLOSE) { public boolean run() { return executeSERVER_CLOSE(); } }, new Command(BKW_SOCKET_CLIENT_IP) { public boolean run() { return executeSERVER_CLIENT_IP(); } }, new Command(BKW_SOCKET_READ_FILE) { public boolean run() { return executeSERVER_GETFILE(); } }, new Command(BKW_SOCKET_WRITE_FILE) { public boolean run() { return executeSERVER_PUTFILE(); } } }; // **************** DEBUG Group and ECHO Group private final Command[] debug_cmd = new Command[] { // Map debug command keywords to their execution functions new Command(BKW_ON) { public boolean run() { return executeDEBUG_TOGGLE(ON); } }, new Command(BKW_OFF) { public boolean run() { return executeDEBUG_TOGGLE(OFF); } }, new Command(BKW_PRINT) { public boolean run() { return executeDEBUG_PRINT(); } }, new Command(BKW_PRINT_SHORTCUT) { public boolean run() { return executeDEBUG_PRINT(); } }, new Command(BKW_DEBUG_ECHO_ON) { public boolean run() { return executeECHO_TOGGLE(ON); } }, new Command(BKW_DEBUG_ECHO_OFF) { public boolean run() { return executeECHO_TOGGLE(OFF); } }, new Command(BKW_DEBUG_DUMP_SCALARS) { public boolean run() { return executeDUMP_SCALARS(); } }, new Command(BKW_DEBUG_DUMP_ARRAY) { public boolean run() { return executeDUMP_ARRAY(); } }, new Command(BKW_DEBUG_DUMP_LIST) { public boolean run() { return executeDUMP_LIST(); } }, new Command(BKW_DEBUG_DUMP_STACK) { public boolean run() { return executeDUMP_STACK(); } }, new Command(BKW_DEBUG_DUMP_BUNDLE) { public boolean run() { return executeDUMP_BUNDLE(); } }, new Command(BKW_DEBUG_WATCH_CLEAR) { public boolean run() { return executeDEBUG_WATCH_CLEAR(); } }, new Command(BKW_DEBUG_WATCH) { public boolean run() { return executeDEBUG_WATCH(); } }, new Command(BKW_DEBUG_SHOW_SCALARS) { public boolean run() { return executeDEBUG_SHOW_SCALARS(); } }, new Command(BKW_DEBUG_SHOW_ARRAY) { public boolean run() { return executeDEBUG_SHOW_ARRAY(); } }, new Command(BKW_DEBUG_SHOW_LIST) { public boolean run() { return executeDEBUG_SHOW_LIST(); } }, new Command(BKW_DEBUG_SHOW_STACK) { public boolean run() { return executeDEBUG_SHOW_STACK(); } }, new Command(BKW_DEBUG_SHOW_BUNDLE) { public boolean run() { return executeDEBUG_SHOW_BUNDLE(); } }, new Command(BKW_DEBUG_SHOW_WATCH) { public boolean run() { return executeDEBUG_SHOW_WATCH(); } }, new Command(BKW_DEBUG_SHOW_PROGRAM) { public boolean run() { return executeDEBUG_SHOW_PROGRAM(); } }, new Command(BKW_DEBUG_SHOW) { public boolean run() { return executeDEBUG_SHOW(); } }, new Command(BKW_DEBUG_CONSOLE) { public boolean run() { return executeDEBUG_CONSOLE(); } }, new Command(BKW_DEBUG_COMMANDS) { public boolean run() { return executeDEBUG_COMMANDS(); } }, new Command(BKW_DEBUG_STATS) { public boolean run() { return executeDEBUG_STATS(); } }, }; // Legacy: can use DEBUG.ECHO or just ECHO private final Command[] echo_cmd = new Command[] { // Map echo command keywords to their execution functions new Command(BKW_ON) { public boolean run() { return executeECHO_TOGGLE(ON); } }, new Command(BKW_OFF) { public boolean run() { return executeECHO_TOGGLE(OFF); } }, }; // **************** TTS Group - text-to-speech private final Command[] tts_cmd = new Command[] { // Map TTS command keywords to their execution functions new Command(BKW_TTS_INIT) { public boolean run() { return executeTTS_INIT(); } }, new Command(BKW_TTS_SPEAK_TOFILE) { public boolean run() { return executeTTS_SPEAK_TOFILE(); } }, new Command(BKW_TTS_SPEAK) { public boolean run() { return executeTTS_SPEAK(); } }, new Command(BKW_TTS_STOP) { public boolean run() { return executeTTS_STOP(); } } }; // **************** FTP Group private final Command[] ftp_cmd = new Command[] { // Map FTP command keywords to their execution functions new Command(BKW_FTP_OPEN) { public boolean run() { return executeFTP_OPEN(); } }, new Command(BKW_FTP_CLOSE) { public boolean run() { return executeFTP_CLOSE(); } }, new Command(BKW_FTP_DIR) { public boolean run() { return executeFTP_DIR(); } }, new Command(BKW_FTP_CD) { public boolean run() { return executeFTP_CD(); } }, new Command(BKW_FTP_GET) { public boolean run() { return executeFTP_GET(); } }, new Command(BKW_FTP_PUT) { public boolean run() { return executeFTP_PUT(); } }, new Command(BKW_FTP_DELETE) { public boolean run() { return executeFTP_DELETE(); } }, new Command(BKW_FTP_RMDIR) { public boolean run() { return executeFTP_RMDIR(); } }, new Command(BKW_FTP_MKDIR) { public boolean run() { return executeFTP_MKDIR(); } }, new Command(BKW_FTP_RENAME) { public boolean run() { return executeFTP_RENAME(); } }, }; // **************** BT Group - Bluetooth channel operations private final Command[] bt_cmd = new Command[] { // Map Bluetooth command keywords to their execution functions new Command(BKW_BT_OPEN, CID_OPEN) { public boolean run() { return execute_BT_open(); } }, new Command(BKW_BT_CLOSE) { public boolean run() { return execute_BT_close(); } }, new Command(BKW_BT_STATUS, CID_STATUS) { public boolean run() { return execute_BT_status(); } }, new Command(BKW_BT_CONNECT) { public boolean run() { return execute_BT_connect(); } }, new Command(BKW_BT_DEVICE_NAME) { public boolean run() { return execute_BT_device_name(); } }, new Command(BKW_BT_WRITE) { public boolean run() { return execute_BT_write(); } }, new Command(BKW_BT_READ_READY) { public boolean run() { return execute_BT_read_ready(); } }, new Command(BKW_BT_READ_BYTES) { public boolean run() { return execute_BT_read_bytes(); } }, new Command(BKW_BT_SET_UUID) { public boolean run() { return execute_BT_set_uuid(); } }, new Command(BKW_BT_LISTEN) { public boolean run() { return execute_BT_listen(); } }, new Command(BKW_BT_RECONNECT) { public boolean run() { return execute_BT_reconnect(); } }, new Command(BKW_BT_ONREADREADY_RESUME) { public boolean run() { return execute_BT_readReady_Resume(); } }, new Command(BKW_BT_DISCONNECT) { public boolean run() { return execute_BT_disconnect(); } }, }; // **************** SU and SYSTEM Groups - superuser and system commands private final Command[] SU_cmd = new Command[] { // Map SU/System command keywords to their execution functions new Command(BKW_SU_OPEN, CID_OPEN) { public boolean run() { return execute_SU_open(); } }, new Command(BKW_SU_WRITE) { public boolean run() { return execute_SU_write(); } }, new Command(BKW_SU_READ_READY) { public boolean run() { return execute_SU_read_ready(); } }, new Command(BKW_SU_READ_LINE) { public boolean run() { return execute_SU_read_line(); } }, new Command(BKW_SU_CLOSE) { public boolean run() { return execute_SU_close(); } } }; // **************** SP Group - soundpool private final Command[] sp_cmd = new Command[] { // Map soundpool command keywords to their execution functions new Command(BKW_SOUNDPOOL_OPEN, CID_OPEN) { public boolean run() { return execute_SP_open(); } }, new Command(BKW_SOUNDPOOL_LOAD) { public boolean run() { return execute_SP_load(); } }, new Command(BKW_SOUNDPOOL_PLAY) { public boolean run() { return execute_SP_play(); } }, new Command(BKW_SOUNDPOOL_STOP) { public boolean run() { return execute_SP_stop(); } }, new Command(BKW_SOUNDPOOL_UNLOAD) { public boolean run() { return execute_SP_unload(); } }, new Command(BKW_SOUNDPOOL_PAUSE) { public boolean run() { return execute_SP_pause(); } }, new Command(BKW_SOUNDPOOL_RESUME) { public boolean run() { return execute_SP_resume(); } }, new Command(BKW_SOUNDPOOL_RELEASE) { public boolean run() { return execute_SP_release(); } }, new Command(BKW_SOUNDPOOL_SETVOLUME) { public boolean run() { return execute_SP_setvolume(); } }, new Command(BKW_SOUNDPOOL_SETPRIORITY) { public boolean run() { return execute_SP_setpriority(); } }, new Command(BKW_SOUNDPOOL_SETLOOP) { public boolean run() { return execute_SP_setloop(); } }, new Command(BKW_SOUNDPOOL_SETRATE) { public boolean run() { return execute_SP_setrate(); } }, }; // **************** RINGER Group private final Command[] ringer_cmd = new Command[] { // Map ringer command keywords to their execution functions new Command(BKW_RINGER_GET_MODE) { public boolean run() { return executeRINGER_GET_MODE(); } }, new Command(BKW_RINGER_SET_MODE) { public boolean run() { return executeRINGER_SET_MODE(); } }, new Command(BKW_RINGER_GET_VOLUME) { public boolean run() { return executeRINGER_GET_VOLUME(); } }, new Command(BKW_RINGER_SET_VOLUME) { public boolean run() { return executeRINGER_SET_VOLUME(); } }, }; // **************** HTML Group private final Command[] html_cmd = new Command[] { // Map HTML command keywords to their execution functions new Command(BKW_HTML_OPEN, CID_OPEN) { public boolean run() { return execute_html_open(); } }, new Command(BKW_HTML_ORIENTATION) { public boolean run() { return execute_html_orientation(); } }, new Command(BKW_HTML_LOAD_URL) { public boolean run() { return execute_html_load_url(); } }, new Command(BKW_HTML_LOAD_STRING) { public boolean run() { return execute_html_load_string(); } }, new Command(BKW_HTML_GET_DATALINK, CID_DATALINK) { public boolean run() { return execute_html_get_datalink(); } }, new Command(BKW_HTML_CLOSE, CID_CLOSE) { public boolean run() { return execute_html_close(); } }, new Command(BKW_HTML_GO_BACK) { public boolean run() { return execute_html_go_back(); } }, new Command(BKW_HTML_GO_FORWARD) { public boolean run() { return execute_html_go_forward(); } }, new Command(BKW_HTML_CLEAR_CACHE) { public boolean run() { return execute_html_clear_cache(); } }, new Command(BKW_HTML_CLEAR_HISTORY) { public boolean run() { return execute_html_clear_history(); } }, new Command(BKW_HTML_POST) { public boolean run() { return execute_html_post(); } }, }; // **************** SMS Group - text messages private final Command[] sms_cmd = new Command[] { // Map SMS command keywords to their execution functions new Command(BKW_SMS_RCV_INIT) { public boolean run() { return executeSMS_RCV_INIT(); } }, new Command(BKW_SMS_RCV_NEXT) { public boolean run() { return executeSMS_RCV_NEXT(); } }, new Command(BKW_SMS_SEND) { public boolean run() { return executeSMS_SEND(); } } }; // **************** TIMER Group private final Command[] Timer_cmd = new Command[] { // Map Timer command keywords to their execution functions new Command(BKW_TIMER_SET) { public boolean run() { return executeTIMER_SET(); } }, new Command(BKW_TIMER_CLEAR) { public boolean run() { return executeTIMER_CLEAR(); } }, new Command(BKW_TIMER_RESUME) { public boolean run() { return executeTIMER_RESUME(); } } }; // **************** TIMEZONE Group private final Command[] TimeZone_cmd = new Command[] { // Map TimeZone command keywords to their execution functions new Command(BKW_TIMEZONE_SET) { public boolean run() { return executeTIMEZONE_SET(); } }, new Command(BKW_TIMEZONE_GET) { public boolean run() { return executeTIMEZONE_GET(); } }, new Command(BKW_TIMEZONE_LIST) { public boolean run() { return executeTIMEZONE_LIST(); } } }; // **************** PHONE Group private final Command[] phone_cmd = new Command[] { // Map phone command keywords to their execution functions new Command(BKW_PHONE_CALL) { public boolean run() { return executePHONE_DIAL(Intent.ACTION_CALL); } }, new Command(BKW_PHONE_DIAL) { public boolean run() { return executePHONE_DIAL(Intent.ACTION_DIAL); } }, new Command(BKW_PHONE_RCV_INIT) { public boolean run() { return executePHONE_RCV_INIT(); } }, new Command(BKW_PHONE_RCV_NEXT) { public boolean run() { return executePHONE_RCV_NEXT(); } }, new Command(BKW_PHONE_INFO) { public boolean run() { return executePHONE_INFO(); } } }; // **************** VOLKEYS Group private final Command[] VolKeys_cmd = new Command[] { // Map VolKeys command keywords to their execution functions new Command(BKW_ON) { public boolean run() { return executeVOLKEYS_TOGGLE(ON); } }, new Command(BKW_OFF) { public boolean run() { return executeVOLKEYS_TOGGLE(OFF); } }, }; // **************** APP Group - activity manager commands private final Command[] app_cmd = new Command[] { // Map app command keywords to their execution functions new Command(BKW_APP_BROADCAST) { public boolean run() { return executeAPP_BROADCAST(); } }, new Command(BKW_APP_START) { public boolean run() { return executeAPP_START(); } }, }; //********************************************************************************************* // Methods used by execution functions of group commands private Command findSubcommand(Command[] commands, String type) { Command c = ExecutingLineBuffer.findSubCommand(commands, LineIndex); if (c == null) { RunTimeError("Unknown " + type + " command"); } // no keyword found LineIndex += ExecutingLineBuffer.subOffset(); return c; } private boolean executeSubcommand(Command[] commands, String type) { Command c = findSubcommand(commands, type); if (c == null) return false; ExecutingLineBuffer.promoteSubCommand(); // replace the group command with the subcommand return c.run(); } // Very special case: <group>.<subgroup>.<subcommand> where the <group> needs a validity check. // For example, GR.TEXT.SIZE: statementExecuter() must run executeGR() every time. private boolean executeSubgroupCommand(Command[] commands, String type) { int groupOffset = ExecutingLineBuffer.subOffset(); // get offset of <subgroup> keyword ExecutingLineBuffer.subcmd(null); // force findSubcommand to search Command c = findSubcommand(commands, type); // replace subcmd field with <subcommand> LineIndex -= groupOffset; // got counted twice return (c != null) && c.run(); } //********************************************************************************************* // The methods starting here are the core code for running a Basic program // Variable names consist of letters, digits, and these non-alphanumeric characters. private final static String varChars = "_@#"; // Look for a BASIC! word: [_@#\l]?[_@#\l\d]* private String getWord(String line, int start, String possibleKeyword) { int max = line.length(); if (start >= max || start < 0) { return ""; } boolean isPossibleKeyword = (possibleKeyword.length() != 0); int li = start; char c = line.charAt(li); if ((c >= 'a' && c <= 'z') || (varChars.indexOf(c) >= 0)) { // if first character matches do { // there's a word if (++li >= max) break; // done if no more characters if (isPossibleKeyword && // caller wants to stop at keyword line.startsWith(possibleKeyword, li)) { break; } // THEN, TO, or STEP c = line.charAt(li); // get next character } // and check it, stop if not valid while ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (varChars.indexOf(c) >= 0)); } return line.substring(start, li); } private void PrintShow(String... strs) { // write the console synchronized (mConsoleBuffer) { if (strs != null) { for (String str : strs) { mConsoleBuffer.add(str); } } } } private void SyntaxError() { // Called to output Syntax Error Message if (!SyntaxError) { // If a previous syntax error message has RunTimeError("Syntax Error"); // not been displayed then display one now SyntaxError = true; // and set the flag so we don't do it again. } if (GRopen) { // If graphics is opened then the user will not be able to see error messages. // Provide a haptic notice. lv.performHapticFeedback(2, 1); try { Thread.sleep(300); } catch (InterruptedException e) { } lv.performHapticFeedback(2, 1); try { Thread.sleep(300); } catch (InterruptedException e) { } lv.performHapticFeedback(2, 1); } } private boolean RunTimeError(String... msgs) { if (mOnErrorInt == null) { // don't display anything if error is being trapped String text = ExecutingLineBuffer.text(); if (text.endsWith("\n")) { text = chomp(text); } PrintShow(msgs[0], text); // display error message and offending line for (int i = 1; i < msgs.length; ++i) { // display any supplemental text PrintShow(msgs[i]); } } SyntaxError = true; Editor.SyntaxErrorDisplacement = ExecutingLineIndex + 1; writeErrorMsg(msgs[0]); Log.d(LOGTAG, "RunTimeError: " + mErrorMsg); return false; // always return false as convenience for caller } private boolean RunTimeError(Throwable e) { return RunTimeError("Error:", e); } private boolean RunTimeError(String prefix, Throwable e) { String msg = (e == null) ? null : e.getMessage(); return RunTimeError(prefix + " " + ((msg == null) ? "?" : msg)); } private void writeErrorMsg(String msg) { // Write errorMsg, do NOT set SyntaxError mErrorMsg = msg + "\nLine: " + ExecutingLineBuffer.text(); } private void writeErrorMsg(String prefix, Throwable e) { String msg = (e == null) ? null : e.getMessage(); writeErrorMsg(prefix + " " + ((msg == null) ? "?" : msg)); } private void writeErrorMsg(Exception e) { writeErrorMsg("Error:", e); } private void clearErrorMsg() { // Clear the global error message mErrorMsg = NO_ERROR; } // ************************* start of getVar() and its derivatives **************************** private static final boolean TYPE_NUMERIC = true; // true: type is numeric private static final boolean TYPE_STRING = false; // false: type is NOT numeric private static final boolean USER_FN_OK = true; // flag used as parseVar() argument, when false // user-defined function names are not recognized as valid symbols private static final String EXPECT_ARRAY_VAR = "Array variable expected"; private static final String EXPECT_ARRAY_EXISTS = "Array must be created before using"; private static final String EXPECT_ARRAY_NO_INDEX = "Expected '[]'"; private static final String EXPECT_NUM_ARRAY = "Array not numeric"; private static final String EXPECT_STRING_ARRAY = "Not string array"; private static final String EXPECT_NEW_FN_NAME = "Function previously defined at:"; // getVar: // This function parses a function name out of the input stream, then searches for // it in the variable lists. If an existing variable is not found, it creates one. // It writes global flags, variables, and data structures for results and status. // There are other functions that duplicate getVar() except for small changes that // make certain cases more efficient by leaving out some of the work. // // In Paul's original design, this function handled all cases of scalar and array // variables, and user-defined function names for FN.DEF (but not function calls) // with special cases directed by these global flags: // doingDim, unDiming, SkipArrayValues, DoingDef // This implementation behaves as the original did when all flags were set false. // There are now dedicated functions for some of the special cases. // All other cases must be built up from the primitives found below. // private boolean getVar() { // Get variable name if there is one. int LI = LineIndex; mVal = getVarValue(getVarAndType()); // If there is one, find it in the symbol table. // If not found in table, create new variable. if (mVal != null) return true; // found: value is in mVal LineIndex = LI; // not found: back up LineIndex return false; } private boolean getNVar() { // get var and assure that it is numeric int LI = LineIndex; mVal = getVarValue(getVarAndType(TYPE_NUMERIC)); if (mVal != null) return true; // found: value is in mVal LineIndex = LI; // not found: back up LineIndex return false; } private boolean getSVar() { // get var and assure that it is not numeric int LI = LineIndex; mVal = getVarValue(getVarAndType(TYPE_STRING)); if (mVal != null) return true; // found: value is in mVal LineIndex = LI; // not found: back up LineIndex return false; } private boolean getSVars(ArrayList<Var> vars) { // get string scalar(s) or writeable array variable from command line boolean isArray; do { Var var = getVarAndType(TYPE_STRING); // string var, may be scalar or array if (var == null) return false; isArray = var.isArray(); if (isArray) { // note: new array is not added to symbol table yet if (!validArrayVarForWrite(var, TYPE_STRING)) return false; } else { if (getVarValue(var) // create a new variable in symbol table if necessary == null) return false; // for completeness: can't happen because var is not array } vars.add(var); // add to caller's Var list } while (!isArray && isNext(',')); // continue until no more parameters unless one was an array return true; } // Get a Var for creating an array: must be array name with no index(es). private Var getArrayVarForWrite() { // returns the Var, null if error or no var Var var = getVarAndType(); // either string or numeric type is ok if (validArrayVarForWrite(var)) { return var; } // no error: return Var // error: return null if (var != null) { LineIndex -= var.name().length(); } // if LineIndex moved, back it up return null; } private boolean validArrayVarForWrite(Var var) { if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (!isNext(']')) { return RunTimeError(EXPECT_ARRAY_NO_INDEX); } return true; // no error } // Get a Var for creating an array: must be array name of correct type with no index(es). private Var getArrayVarForWrite(boolean type) { // returns the Var, null if error or no var Var var = getVarAndType(); // either string or numeric type is ok if (validArrayVarForWrite(var, type)) { return var; } // no error: return Var // error: return null if (var != null) { LineIndex -= var.name().length(); } // if LineIndex moved, back it up return null; } private boolean validArrayVarForWrite(Var var, boolean type) { if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (type != var.isNumeric()) { return RunTimeError(type ? EXPECT_NUM_ARRAY : EXPECT_STRING_ARRAY); } if (!isNext(']')) { return RunTimeError(EXPECT_ARRAY_NO_INDEX); } return true; // no error } // Get a Var for using an array or array segment: must be name of existing array private Var getExistingArrayVar() { // returns the Var, null if error or no var Var var = getVarAndType(); if (validExistingArrayVar(var)) { return var; } // no error: return Var // error: return null if (var != null) { LineIndex -= var.name().length(); } // if LineIndex moved, back it up return null; } private boolean validExistingArrayVar(Var var) { if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (var.isNew()) { return RunTimeError(EXPECT_ARRAY_EXISTS); } return true; // no error } private Var getNewFNVar() { // get var and assure that it is a new function name // returns the name, does NOT create a variable Var var = parseVar(USER_FN_OK); // Get function name if there is one. if (var != null) { if (var.isFunction()) { // If there is one... var = searchVar(var); // ... look for it in the symbol table. if (var.isNew()) { return var; // return new Var: SUCCESSFUL EXIT } else { RunTimeError(EXPECT_NEW_FN_NAME); // not new } } LineIndex -= var.name().length(); // name not valid, back up LineIndex } return null; // ERROR EXIT } // ************************* top half of getVar() ************************* // Result is a Var, or null if no variable name found on the line. private Var getVarAndType() { Var var = parseVar(!USER_FN_OK); // Get variable name if there is one. if (var != null) { // If there is one... var = searchVar(var); // ... find it in the symbol table. } return var; // note: LineIndex does not change if var is null } // Result is a Var, or null if no variable name found on the line, or if variable type is wrong. private Var getVarAndType(boolean needNumeric) { // specify TYPE_NUMERIC or TYPE_STRING Var var = parseVar(!USER_FN_OK); // get variable name if there is one if (var != null) { // if found, parseVar changed LineIndex if (needNumeric == var.isNumeric()) { // if type matches expected type return searchVar(var); // look up the variable name } // else type mismatch, return no var LineIndex -= var.name().length(); // back up LineIndex } return null; // no variable name found, LineIndex does not change } // Gets the variable name and type. Returns them in a Var object. // Name includes $ for strings, [ for arrays, ( for functions. // If arg is false, user-defined function names are not valid variable names. // If no valid variable name, returns null and does not advance LineIndex. // // Note: returns one of the "working Var" pool. Don't store this reference // anywhere. Copy the Var instead. private Var parseVar(boolean isUserFnAllowed) { String text = ExecutingLineBuffer.text(); int LI = LineIndex; int max = text.length(); Var var = null; // PosibleKeyWord is for the special cases where a var could be followed by keyword THEN, TO or STEP. String name = getWord(text, LI, PossibleKeyWord); // isolate the var characters LI += name.length(); if (LI > LineIndex) { // no var if length is 0 char c = text.charAt(LI); boolean isNumeric = (c != '$'); // Is this a string var? if (!isNumeric) { name += c; ++LI; c = text.charAt(LI); } if (c == '[') { // Is this an array? name += c; ++LI; var = mArrayVar.reNew(name, isNumeric); } else if (c == '(') { // Is this a function? if (!isUserFnAllowed) { return null; } // Do not write LineIndex name += c; ++LI; var = mFunctionVar.reNew(name, isNumeric); } else { var = mScalarVar.reNew(name, isNumeric); } } if (LI >= max) { LineIndex = max; return null; } // can't happen: line ends with "\n" LineIndex = LI; VarIsInt = false; // Never an integer return var; // null if no variable name found } // Always returns a Var, never null. Can use this Var anywhere; // if it was one of the "working Var" pool from parseVar() it gets copied. private Var searchVar(Var var) { // search for a variable by name // Skip global table for now, there's nothing in it. Var v = null; // Var v = searchVar(mGlobalSymbolTable, var); // first search global symbols // if (v == null) { if (interruptResume < 0) { // normal operation, not in interrupt v = searchVar(mSymbolTable, var); // search local symbols } else { // in interrupt for (Var.Table symbols : mSymbolTables) { // search all tables v = searchVar(symbols, var); if (v != null) { break; } } } if (v != null) { return v; } // } return var.copy(); // not in lists of variable names, return new Var } // Utility for searchVar(Var), searches one table. private Var searchVar(Var.Table symbols, Var var) { // search a table for a variable by name if (symbols == null) { return null; } // no table int j = Collections.binarySearch(symbols.mVarNames, var.name()); if (j < 0) { return null; } // name not found var = symbols.mVars.get(j); if (var.isArray()) { // If variable is array invalidated by UNDIM, // delete it so a new one with the same name can be created. Var.ArrayDef array = var.arrayDef(); if ((array != null) && !array.valid()) { symbols.remove(j); var.reNew(); } } return var; } // ************************* bottom half of getVar() ********************** // Do NOT call before calling parseVar() and searchVar(Var). // can automatically create scalar but not array. // Error if isArray and isNew or if GetArrayValue returns null. private Var.Val getVarValue(Var var) { // bottom half of getVar() if (var == null) return null; // no var to get Var.Val val = null; if (var.isArray()) { Var.ArrayVar aVar = (Var.ArrayVar) var; if (var.isNew()) { // new array: error RunTimeError(EXPECT_ARRAY_EXISTS); // set error and return null } else { // existing array val = GetArrayValue(aVar); // get a value based upon user's index values } } else { if (var.isNew()) { // new scalar: put an empty Val in the Var var.newVal(); // make a new Val attached to the Var insertNewVar(var); // and put the Var in the symbol table; } val = var.val(); // get the Val from the Var } return val; } // Get insertion point (-index - 1) so name list will still be in alphabetical order. private void insertNewVar(Var var) { // make a new var table entry String name = var.name(); // Throws exception if var already exists. If necessary, avoid by testing var.isNew() first. int index = newVarIndex(mSymbolTable.mVarNames, name); var.notNew(); mSymbolTable.add(index, name, var); // create new intry in symbol table } // ************************************* end of getVar() ************************************** // ********************************** The Expression Parsers ********************************** private boolean getNumber() { // Get a number if there is one char c = 0; String text = ExecutingLineBuffer.text(); int max = text.length(); int i = LineIndex; while (i < max) { // Must start with one or more digits c = text.charAt(i); if (c > '9' || c < '0') { break; } // If not a digit, done with whole part ++i; } if (i == LineIndex) { return false; } // No digits, not a number if (c == '.') { // May have a decimal point while (++i < max) { // Followed by more digits c = text.charAt(i); if (c > '9' || c < '0') { break; } // If not a digit, done with fractional part } } if (c == 'e' || c == 'E') { // Is there an exponent if (++i < max) { c = text.charAt(i); if (c == '+' || c == '-') { ++i; } // Is there a sign on the exponent } while (i < max) { // Get the exponent c = text.charAt(i); if (c > '9' || c < '0') { break; } // If not a digit, done with exponent ++i; } } String num = text.substring(LineIndex, i); // isolate the numeric characters LineIndex = i; double d = 0.0; try { d = Double.parseDouble(num); } // have java parse it into a double catch (Exception e) { return RunTimeError(e); } // can't happen if above parsing is correct GetNumberValue = d; // Report the value return true; // Say we found a number } private boolean GetStringConstant() { // Get a string constant if there is one String text = ExecutingLineBuffer.text(); int max = text.length(); if (LineIndex >= max || LineIndex < 0) { return false; } int i = LineIndex; StringConstant = ""; char c = text.charAt(i); if (c != '"') { return false; } // first char not "", not String Constant while (true) { // Get the rest of the String ++i; // copy character until " or EOL if (i >= max) { return false; } c = text.charAt(i); if (c == '"') { break; } // if " we're done if (c == '\r') { // AddProgramLine hides embedded newline as carriage return c = '\n'; } else if (c == '\\') { // AddProgramLine allows only quote or backslash after backslash c = text.charAt(++i); } StringConstant += c; // add to String Constant } if (i < max - 1) { ++i; } // do not let index be >= line length LineIndex = i; return true; // Say we have a string constant } private boolean evalToPossibleKeyword(String keyword) { // use with midline keywords THEN, TO, STEP // Evaluate a numeric expression, terminated either by EOL or by the given possible keyword. // Expression value is left in EvalNumericExpressionValue; return true is expression is valid. PossibleKeyWord = keyword; // tell parseVar to expect the keyword boolean ok = evalNumericExpression(); PossibleKeyWord = ""; // restore global before return return ok; } private boolean evalNumericExpression() { // Evaluate a numeric expression if (LineIndex >= ExecutingLineBuffer.length()) return false; char c = ExecutingLineBuffer.text().charAt(LineIndex); if (c == '\n' || c == ')') return false; // If eol or starts with ')', there is not an expression Stack<Double> ValueStack = new Stack<Double>(); // Each call to eval gets its own stack Stack<Integer> OpStack = new Stack<Integer>(); // thus we can recursively call eval int SaveIndex = LineIndex; OpStack.push(SOE); // Push Start of Expression onto stack if (!ENE(OpStack, ValueStack)) { // Now do the recursive evaluation LineIndex = SaveIndex; // if it fails, back up return false; // and die } if (ValueStack.empty()) return false; EvalNumericExpressionValue = ValueStack.pop(); // Recursive eval succeeded. Pop stack for results return true; } // The recursive part of evalNumericExpression private boolean ENE(Stack<Integer> theOpStack, Stack<Double> theValueStack) { double incdec = 0.0; // for recording pre-inc/dec char c = ExecutingLineBuffer.text().charAt(LineIndex); // First character if (c == '+') { // Check for unary operators or pre-inc/dec ++LineIndex; // move to the next character if (isNext('+')) { incdec = 1.0; } // remember to pre-increment else { theOpStack.push(UPLUS); } // save the operator } else if (c == '-') { ++LineIndex; if (isNext('-')) { incdec = -1.0; } // remember to post-decrement else { theOpStack.push(UMINUS); } // save the operator } else if (c == '!') { theOpStack.push(NOT); ++LineIndex; } int holdLI = LineIndex; Var var = parseVar(USER_FN_OK); // duplicate getVarAndType() except USER_FN_OK if (var != null) { // is variable or function name if (!var.isFunction()) { var = searchVar(var); // finish getVarAndType() if (var.isNumeric()) { mVal = getVarValue(var); // finish getNVar() if (mVal != null) { // we have a numeric variable! Var.Val val = mVal; double value = val.nval(); if (incdec != 0) { value += incdec; // pre-inc or dec val.val(value); } if (ExecutingLineBuffer.startsWith(OP_INC, LineIndex)) { val.val(value + 1); // post-increment LineIndex += 2; } else if (ExecutingLineBuffer.startsWith(OP_DEC, LineIndex)) { val.val(value - 1); // post-decrement LineIndex += 2; } theValueStack.push(value); // PUSH THE VALUE of the nvar } else { return false; } // nvar, but null val: runtime error getting array val } else { // var but not numeric LineIndex = holdLI; // not nvar, back up and try something else } } else { // function name if (incdec != 0) { SyntaxError(); return false; } // pre-inc/dec applies only to numeric variables Command cmd; String name = var.name(); Var.FnDef def = mFunctionTable.get(name); if ((def != null) && def.type().isNumeric()) { // like isUserFunction(true, TYPE_NUMERIC) if (!doUserFunction(def)) { SyntaxError(); return false; } theValueStack.push(EvalNumericExpressionValue); // PUSH THE VALUE (result of the function) } else if ((cmd = MF_map.get(name)) != null) { // try Math Function if (!doMathFunction(cmd)) { SyntaxError(); return false; } theValueStack.push(EvalNumericExpressionValue); // PUSH THE VALUE (result of the function) } else { LineIndex = holdLI; // not valid num function, back up and try something else } } } // else var is null, try something else if (LineIndex == holdLI) { // no valid Var found, here's where we try stomething else if (incdec != 0) { SyntaxError(); return false; } // pre-inc/dec applies only to numeric variables else if (getNumber()) { // is it a number? theValueStack.push(GetNumberValue); // PUSH THE VALUE (the number) } else if (evalStringExpression()) { // try String Logical Expression if (!SEisLE) return false; // if was not a logical string expression, fail theValueStack.push(EvalNumericExpressionValue); } else if (isNext('(')) { // handle possible ( String holdPKW = PossibleKeyWord; PossibleKeyWord = ""; boolean ok = evalNumericExpression() && isNext(')'); // eval expression inside the parens PossibleKeyWord = holdPKW; // restore global before possible return if (!ok) { return false; } theValueStack.push(EvalNumericExpressionValue); // no errors, push expression value } else { return false; } // nothing left, fail } if (LineIndex >= ExecutingLineBuffer.length()) { return false; } c = ExecutingLineBuffer.text().charAt(LineIndex); if (!PossibleKeyWord.equals("")) { if (ExecutingLineBuffer.startsWith(PossibleKeyWord, LineIndex)) { return handleOp(EOL, theOpStack, theValueStack); } } if (",:;]".indexOf(c) >= 0) { // treat any of these characters as an eol return handleOp(EOL, theOpStack, theValueStack); } if (!getOp()) { return false; } // If operator does not follow, then fail switch (OperatorValue) { // Handle special case operators // (This is probably redundant given the above) case EOL: if (!handleOp(EOL, theOpStack, theValueStack)) { return false; } --LineIndex; return true; case RPRN: if (!handleOp(RPRN, theOpStack, theValueStack)) { return false; } if (theOpStack.isEmpty()) { return true; } // ')' was removed with matching '(' if ((theOpStack.pop() == RPRN) && !theOpStack.isEmpty()) { if (theOpStack.pop() == SOE) { --LineIndex; // LineIndex points at ')' return true; // Let caller try to match ')' } } return false; // op stack got corrupted? case NOT: // can't use unary operator after operand case LPRN: // can't start new expression after operand return false; default: if (!handleOp(OperatorValue, theOpStack, theValueStack)) { // Handles non special case ops return false; } } return ENE(theOpStack, theValueStack); // Recursively call ENE for rest of expression } private boolean getOp() { // Get an expression operator if there is one int lastOp = OperatorString.length; // Look for operator for (int i = 0; i < lastOp; ++i) { String op = OperatorString[i]; if (ExecutingLineBuffer.startsWith(op, LineIndex)) { OperatorValue = i; LineIndex += op.length(); return true; } } if (isNext('~')) { // Look for the array.load continue line character OperatorValue = EOL; // Change it to EOL return true; } return false; } private boolean handleOp(int op, Stack<Integer> theOpStack, Stack<Double> theValueStack) { // handle an expression operator // Execute operator in turn by their precedence double d1 = 0; double d2 = 0; int ExecOp = 0; // If the operator stack is empty, push an SOE (should never happen) if (theOpStack.empty()) { // theOpStack.push(SOE); return false; } // If the current operator's Goes Onto Stack Precedence // is less than the top of stack' Come Off precedence // then pop the top of stack operator and execute it // keep doing this until the Goes Onto Precedence // is less then the TOS Come Off Precedence and then // push the current operator onto the operator stack while (ComesOffPrecedence[theOpStack.peek()] >= GoesOnPrecedence[op]) { if (theValueStack.empty()) return false; // Avoid a crash ExecOp = theOpStack.pop(); // Execute the popped operator // In general values are popped from the stack and then // operated on by the operator // the result is then pushed onto the value stack switch (ExecOp) { case UMINUS: d1 = theValueStack.pop(); d1 = -d1; theValueStack.push(d1); break; case UPLUS: break; case PLUS: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = d2 + d1; theValueStack.push(d1); break; case MINUS: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = d2 - d1; theValueStack.push(d1); break; case MUL: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = d2 * d1; theValueStack.push(d1); break; case DIV: d1 = theValueStack.pop(); d2 = theValueStack.pop(); // handle divide by zero if (d1 == 0) { return RunTimeError("DIVIDE BY ZERO AT:"); } d1 = d2 / d1; theValueStack.push(d1); break; case EXP: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = Math.pow(d2, d1); theValueStack.push(d1); break; case LE: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 <= d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case NE: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 != d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case GE: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 >= d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case GT: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 > d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case LT: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 < d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case LEQ: // Logical Equals d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = (d2 == d1) ? 1.0 : 0.0; theValueStack.push(d1); break; case OR: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = ((d1 != 0) || (d2 != 0)) ? 1.0 : 0.0; theValueStack.push(d1); break; case AND: d1 = theValueStack.pop(); d2 = theValueStack.pop(); d1 = ((d1 != 0) && (d2 != 0)) ? 1.0 : 0.0; theValueStack.push(d1); break; case NOT: d1 = theValueStack.pop(); d1 = (d1 == 0) ? 1.0 : 0.0; theValueStack.push(d1); break; case LPRN: if (op != RPRN) { return false; } break; case FLPRN: break; case RPRN: break; case SOE: return true; case EOL: d1 = d2; break; default: } // if (op == RPRN) break; } // End of while pop stack operations theOpStack.push(op); // Push the current operator return true; } private boolean evalStringExpression() { // Evaluate a string expression int max = ExecutingLineBuffer.length(); if (LineIndex >= max) { return false; } char c = ExecutingLineBuffer.text().charAt(LineIndex); if (c == '\n' || c == ')') { return false; } // If eol or starts with ')', there is not an expression SEisLE = false; // Assume not Logical Expression if (!ESE()) { return false; } // Get the next element (constant, var, function, etc) String Temp1 = StringConstant; StringBuilder sb = null; while (isNext('+')) { // Another piece to concatenate? if (!ESE()) { return false; } // Get the next element (constant, var, function, etc) if (sb == null) { sb = new StringBuilder(Temp1); } sb.append(StringConstant); // save the resulting string } if (sb != null) { StringConstant = Temp1 = sb.toString(); sb = null; } EvalNumericExpressionValue = 0.0; // Set Logical Compare Result to false if (LineIndex >= max) { return false; } c = ExecutingLineBuffer.text().charAt(LineIndex); if ((c == '\n') || // end of line (c == ')') || // end of parenthesized expression (c == ',') || // parameter separator (c == ';') || // PRINT separator (c == ':') // SQL.UPDATE separator ) { return true; } // string expression done // logical comparison operator required int SaveLineIndex = LineIndex; boolean isOp = getOp(); int operator = OperatorValue; isOp &= operator == LE || operator == NE || operator == GE || operator == GT || operator == LT || operator == LEQ; if (!isOp) { // not a logical comparison op LineIndex = SaveLineIndex; return true; // string expression done } if (!ESE()) { return false; } // get the string to compare SEisLE = true; // signal logical string expression String Temp2 = StringConstant; // do any concat on the right side while (isNext('+')) { SaveLineIndex = LineIndex - 1; // index of the '+' if (ESE()) { if (sb == null) { sb = new StringBuilder(Temp2); } sb.append(StringConstant); // build up the right side string } else { // what follows is not a string expression LineIndex = SaveLineIndex; // assume the + operation is numeric break; } } if (sb != null) { StringConstant = Temp2 = sb.toString(); } if ((Temp1 == null) || (Temp2 == null)) { return false; } int cv = Temp1.compareTo(Temp2); // Do the compare /* if Temp1 < Temp2, cv < 0 * if Temp1 = Temp2, cv = 0 * if Temp1 > Temp2, cv > 0 */ EvalNumericExpressionValue = 0.0; // assume false switch (operator) { case LE: if (cv <= 0) EvalNumericExpressionValue = 1.0; break; case NE: if (cv != 0) EvalNumericExpressionValue = 1.0; break; case GE: if (cv >= 0) EvalNumericExpressionValue = 1.0; break; case GT: if (cv > 0) EvalNumericExpressionValue = 1.0; break; case LEQ: if (cv == 0) EvalNumericExpressionValue = 1.0; break; case LT: if (cv < 0) EvalNumericExpressionValue = 1.0; break; default: return false; // Can't happen } return true; } private boolean ESE() { // Get a String expression element if (GetStringConstant()) return true; // Try String Constant int LI = LineIndex; if (isNext('(')) { // Try parenthesized string expression if (getStringArg() && isNext(')')) return true; // logical expresson not allowed here LineIndex = LI; return false; } Var var = parseVar(USER_FN_OK); // duplicate getVarAndType() except USER_FN_OK if (var == null) return false; // no variable or function name if (var.isFunction()) { // name ends with '('; it's a function if (var.isString()) { // could be a user-defined string-type function Var.FnDef fn = mFunctionTable.get(var.name()); // search like isUserFunction() if (fn != null) { boolean ok = doUserFunction(fn); // execute the user function if (!ok) { LineIndex = LI; SyntaxError(); } return ok; } } Command cmd = SF_map.get(var.name()); // try built-in string function if (cmd != null) { boolean ok = cmd.run(); // execute the built-in functon if (!ok) { LineIndex = LI; SyntaxError(); } return ok; } } else // array or scalar if (var.isString()) { // is it a string type? var = searchVar(var); Var.Val val = getVarValue(var); // bottom half of getVar() if (val != null) { StringConstant = val.sval(); return true; } } LineIndex = LI; return false; } // ******************************* Statement Parsing Utilities ******************************** private boolean isEOL() { return (LineIndex >= ExecutingLineBuffer.length()) || (ExecutingLineBuffer.text().charAt(LineIndex) == '\n'); } private boolean checkEOL() { if (isEOL()) return true; String ec = ExecutingLineBuffer.text().substring(LineIndex); RunTimeError("Extraneous characters in line: " + ec); return false; } private boolean isNext(char c) { // Check the current character if ((LineIndex < ExecutingLineBuffer.length()) && // if it is as expected... (ExecutingLineBuffer.text().charAt(LineIndex) == c)) { ++LineIndex; // ... increment the character pointer return true; } return false; // else don't increment LineIndex } private boolean getStringArg() { // Get and validate a string return (evalStringExpression() // Get the string expression && !SEisLE // Okay if not logical expression && (StringConstant != null)); // and not null // Leaves evaluation result in StringConstant } private boolean getArgAsNum() { // Get and validate a numeric expression if (!evalNumericExpression()) { // or string that evaluates to a number if (SyntaxError || !getStringArg()) { return false; } try { EvalNumericExpressionValue = Double.valueOf(StringConstant); } catch (NumberFormatException e) { return false; } } return true; // return value in EvalNumericExpressionValue } // Get optional arguments, where all are variables, not expressions. // types: 1 numeric, 2 string, 3 either // Type 3 coming in is overwritten to type of variable found on command line. private boolean getOptVars(byte[] types, Var.Val[] vals) { if (isEOL()) return true; // no arguments int nArgs = types.length; if (nArgs != vals.length) return false; // array lengths must match boolean isComma = true; for (int arg = 0; arg < nArgs; ++arg) { if (isComma) { isComma = isNext(','); if (!isComma) { if (!getVar()) return false; Var.Val val = mVal; byte vType = (byte) (val.isNumeric() ? 1 : 2); if ((vType & types[arg]) == 0) return false; // type mismatch types[arg] = vType; vals[arg] = val; isComma = isNext(','); } } } return (!isComma && checkEOL()); } // getOptVars // Get optional arguments, where all are expressions, not variables. // types: 1 numeric, 2 string, 3 either // Type 3 coming in is overwritten to type of expression found on command line. private boolean getOptExprs(byte[] type, Double[] nVal, String[] sVal) { if (isEOL()) return true; // no arguments int nArgs = type.length; if (nArgs != nVal.length) return false; // array lengths must match if (nArgs != sVal.length) return false; // array lengths must match boolean isComma = true; for (int arg = 0; arg < nArgs; ++arg) { int typ = type[arg]; if (isComma) { isComma = isNext(','); if (!isComma) { if (typ != 2) { // try numeric expression if (!evalNumericExpression()) { if (typ == 1) return false; // required numeric typ = 2; // actual type is string } else { if (typ == 3) { typ = 1; } // actual type is numeric nVal[arg] = EvalNumericExpressionValue; } } if (typ == 2) { // try string expression if (!getStringArg()) return false; sVal[arg] = StringConstant; } isComma = isNext(','); } } } return (!isComma && checkEOL()); } // getOptExprs(byte[], Double[], Sring[]) // Like getOptExprs, but limited to integer arguments. private boolean getOptExprs(int[] iVal) { if (isEOL()) return true; // no arguments int nArgs = iVal.length; boolean isComma = true; for (int arg = 0; arg < nArgs; ++arg) { if (isComma) { isComma = isNext(','); if (!isComma) { if (!evalNumericExpression()) return false; iVal[arg] = EvalNumericExpressionValue.intValue(); isComma = isNext(','); } } } return (!isComma && checkEOL()); } // getOptExprs(int[]) // Like getOptExprs, but limited to String arguments. private boolean getOptExprs(String[] sVal) { if (isEOL()) return true; // no arguments int nArgs = sVal.length; boolean isComma = true; for (int arg = 0; arg < nArgs; ++arg) { if (isComma) { isComma = isNext(','); if (!isComma) { if (!getStringArg()) return false; sVal[arg] = StringConstant; isComma = isNext(','); } } } return (!isComma && checkEOL()); } // getOptExprs(String[]) private double[] getArgsDD() { // get two numeric arguments (doubles) if (!evalNumericExpression()) { return null; } double d1 = EvalNumericExpressionValue; if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } double d2 = EvalNumericExpressionValue; double[] args = { d1, d2 }; return args; } private long[] getArgsLL() { // get two numeric arguments (longs) if (!evalNumericExpression()) { return null; } long l1 = EvalNumericExpressionValue.longValue(); if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } long l2 = EvalNumericExpressionValue.longValue(); long[] args = { l1, l2 }; return args; } private int[] getArgsII() { // get two numeric arguments (ints) if (!evalNumericExpression()) { return null; } int i1 = EvalNumericExpressionValue.intValue(); if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } int i2 = EvalNumericExpressionValue.intValue(); int[] args = { i1, i2 }; return args; } private int[] getArgs4I() { // get four numeric int arguments if (!evalNumericExpression()) { return null; } int i1 = EvalNumericExpressionValue.intValue(); if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } int i2 = EvalNumericExpressionValue.intValue(); if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } int i3 = EvalNumericExpressionValue.intValue(); if (!isNext(',')) { return null; } if (!evalNumericExpression()) { return null; } int i4 = EvalNumericExpressionValue.intValue(); int[] args = { i1, i2, i3, i4 }; return args; } private Var.Val[] getArgs4NVar() { // get four numeric var arguments if (!getNVar()) { return null; } Var.Val v1 = mVal; if (!isNext(',')) { return null; } if (!getNVar()) { return null; } Var.Val v2 = mVal; if (!isNext(',')) { return null; } if (!getNVar()) { return null; } Var.Val v3 = mVal; if (!isNext(',')) { return null; } if (!getNVar()) { return null; } Var.Val v4 = mVal; Var.Val[] args = { v1, v2, v3, v4 }; return args; } private String[] getArgsSS() { // get two string arguments if (!getStringArg()) { return null; } String s1 = StringConstant; if (!isNext(',')) { return null; } if (!getStringArg()) { return null; } String s2 = StringConstant; String[] args = { s1, s2 }; return args; } // ************************************** Math Functions ************************************** private boolean doMathFunction(Command cmd) { // If the function exists, run it, and verify that the closing ')' is present. // Function value is returned in EvalNumericExpressionValue. return (cmd != null) && cmd.run() && isNext(')'); } private boolean executeMF_SIN() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.sin(EvalNumericExpressionValue); return true; } private boolean executeMF_COS() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.cos(EvalNumericExpressionValue); return true; } private boolean executeMF_TAN() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.tan(EvalNumericExpressionValue); return true; } private boolean executeMF_SQR() { if (!evalNumericExpression()) return false; double d1 = EvalNumericExpressionValue; if (d1 < 0) { return RunTimeError("SQR parameter must be >= 0"); } EvalNumericExpressionValue = Math.sqrt(d1); return true; } private boolean executeMF_ABS() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.abs(EvalNumericExpressionValue); return true; } private boolean executeMF_SGN() { // 2014-03-16 gt if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.signum(EvalNumericExpressionValue); return true; } private boolean executeMF_CEIL() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.ceil(EvalNumericExpressionValue); EvalNumericExpressionIntValue = EvalNumericExpressionValue.longValue(); VarIsInt = true; return true; } private boolean executeMF_FLOOR() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.floor(EvalNumericExpressionValue); EvalNumericExpressionIntValue = EvalNumericExpressionValue.longValue(); VarIsInt = true; return true; } private boolean executeMF_INT() { // 2014-03-16 gt if (!evalNumericExpression()) return false; EvalNumericExpressionIntValue = EvalNumericExpressionValue.longValue(); EvalNumericExpressionValue = Double.valueOf(EvalNumericExpressionIntValue); VarIsInt = true; return true; } private boolean executeMF_FRAC() { // 2014-03-16 gt if (!evalNumericExpression()) return false; String str = EvalNumericExpressionValue.toString(); String sgn = (EvalNumericExpressionValue < 0) ? "-" : ""; int point = str.indexOf('.'); EvalNumericExpressionValue = (point < 0) ? 0.0 : Double.parseDouble(sgn + str.substring(point)); return true; } private boolean executeMF_MIN() { // 2013-07-29 gt double[] args = getArgsDD(); if (args == null) return false; EvalNumericExpressionValue = Math.min(args[0], args[1]); return true; } private boolean executeMF_MAX() { // 2013-07-29 gt double[] args = getArgsDD(); if (args == null) return false; EvalNumericExpressionValue = Math.max(args[0], args[1]); return true; } private boolean executeMF_LOG() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.log(EvalNumericExpressionValue); return true; } private boolean executeMF_EXP() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.exp(EvalNumericExpressionValue); return true; } private boolean executeMF_TODEGREES() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.toDegrees(EvalNumericExpressionValue); return true; } private boolean executeMF_TORADIANS() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.toRadians(EvalNumericExpressionValue); return true; } private boolean executeMF_PI() { // 2013-07-29 gt EvalNumericExpressionValue = Math.PI; return true; } private boolean executeMF_ATAN() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.atan(EvalNumericExpressionValue); return true; } private boolean executeMF_ACOS() { if (!evalNumericExpression()) return false; double d1 = EvalNumericExpressionValue; if (d1 < -1 || d1 > 1) { return RunTimeError("ACOS parameter out of range"); } EvalNumericExpressionValue = Math.acos(d1); return true; } private boolean executeMF_ASIN() { if (!evalNumericExpression()) return false; double d1 = EvalNumericExpressionValue; if (d1 < -1 || d1 > 1) { return RunTimeError("ASIN parameter out of range"); } EvalNumericExpressionValue = Math.asin(d1); return true; } private boolean executeMF_ROUND() { if (!evalNumericExpression()) return false; double d1 = EvalNumericExpressionValue; // look for optional place count arg if (!isNext(',')) { // no optional parameters, legacy behavior d1 = (double) Math.round(d1); } else { int places = 0; // default decimal place count int roundingMode = BigDecimal.ROUND_HALF_DOWN; // default rounding mode boolean isComma = isNext(','); if (!isComma) { if (!evalNumericExpression()) return false; // get decimal place count places = EvalNumericExpressionValue.intValue(); if (places < 0) { return RunTimeError("Decimal place count (" + places + ") must be >= 0"); } isComma = isNext(','); } if (isComma) { // look for optional rounding mode if (!getStringArg()) return false; String roundingArg = StringConstant.toLowerCase(Locale.US); Integer mode = mRoundingModeTable.get(roundingArg); if (mode == null) { return RunTimeError("Invalid rounding mode: " + roundingArg); } roundingMode = mode.intValue(); } d1 = new BigDecimal(d1).setScale(places, roundingMode).doubleValue(); } EvalNumericExpressionValue = d1; return true; } private boolean executeMF_LEN() { // LEN(s$ if (!getStringArg()) return false; // Get and check the string expression double d1 = StringConstant.length(); // then get its length EvalNumericExpressionValue = d1; return true; } private boolean executeMF_RANDOMIZE() { if (isNext(')')) { // no parameter --LineIndex; // unget the ')' randomizer = new Random(); // same as seed == 0 } else { if (!evalNumericExpression()) return false; long seed = EvalNumericExpressionValue.longValue(); randomizer = (seed == 0L) ? new Random() : new Random(seed); } EvalNumericExpressionValue = 0.0; return true; } private boolean executeMF_RND() { if (randomizer == null) { randomizer = new Random(); } EvalNumericExpressionValue = randomizer.nextDouble(); return true; } private boolean executeMF_BACKGROUND() { boolean background = RunPaused && !GRFront && !mWebFront; EvalNumericExpressionValue = background ? 1.0 : 0.0; return true; } private boolean executeMF_VAL() { // VAL(s$ if (!getStringArg()) return false; // Get and check the string expression StringConstant = StringConstant.trim(); if (StringConstant.length() == 0) { return RunTimeError("VAL of empty string is not valid"); } try { double d1 = Double.parseDouble(StringConstant); // have java parse it into a double EvalNumericExpressionValue = d1; } catch (NumberFormatException e) { return RunTimeError("Not a valid number: " + StringConstant); } return true; } private boolean executeMF_IS_NUMBER() { // IS_NUMBER(s$ if (!getStringArg()) return false; // Get and check the string expression StringConstant = StringConstant.trim(); double isNum = 0.0; // default: not a valid numeric string if (StringConstant.length() > 0) { // not valid if length 0 try { Double.parseDouble(StringConstant); // have java parse it into a double isNum = 1.0; // return 1: it's a valid numeric string } catch (NumberFormatException e) { } // return 0: not a valid numeric string } EvalNumericExpressionValue = isNum; return true; } private int getIndexArg(int max) { // return 1-based index, 0 if error, default 1 int index = 1; if (isNext(',')) { if (!evalNumericExpression()) return 0; index = EvalNumericExpressionValue.intValue(); if (max != 0) { // if empty string, index is irrelevant if ((index < 1) || (index > max)) { RunTimeError("Index (" + index + ") out of range"); return 0; } } } return index; } private boolean executeMF_ASCII() { if (!getStringArg()) return false; // Get and check the string expression String arg = StringConstant; int len = arg.length(); int index = getIndexArg(len); // get 1-based string index, 0 if error if (--index < 0) return false; // convert to 0-based index EvalNumericExpressionIntValue = (len == 0) ? 256L : (arg.charAt(index) & 0x00FF); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_UCODE() { if (!getStringArg()) return false; // Get and check the string expression String arg = StringConstant; int len = arg.length(); int index = getIndexArg(len); // get 1-based string index, 0 if error if (--index < 0) return false; // convert to 0-based index EvalNumericExpressionIntValue = (len == 0) ? 0x10000L : arg.charAt(index); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_MOD() { // MOD( d1,d2 double[] args = getArgsDD(); if (args == null) return false; if (args[1] == 0.0) { return RunTimeError("DIVIDE BY ZERO AT:"); } EvalNumericExpressionValue = (args[0] % args[1]); return true; } private boolean executeMF_BNOT() { if (!evalNumericExpression()) return false; long arg = EvalNumericExpressionValue.longValue(); EvalNumericExpressionIntValue = ~arg; EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_BOR() { long[] args = getArgsLL(); if (args == null) return false; EvalNumericExpressionIntValue = (args[0] | args[1]); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_BAND() { long[] args = getArgsLL(); if (args == null) return false; EvalNumericExpressionIntValue = (args[0] & args[1]); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_BXOR() { long[] args = getArgsLL(); if (args == null) return false; EvalNumericExpressionIntValue = (args[0] ^ args[1]); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_IS_IN() { String[] args = getArgsSS(); if (args == null) return false; String searchFor = args[0]; String searchIn = args[1]; int start = 1; if (isNext(',')) { if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); } double k; // result: index of matched substring if (start < 0) { // do reverse search start += searchIn.length(); // zero-based index of start character k = searchIn.lastIndexOf(searchFor, start); } else { // do forward search --start; // zero-based index of start character k = searchIn.indexOf(searchFor, start); } EvalNumericExpressionValue = (k < 0) ? 0 : ++k; // convert to one-based index return true; } private boolean executeMF_STARTS_WITH() { String[] args = getArgsSS(); if (args == null) return false; String searchFor = args[0]; String searchIn = args[1]; int start = 1; if (isNext(',')) { if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); if (start < 1) { return RunTimeError("Start value must be >= 1"); } } if (start > searchIn.length()) { start = searchIn.length(); } if (--start < 0) { start = 0; } // make one-based start index zero-based int i1 = searchIn.startsWith(searchFor, start) ? searchFor.length() : 0; EvalNumericExpressionValue = (double) i1; return true; } private boolean executeMF_ENDS_WITH() { String[] args = getArgsSS(); if (args == null) return false; String searchFor = args[0]; String searchIn = args[1]; int i1 = searchIn.endsWith(searchFor) ? (searchIn.length() - searchFor.length() + 1) : 0; EvalNumericExpressionValue = (double) i1; return true; } private boolean executeMF_CLOCK() { EvalNumericExpressionValue = (double) SystemClock.elapsedRealtime(); return true; } private boolean executeMF_TIME() { if (ExecutingLineBuffer.text().charAt(LineIndex) == ')') { // If no args, use current time EvalNumericExpressionIntValue = System.currentTimeMillis(); } else { // Otherwise, get user-supplied time Time time = theTimeZone.equals("") ? new Time() : new Time(theTimeZone); if (!parseTimeArgs(time)) return false; EvalNumericExpressionIntValue = time.toMillis(true); } EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_GR_COLLISION() { int[] args = getArgsII(); if (args == null) return false; EvalNumericExpressionValue = gr_collide(args[0], args[1]); return (EvalNumericExpressionValue != -1); // -1 is run time error } private boolean executeMF_base(int base) { // BIN, OCT, or HEX, depending on the base parameter if (!getStringArg()) return false; // Get and check the string expression try { EvalNumericExpressionIntValue = new BigInteger(StringConstant, base).longValue(); EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; } catch (NumberFormatException e) { return RunTimeError("Not a valid number: " + StringConstant); } return true; } private boolean executeMF_SHIFT() { long[] args = getArgsLL(); if (args == null) return false; long value = args[0]; long bits = args[1]; EvalNumericExpressionIntValue = (bits < 0) ? (value << -bits) : (value >> bits); ; EvalNumericExpressionValue = EvalNumericExpressionIntValue.doubleValue(); VarIsInt = true; return true; } private boolean executeMF_ATAN2() { double[] args = getArgsDD(); if (args == null) return false; EvalNumericExpressionValue = Math.atan2(args[0], args[1]); return true; } private boolean executeMF_CBRT() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.cbrt(EvalNumericExpressionValue); return true; } private boolean executeMF_COSH() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.cosh(EvalNumericExpressionValue); return true; } private boolean executeMF_HYPOT() { double[] args = getArgsDD(); if (args == null) return false; EvalNumericExpressionValue = Math.hypot(args[0], args[1]); return true; } private boolean executeMF_SINH() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.sinh(EvalNumericExpressionValue); return true; } private boolean executeMF_POW() { double[] args = getArgsDD(); if (args == null) return false; EvalNumericExpressionValue = Math.pow(args[0], args[1]); return true; } private boolean executeMF_LOG10() { if (!evalNumericExpression()) return false; EvalNumericExpressionValue = Math.log10(EvalNumericExpressionValue); return true; } // ************************************ end Math Functions ************************************ // ************************************* String Functions ************************************* // Each string function must check for the closing parenthesis (')'). private boolean executeSF_LEFT() { // LEFT$ if (!getStringArg()) return false; String str = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' int length = str.length(); if (length > 0) { int count = EvalNumericExpressionValue.intValue(); if (count < 0) { count += length; str = (count <= 0) ? "" : str.substring(0, count); } else if (count < length) { str = str.substring(0, count); } } StringConstant = str; return true; } private boolean executeSF_RIGHT() { // RIGHT$ if (!getStringArg()) return false; if (!isNext(',')) return false; String str = StringConstant; if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' int length = str.length(); if (length > 0) { int count = EvalNumericExpressionValue.intValue(); if (count < 0) { str = (-count >= length) ? "" : str.substring(-count); } else if (count < length) { str = str.substring(length - count); } } StringConstant = str; return true; } private boolean executeSF_MID() { // MID$ if (!getStringArg()) return false; // Get the string String str = StringConstant; int length = str.length(); if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the start index int start = EvalNumericExpressionValue.intValue(); int count; if (isNext(',')) { // If there is a count, get it if (!evalNumericExpression()) return false; count = EvalNumericExpressionValue.intValue(); } else { count = length; // Default count is whole string } if (!isNext(')')) return false; // Function must end with ')' if (length > 0) { if (--start < 0) { start = 0; } // change 1-based index to 0-based else if (start >= length) { start = length; } int end; // 0-based end index if (count > 0) { end = start + count; if (end > length) { end = length; } } else { end = start + 1; if (end > length) { end = length; } start = end + count; if (start < 0) { start = 0; } } if ((count == 0) || (start >= length)) { str = ""; } else if ((start > 0) || (end < length)) { str = str.substring(start, end); } } StringConstant = str; return true; } private String ltrim(String str, String trim) { // remove if ((str == null) || str.equals("")) return ""; if ((trim == null) || trim.equals("")) return str; if (!trim.startsWith("^")) { trim = "^" + trim; } return str.replaceFirst(trim, ""); } private String rtrim(String str, String trim) { if ((str == null) || str.equals("")) return ""; if ((trim == null) || trim.equals("")) return str; if (!trim.endsWith("$")) { trim += "$"; } return str.replaceFirst(trim, ""); } private boolean executeSF_TRIM(int what) { // TRIM$ // use TLEFT, TRIGHT, or TLEFT|TRIGHT for what arg if (!getStringArg()) return false; String str = StringConstant; String trim = "\\s+"; // default: trim whitespace if (isNext(',')) { if (!getStringArg()) return false; trim = StringConstant; } if (!isNext(')')) return false; // Function must end with ')' if (trim != "") { if ((what & TLEFT) != 0) { str = ltrim(str, trim); } if ((what & TRIGHT) != 0) { str = rtrim(str, trim); } } StringConstant = str; return true; } private boolean executeSF_WORD() { // WORD$ if (!getStringArg()) return false; // string to split String SearchString = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // which word to return int wordIndex = EvalNumericExpressionValue.intValue(); String r[] = doSplit(SearchString, 0); // get regex arg, if any, and split the string. if (!isNext(')')) return false; // Function must end with ')' int length = r.length; // get the number of strings generated if (length == 0) return false; // error in doSplit() wordIndex--; // convert to 0-based index for (int i = 0; (i < length) && (r[i].length() == 0); ++i) { wordIndex++; // special case: string started with delimiter(s) } StringConstant = ((wordIndex < 0) || (wordIndex >= length)) ? "" : r[wordIndex]; return true; } private boolean executeSF_STR() { // STR$ if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' StringConstant = String.valueOf(EvalNumericExpressionValue); return true; } private boolean executeSF_UPPER() { // UPPER$ if (!getStringArg()) return false; if (!isNext(')')) return false; // Function must end with ')' StringConstant = StringConstant.toUpperCase(Locale.getDefault()); return true; } private boolean executeSF_LOWER() { // LOWER$ if (!getStringArg()) return false; if (!isNext(')')) return false; // Function must end with ')' StringConstant = StringConstant.toLowerCase(Locale.getDefault()); return true; } private boolean executeSF_ENCODE(boolean mode) { // DECODE$ and ENCODE$ int encoding; String charset = "UTF-8"; // default charset // If one of the special encodings (Base64, URL, or encryption), // the first arg is the type, (optional) second is charset (password if encryption) // and third (required) is the string to encode/decode. if (!getStringArg()) return false; // get the type or charset String type = StringConstant; if (!isNext(',')) return false; String typeLC = type.toLowerCase(Locale.getDefault()); if (typeLC.equals("base64")) { encoding = ENCODING_BASE64; } else if (typeLC.equals("url")) { encoding = ENCODING_URL; } else if (typeLC.equals("encrypt")) { encoding = ENCODING_ENCRYPT; } else if (typeLC.equals("decrypt")) { encoding = ENCODING_ENCRYPT; } else { // Plain string conversion. First arg is charset, second is string to convert. encoding = ENCODING_OTHER; charset = type; } String src; switch (encoding) { case ENCODING_ENCRYPT: charset = ""; // default password is empty string // Fall through to get password, if present. case ENCODING_BASE64: case ENCODING_URL: if (!isNext(',')) { if (!getStringArg()) return false; // get the optional charset (password if ENCRYPT) charset = StringConstant; if (!isNext(',')) return false; } // Fall through to get the string. default: if (!getStringArg()) return false; // Get the required source string src = StringConstant; if (!isNext(')')) return false; // Function must end with ')' } String dest = null; try { switch (encoding) { case ENCODING_ENCRYPT: dest = (mode == ENCODE) ? encrypt(src, charset) : decrypt(src, charset); break; case ENCODING_BASE64: dest = (mode == ENCODE) ? encodeBase64(src, charset) : decodeBase64(src, charset); break; case ENCODING_URL: dest = (mode == ENCODE) ? URLEncoder.encode(src, charset) : URLDecoder.decode(src, charset); break; default: dest = (mode == ENCODE) ? encode(src, charset) : decode(src, charset); break; } } catch (Exception e) { if (encoding == ENCODING_ENCRYPT) { return encryptionException(mode, e); } else { // UnsupportedEncodingException, IllegalCharsetNameException, or UnsupportedCharsetException return RunTimeError(charset + " is not a valid encoding on this device."); } } if (dest == null) return false; StringConstant = dest; return true; } private boolean encryptionException(boolean mode, Exception e) { return (mode == ENCODE) ? RunTimeError("Encryption error (" + e.getMessage() + ")") : RunTimeError("Decryption error (" + e.getMessage() + "). Check password."); } @TargetApi(Build.VERSION_CODES.FROYO) private String encrypt(String src, String pw) throws Exception { Cipher ecipher = new Basic.Encryption(Cipher.ENCRYPT_MODE, pw).cipher(); byte[] utf8 = src.getBytes("UTF-8"); // encode the string into bytes using UTF-8 byte[] enc = ecipher.doFinal(utf8); // encrypt // To support FroYo, there is a copy of Android's Base64.java in our build files. String dest = Base64.encodeToString(enc, Base64.NO_WRAP); // encode bytes to base64 to get a string return dest.trim(); } @TargetApi(Build.VERSION_CODES.FROYO) private String decrypt(String src, String pw) throws Exception { Cipher dcipher = new Basic.Encryption(Cipher.DECRYPT_MODE, pw).cipher(); // To support FroYo, there is a copy of Android's Base64.java in our build files. byte[] dec = Base64.decode(src, Base64.DEFAULT); // decode base64 to get bytes byte[] utf8 = dcipher.doFinal(dec); // decrypt return new String(utf8, "UTF-8"); // encode bytes to UTF-8 to get a string } @TargetApi(Build.VERSION_CODES.FROYO) private String encodeBase64(String src, String charset) throws UnsupportedEncodingException { byte[] enc = src.getBytes(charset); // To support FroYo, there is a copy of Android's Base64.java in our build files. return Base64.encodeToString(enc, Base64.NO_WRAP); } @TargetApi(Build.VERSION_CODES.FROYO) private String decodeBase64(String src, String charset) throws UnsupportedEncodingException { // To support FroYo, there is a copy of Android's Base64.java in our build files. byte[] dec = Base64.decode(src, Base64.DEFAULT); return new String(dec, charset); } private String encode(String src, String charset) throws UnsupportedEncodingException { byte[] enc = src.getBytes(charset); return new String(enc, "ISO-8859-1"); } private String decode(String src, String charset) throws UnsupportedEncodingException { byte[] dec = src.getBytes("ISO-8859-1"); // any char > 00FF maps to byte 3F return new String(dec, charset); } private boolean executeSF_FORMAT() { // FORMAT$ if (!getStringArg()) return false; // get the pattern string String str = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the number to format if (!isNext(')')) return false; // Function must end with ')' return Format(str, EvalNumericExpressionValue); // and then do the format } private boolean executeSF_USING() { Locale locale; if (isNext(',')) { StringConstant = ""; } // force default Locale else { if (!getStringArg()) return false; // get user-specified Locale if (!isNext(',')) return false; } locale = parseLocale(StringConstant); if (locale == null) { return RunTimeError("Unknown locale " + StringConstant); } if (!getStringArg()) return false; // get format string String fmt = StringConstant; Object[] args = getUsingArgs(); // get data to format if (args == null) return false; // error getting args if (!isNext(')')) return false; // Function must end with ')' try { StringConstant = String.format(locale, fmt, args); } catch (Exception e) { return RunTimeError("Cannot complete FPRINT\n", e); } return true; } private boolean executeSF_CHR() { // CHR$ StringBuilder sb = new StringBuilder(); do { if (!evalNumericExpression()) return false; sb.append((char) EvalNumericExpressionValue.intValue()); } while (isNext(',')); if (!isNext(')')) return false; // Function must end with ')' StringConstant = sb.toString(); return true; } private boolean executeSF_VERSION() { // VERSION$ if (!isNext(')')) return false; // Function must end with ')' StringConstant = getString(R.string.version); return true; } private boolean executeSF_GETERROR() { // GETERROR$ if (!isNext(')')) return false; // Function must end with ')' StringConstant = (mErrorMsg != null) ? mErrorMsg : "unknown"; return true; } private boolean executeSF_REPLACE() { // REPLACE$ if (!getStringArg()) return false; String target = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String argument = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String replacment = StringConstant; if (!isNext(')')) return false; // Function must end with ')' if (argument == null || replacment == null) { return RunTimeError("Invalid string"); } StringConstant = target.replace(argument, replacment); return true; } private boolean executeSF_INT() { // INT$ if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' long val = EvalNumericExpressionValue.longValue(); StringConstant = Long.toString(val); return true; } private boolean executeSF_HEX() { // HEX$ if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' long val = EvalNumericExpressionValue.longValue(); StringConstant = Long.toHexString(val); return true; } private boolean executeSF_OCT() { // OCT$ if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' long val = EvalNumericExpressionValue.longValue(); StringConstant = Long.toOctalString(val); return true; } private boolean executeSF_BIN() { // BIN$ if (!evalNumericExpression()) return false; if (!isNext(')')) return false; // Function must end with ')' long val = EvalNumericExpressionValue.longValue(); StringConstant = Long.toBinaryString(val); return true; } private boolean parseTimeArgs(Time time) { // Convert time parameters to Time object fields int year, month, day, hour, minute, second; // Requires six parameters, if (!getArgAsNum()) return false; // either numeric or string containing a number year = EvalNumericExpressionValue.intValue(); // Year$ if (!isNext(',') || !getArgAsNum()) return false; month = EvalNumericExpressionValue.intValue() - 1; // Month$ (convert to 0-index) if (!isNext(',') || !getArgAsNum()) return false; day = EvalNumericExpressionValue.intValue(); // Day$ if (!isNext(',') || !getArgAsNum()) return false; hour = EvalNumericExpressionValue.intValue(); // Hour$ if (!isNext(',') || !getArgAsNum()) return false; minute = EvalNumericExpressionValue.intValue(); // Minute$ if (!isNext(',') || !getArgAsNum()) return false; second = EvalNumericExpressionValue.intValue(); // Second$ time.set(second, minute, hour, day, month, year); return true; } private Locale parseLocale(String localeStr) { if ((localeStr == null) || (localeStr.length() == 0)) { return Locale.getDefault(); } Locale locale; String[] f = localeStr.split("_"); switch (f.length) { default: // ignore excess fields case 3: locale = new Locale(f[0], f[1], f[2]); break; case 2: locale = new Locale(f[0], f[1]); break; case 1: locale = new Locale(f[0]); break; } return locale; } private Object[] getUsingArgs() { ArrayList<Object> args = new ArrayList<Object>(); while (isNext(',')) { if (evalNumericExpression()) { // field is numeric if (VarIsInt) { args.add(EvalNumericExpressionIntValue); } else { args.add(EvalNumericExpressionValue); } } else if (getStringArg()) { args.add(StringConstant); // field is string } if (SyntaxError) return null; } return args.toArray(); } private boolean Format(String Fstring, double Fvalue) { // Format a number for output // Do the heart of the FORMAT$ function BigDecimal B = BigDecimal.valueOf(0.0); try { B = BigDecimal.valueOf(Math.abs(Fvalue)); } catch (Exception e) { return RunTimeError(e); } String Vstring = B.toPlainString(); // and convert the big decimal to a string String FWstring = ""; // Will hold the whole number part of the pattern (format) string String FDstring = ""; // Will hold the decimal part of the pattern string String VWstring = ""; // Will hold the whole number part of the number String VDstring = ""; // Will hold the decimal part of the number String Temp = ""; int i = 0; char c = ' '; boolean FhasDecimal = false; int FdecimalIndex = 0; boolean VhasDecimal = false; int VdecimalIndex = 0; // First find pattern string decimal index for (i = 0; i < Fstring.length(); ++i) { if (Fstring.charAt(i) == '.') { if (FhasDecimal) return false; // if more than one decimal, error FhasDecimal = true; FdecimalIndex = i; } } if (!FhasDecimal) { // If no decimal in pattern FdecimalIndex = i + 1; // set decimal index past end of pattern } else { // else move index past the decimal ++FdecimalIndex; } // Split the pattern string FWstring = Fstring.substring(0, FdecimalIndex - 1); // FW is whole number part (includes decimal) if (FhasDecimal) { FDstring = Fstring.substring(FdecimalIndex, Fstring.length()); } // FD is decimal string for (i = 0; i < FDstring.length(); ++i) { // insure FD only has # chars if (FDstring.charAt(i) != '#') return false; } for (i = 0; i < Vstring.length(); ++i) { // Scan the number string for its decimal index if (Vstring.charAt(i) == '.') { if (VhasDecimal) return false; // more than one decimal should never happen VhasDecimal = true; VdecimalIndex = i; // Set the value decimal index } } if (!VhasDecimal) { // If value has not decimal (should never happen) VdecimalIndex = i + 1; } else { ++VdecimalIndex; // move the decimal index past the decimal } // Split the value string VWstring = Vstring.substring(0, VdecimalIndex - 1); // VW is whole number part (includes decimal) if (VhasDecimal) { VDstring = Vstring.substring(VdecimalIndex, Vstring.length()); } // VD is the decimal part // Build the decimal part of the output string Temp = ""; if (FDstring.length() > 0) { Temp = "."; } // If any pattern for decimal, output the decimal for (i = 0; i < FDstring.length(); ++i) { // Copy Decimal digits to output as long as there if (FDstring.charAt(i) != '#') return false; // are pattern # chars. If run out of digits before if (i < VDstring.length()) { // pattern digits, output 0 characters Temp += VDstring.charAt(i); } else { Temp += "0"; } } VDstring = Temp; // Save the Decimal Value output string Temp = ""; int FWI = FWstring.length(); int VWI = VWstring.length(); // Now work on the floating pattern String FloatChar = " "; // Initialize float charcter to none if ((FWI > 0) && // If there is a float character (FWstring.charAt(0) != '#') && (FWstring.charAt(0) != '%')) { FloatChar = Character.toString(FWstring.charAt(0)); // then move it to FloatChar FWstring = FWstring.substring(1, FWI); --FWI; } String Header = ""; // Initialize Header to empty Header += FloatChar; // add the float char to the header Header += (Fvalue < 0) ? "-" : " "; // if value < 0 output minus sign else space // note: reverse order will be unreversed later // Now work on the whole number par StringConstant = ""; if ((FWI == 0) && (VWI > 0)) { // No Whole format characters and Whole Value characters remain if (VWstring.charAt(0) != '0') { // and the Whole character is not '0' StringConstant = "*" + VDstring; // then show error } else { // No whole number format chars and not whole number digits StringConstant += (Fvalue < 0) ? "-" : " "; // If the decimal digits are < 0 output minus else blank StringConstant += FloatChar + VDstring; // Build result for decimal digits with no whole number } return true; // done } // We have whole format chars and whole value digits --FWI; --VWI; boolean blanks = true; while (FWI >= 0) { // While there are format characters c = FWstring.charAt(FWI); // get the format character switch (c) { case '#': // format charcter = # blanks = true; if (VWI >= 0) { if ((VWI == 0) && (VWstring.charAt(VWI) == '0')) { Temp += " "; } // if there are no more digits, output space else { Temp += Character.toString(VWstring.charAt(VWI)); } // else output the digit --VWI; // go to the next digit } else if (VWI == -1) { // No digits left, if we just ran out, output the header Temp += Header + " "; // output the header --VWI; } else { Temp += " "; // output space } break; case '%': // format charcter = % blanks = false; if (VWI >= 0) { Temp += Character.toString(VWstring.charAt(VWI)); // if more digits, output it --VWI; } else { Temp += "0"; // else output 0 } break; default: // format character not # or % if (blanks) { // if doing blanks if (VWI >= 0) { Temp += c; } // if more digits, output char else if (VWI == -1) { Temp += Header + " "; --VWI; } // if just now ran out, output header and blank else { Temp += " "; } // else just a blank } else { Temp += c; // not blanks, output the char } } --FWI; } if (VWI == -1) { Temp += Header; } // add the header to the end of the whole number Temp = new StringBuilder(Temp).reverse().toString(); // now reverse the whole thing StringConstant = Temp + VDstring; if (VWI >= 0) { // If value decimal digits remain StringConstant = "**" + StringConstant; // show the error in place of the header } return true; // and we are done } // *********************************** end String Functions *********************************** // ************************************* array utilities ************************************** private boolean BuildBasicArray(Var var, int[] DimList) { // build an arbitrary array with initialized data Var.ArrayVar arrayVar = (Var.ArrayVar) var; Var.ArrayDef array; try { array = Var.ArrayDef.create(var.isNumeric(), DimList); } // create a new array defintion catch (InvalidParameterException ex) { return RunTimeError("DIMs must be >= 1 at"); } array.createArray(); // add an empty array of the proper size and type arrayVar.arrayDef(array); // add the definition to the variable if (var.isNew()) { insertNewVar(arrayVar); } // put the variable in the symbol table if not already there return true; } private boolean BuildBasicArray(Var var, ArrayList<Integer> DimList) { // like previous method int[] dims = new int[DimList.size()]; // but convert ArrayList<Integer> to int[] int idx = 0; for (int dim : DimList) { dims[idx++] = dim; } return BuildBasicArray(var, dims); } private boolean BuildBasicArray(Var var, int length) { // build a one-dimensional array with initialized data int[] dimValues = new int[] { length }; // list of dimensions has only one element return (BuildBasicArray(var, dimValues)); // go build an array of the proper size } private boolean ListToBasicNumericArray(Var var, List<Double> Values, int length) { if (!BuildBasicArray(var, length)) return false; // go build an array of the proper size and type double[] array = var.arrayDef().getNumArray(); // Caution! Raw array access! int i = 0; for (double d : Values) { array[i++] = d; } // stuff the array return true; } private boolean ListToBasicStringArray(Var var, List<String> Values, int length) { if (!BuildBasicArray(var, length)) return false; // go build an array of the proper size and type String[] array = var.arrayDef().getStrArray(); // Caution! Raw array access! int i = 0; for (String s : Values) { array[i++] = s; } // stuff the array return true; } private Var.Val GetArrayValue(Var.ArrayVar var) { // index an array, return the value in a Val ArrayList<Integer> indexList = new ArrayList<Integer>(); if (!isNext(']')) { // parse out the index values for this call do { if (!evalNumericExpression()) return null; indexList.add(EvalNumericExpressionValue.intValue()); } while (isNext(',')); if (!isNext(']')) return null; } try { return var.val(indexList); } catch (InvalidParameterException ex) { RunTimeError(ex.getMessage()); return null; } } private boolean getIndexPair(Integer[] pair) { // get contents of [] from command line // at most two index values accepted boolean isBracket = isNext(']'); if (!isBracket) { boolean isComma = isNext(','); if (!isComma) { if (!evalNumericExpression()) return false; pair[0] = EvalNumericExpressionValue.intValue(); // first index isComma = isNext(','); } isBracket = isNext(']'); if (!isBracket && isComma) { if (!evalNumericExpression()) return false; pair[1] = EvalNumericExpressionValue.intValue(); // second index isBracket = isNext(']'); } } return (isBracket); // must end with ']' } private boolean getArraySegment(Var.ArrayDef array, Integer[] pair) { // normalize array segment // pair in is 1-based [start, length], out is 1-based [base, length] if (array == null) { return RunTimeError("Array does not exist"); } int length = array.length(); // get the array length int base = 0; // array always starts at index 0 int max = length; if (pair[0] != null) { int start = pair[0].intValue(); // start index, 1-based, default 1 if (start > 1) { base += start - 1; } // convert to 0-based index, ignore if less than 1 if (base > max) { base = max; } // not an error, just force length to 0 } if (pair[1] != null) { length = pair[1].intValue(); // requested length if (length < 0) { length = 0; } // eliminate negative input } if ((base + length) > max) { length = max - base; } // don't go off end of array pair[0] = base; pair[1] = length; return true; } // *********************************** end array utilities ************************************ // ************************************* Command Methods ************************************** private boolean executeImplicitCommand() { // no keyword at beginning of line int LI = LineIndex; Var var = parseVar(USER_FN_OK); // look for a variable or function name if (var == null) return false; LineIndex = LI; // rewind the LineIndex for re-parse Command cmd = var.isFunction() ? CMD_CALL : CMD_IMPL_LET; // implicit CALL or LET (no preinc/dec) ExecutingLineBuffer.cmd(cmd, 0); // remember for next time the line is executed return cmd.run(); } private double incdec() { double result = 0.0; if (ExecutingLineBuffer.startsWith(OP_INC, LineIndex)) { LineIndex += 2; result = 1.0; } else if (ExecutingLineBuffer.startsWith(OP_DEC, LineIndex)) { LineIndex += 2; result = -1.0; } return result; } private boolean executeLET() { // Execute LET (an assignment statement) /* * This is the entry point for use when the command keyword "LET" is present. */ double preVal = incdec(); // check for pre-increment/decrement // Remember the result so, if the line is executed again, // it will go directly to the executeLET(double) with the right pre-inc/dec val. Command cmd = (preVal == 0) ? CMD_IMPL_LET : ((preVal > 0.0) ? CMD_PREINC : CMD_PREDEC); int length = CMD_LET.name.length() + cmd.name.length(); ExecutingLineBuffer.cmd(cmd, length); return executeLET(preVal); } private boolean executeLET(double nval) { // Execute LET (an assignment statement) /* * This is the execution function for commands BKW_LET, BKW_PREDEC, and BKW_PREINC, * and it is also called from executeImplicitCommand() for implied LET commands. * Pre-decrement and pre-increment with no LET are treated as explicit * BKW_PREDEC and BKW_PREINC commands. */ boolean islval = (nval == 0.0); // assignable only if no inc/dec if (!getVar()) return false; // get or create the variable to assign a value to Var.Val val = mVal; // variable's value boolean varIsNumeric = val.isNumeric(); if (varIsNumeric) { double postincdec = incdec(); // check for post-increment/decrement if (postincdec != 0) { nval += postincdec; islval = false; } } else if (!islval) return false; // can't inc/dec a string if (isEOL()) { // no more to parse, may have created variable if (varIsNumeric && (nval != 0.0)) { // pre/post inc/dec of numeric variable val.addval(nval); return true; } return false; // else (e.g., bare variable name) syntax error } else if (!islval) return false; // can't be label or assignment if already inc/dec else if (isNext(':')) { ExecutingLineBuffer.cmd(CMD_LABEL, LineIndex); // it's a label return checkEOL(); // must end line } // Implementation note: this should probably be put in a Java enum type. (TODO) int op = ASSIGN; String sval = ""; if (!isNext('=')) { // require some kind of assignment operator // NOTE: The lvalue before assignment is the ORIGINAL value of the variable, // not the value as modified by and '++' or '--' in the rvalue expression. if (varIsNumeric) { nval += val.nval(); } else { sval = val.sval(); } if (ExecutingLineBuffer.startsWith("+=", LineIndex)) { op = PLUS; } else if (varIsNumeric) { if (ExecutingLineBuffer.startsWith("-=", LineIndex)) { op = MINUS; } else if (ExecutingLineBuffer.startsWith("*=", LineIndex)) { op = MUL; } else if (ExecutingLineBuffer.startsWith("/=", LineIndex)) { op = DIV; } else if (ExecutingLineBuffer.startsWith("^=", LineIndex)) { op = EXP; } else if (ExecutingLineBuffer.startsWith("&=", LineIndex)) { op = AND; } else if (ExecutingLineBuffer.startsWith("|=", LineIndex)) { op = OR; } else return false; } else return false; LineIndex += 2; } if (varIsNumeric) { // if variable is numeric then if (!evalNumericExpression()) return false; // evaluate following numeric expression if (op != ASSIGN) { double rval = EvalNumericExpressionValue; switch (op) { // apply the required operation case PLUS: nval += rval; break; case MINUS: nval -= rval; break; case MUL: nval *= rval; break; case DIV: nval /= rval; break; case EXP: nval = Math.pow(nval, rval); break; case AND: nval = ((nval != 0) && (rval != 0)) ? 1.0 : 0.0; case OR: nval = ((nval != 0) || (rval != 0)) ? 1.0 : 0.0; } EvalNumericExpressionValue = nval; } val.val(EvalNumericExpressionValue); // assign result to the numeric var } else { // var is string if (!getStringArg()) return false; // evaluate the string expression if (op == PLUS) { StringConstant = sval + StringConstant; } val.val(StringConstant); // assign result to the string var } return checkEOL(); } // executeLET private boolean executeDIM() { do { // Multiple Arrays can be DIMed in one DIM statement separated by commas Var var = getVarAndType(); // get the array variable name if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (isNext(']')) return false; // must have dimension(s) ArrayList<Integer> dimValues = new ArrayList<Integer>(); // a list to hold the array dimension values do { // get each index value if (!evalNumericExpression()) return false; dimValues.add(EvalNumericExpressionValue.intValue()); // and add it to the list } while (isNext(',')); if (!isNext(']')) return false; // must have closing bracket if (!BuildBasicArray(var, dimValues)) return false; // no error, build the array } while (isNext(',')); // continue while there are arrays to be DIMed return checkEOL(); // then done } private boolean executeUNDIM() { mVal = null; // clear mVal just in case it's pointing at a big array that's about to be UNDIMed do { // Multiple arrays can be UNDIMed in one statement Var var = getVarAndType(); if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (!isNext(']')) { return RunTimeError(EXPECT_ARRAY_NO_INDEX); } if (!var.isNew()) { // if DIMed, UNDIM it var.arrayDef().invalidate(); // mark the array invalid in case any other variable is looking at it searchVar(var); // delete the variable so it can't be used to access this array any more } } while (isNext(',')); // continue while there are arrays to be UNDIMed return checkEOL(); } private boolean executePRINT() { if (!buildPrintLine(PrintLine, "")) return false; // build up the print line in StringConstant if (!PrintLineReady) { // flag set by buildPrintLine PrintLine = StringConstant; // not ready to print; hold line return true; // and wait for next Print command } PrintLine = ""; // clear the accumulated print line PrintShow(StringConstant); // output the line return true; } // Convert the fields of a print command into a String for printing. // The line param can hold an existing String to add the new String to. // If the line ends with a semicolon set PrintLineReady false, // else add the newline param to the String and set PrintLineReady true. // If the String is valid, put it in StringConstant and return true, // else return false to signal a syntax error. private boolean buildPrintLine(String text, String newline) { StringBuilder printLine = new StringBuilder((text == null) ? "" : text); char sep = ' '; do { // do each field in the print statement char c = ExecutingLineBuffer.text().charAt(LineIndex); if (c == '\n') { break; // done processing the line } // not EOL: expression is required if (evalNumericExpression()) { printLine.append(EvalNumericExpressionValue); // convert to string } else if (!SyntaxError && evalStringExpression()) { printLine.append(StringConstant); // field is string } else { if (!SyntaxError) checkEOL(); // report junk at EOL unless prior error return false; } if (SyntaxError) { return false; } sep = ExecutingLineBuffer.text().charAt(LineIndex); // get possible separator if (sep == ',') { // if the separator is a comma printLine.append(sep).append(' '); // add comma + blank to the line ++LineIndex; } else if (sep == ';') { // if separator is semicolon ++LineIndex; // don't add anything to output } } while (true); // Exit loop happens internal to loop PrintLineReady = (sep != ';'); if (PrintLineReady) { // if not ended in semicolon printLine.append(newline); // add newline character(s) } StringConstant = printLine.toString(); return true; } private boolean executeEND() { String endMsg = "END"; // Default END message boolean ok = true; if (!isEOL()) { ok = getStringArg(); // Get user's END message if (ok) { endMsg = StringConstant; ok = checkEOL(); } } if (endMsg.length() > 0) { PrintShow(endMsg); } Stop = true; // ALWAYS stop return ok; } private boolean getLabelLineNum() { int li = LineIndex; String label = getWord(ExecutingLineBuffer.text(), LineIndex, ""); // get label name int len = label.length(); LineIndex += len; // move LineIndex for isEOL() if (isEOL()) { // If EOL, this is a simple "GOTO/GOSUB label" statement. if (len == 0) return false; // no label and no expression: error } else { // Otherwise it is a "GOTO/GOSUB index, label_list..." statement. int comma = ExecutingLineBuffer.text().indexOf(',', LineIndex); if (comma < 0) return false; // if no comma, there is no expression list, so syntax error LineIndex = li; if (!evalNumericExpression() || !isNext(',')) return false; int index = (int) (EvalNumericExpressionValue + 0.5); // round index ArrayList<String> labels = new ArrayList<String>(); // build label list do { if (isEOL()) return false; // don't end with comma label = getWord(ExecutingLineBuffer.text(), LineIndex, ""); labels.add(label); LineIndex += label.length(); } while (isNext(',')); if (!isEOL()) return false; if ((index < 1) || (index > labels.size())) return true; // no target, fall through label = labels.get(index - 1); len = label.length(); if (len == 0) return true; // no target, fall through } Integer lineRef = Labels.get(label); if (lineRef == null) return RunTimeError("Undefined Label \"" + label + "\" at:"); ExecutingLineIndex = lineRef.intValue(); return true; } private boolean executeGOTO() { // Look for severe block boundary crossing abuse: GOTO out of IF or FOR. int maxStack = 50000; // 50,000 should be enough if ((IfElseStack.size() > maxStack) || (ForNextStack.size() > maxStack)) { return RunTimeError("Stack overflow. See manual about use of GOTO."); } return getLabelLineNum(); } private boolean executeGOSUB() { int returnAddress = ExecutingLineIndex; if (!getLabelLineNum()) return false; if (ExecutingLineIndex != returnAddress) { GosubStack.push(returnAddress); // found a valid gosub address, expect a return } return true; } private boolean executeRETURN() { if (!checkEOL()) return false; if (GosubStack.empty()) { return RunTimeError("RETURN without GOSUB"); } ExecutingLineIndex = GosubStack.pop(); return true; } private boolean executeIF() { if (!IfElseStack.empty()) { // if inside of an IF block Integer q = IfElseStack.peek(); if ((q != IEexec) && (q != IEinterrupt)) { // and not executing int index = ExecutingLineBuffer.text().indexOf("then"); if (index < 0) { // if there is no THEN IfElseStack.push(IEskip2); // need to skip to this if's end } else { LineIndex = index + 4; // skip over the THEN if (isNext('\n')) { // if not single line if IfElseStack.push(IEskip2); // need to skip to this if's end } // else is single line, no skip needed } return true; } } // Execute this IF String kw = "then"; if (!evalToPossibleKeyword(kw)) { return false; } // tell getVar that THEN is expected boolean condition = (EvalNumericExpressionValue != 0); if (ExecutingLineBuffer.text().charAt(LineIndex) != '\n') { if (!ExecutingLineBuffer.startsWith(kw, LineIndex)) { checkEOL(); return false; } LineIndex += 4; if (!isNext('\n')) { return SingleLineIf(condition); } // assume single-line IF // at this point: "IF condition THEN\n" and LineIndex is after '\n' } IfElseStack.push((condition) ? IEexec : IEskip1); return true; } private boolean SingleLineIf(boolean condition) { int index = ExecutingLineBuffer.text().lastIndexOf("else"); // At present, can't use the same ProgramLine when calling StatementExecuter recursively. // We create a new, temporary ProgramLine, with its mCommand null, every time. if (condition) { String text = ExecutingLineBuffer.text(); if (index > 0) { text = text.substring(0, index) + "\n"; // clip off the "else" clause } ExecutingLineBuffer = new ProgramLine(text); return StatementExecuter(); } if (index > 0) { LineIndex = index + 4; ExecutingLineBuffer = new ProgramLine(ExecutingLineBuffer.text()); return StatementExecuter(); } return true; } private boolean executeELSE() { if (IfElseStack.empty()) { // prior IF or ELSEIF should have put something on the stack RunTimeError("Misplaced ELSE"); return false; } if (!checkEOL()) return false; Integer q = IfElseStack.pop(); IfElseStack.push((q == IEexec) ? IEskip2 : IEexec); return true; } private boolean executeELSEIF() { if (IfElseStack.empty()) { // prior IF or ELSEIF should have put something on the stack RunTimeError("Misplaced ELSEIF"); return false; } Integer q = IfElseStack.pop(); if (q == IEexec) { IfElseStack.push(IEskip2); } else { String kw = "then"; if (!evalToPossibleKeyword(kw)) { return false; } // tell getVar that THEN is expected boolean condition = (EvalNumericExpressionValue != 0); if (ExecutingLineBuffer.startsWith(kw, LineIndex)) { LineIndex += 4; } if (!checkEOL()) { return false; } IfElseStack.push((condition) ? IEexec : IEskip1); } return true; } private boolean executeENDIF() { if (IfElseStack.empty()) { // Something must be on the stack return RunTimeError("Misplaced ENDIF"); } if (!checkEOL()) return false; IfElseStack.pop(); // but we do not care what it is return true; } private boolean skipTo(Command target, Command nest, String errMsg) { int lineNum; int limit = Basic.lines.size(); for (lineNum = ExecutingLineIndex + 1; lineNum < limit; ++lineNum) { ProgramLine line = Basic.lines.get(lineNum); Command c = line.cmd(); if (c == null) { if (line.startsWith(target.name)) { line.cmd(target); // found the target c = target; } else if (line.startsWith(nest.name)) { line.cmd(nest); // found nested block of same type c = nest; } else continue; // next line } if (c == target) { ExecutingLineIndex = lineNum; // found the target ExecutingLineBuffer = line; LineIndex = line.offset(); return true; } if (c == nest) { ExecutingLineIndex = lineNum; // found nested block of same type if (!skipTo(target, nest, errMsg)) return false; // recursively seek its end lineNum = ExecutingLineIndex; } } return RunTimeError(errMsg); // end of program, target not found } private boolean executeFOR() { int fline = ExecutingLineIndex; // Loop return location if (!getNVar()) return false; Var.Val indexVal = mVal; // For Var if (!isNext('=')) return false; // For Var = String kw = "to"; if (!evalToPossibleKeyword(kw)) return false; // tell getVar that TO is expected double fstart = EvalNumericExpressionValue; if (!ExecutingLineBuffer.startsWith(kw, LineIndex)) return false; LineIndex += 2; kw = "step"; if (!evalToPossibleKeyword(kw)) { return false; } // For Var = <exp> to <exp> double flimit = EvalNumericExpressionValue; double fstep = 1.0; // For Var = <exp> to <exp> <default step> if (ExecutingLineBuffer.startsWith(kw, LineIndex)) { LineIndex += 4; if (!evalNumericExpression()) { return false; } // For Var = <exp> to <exp> step <exp> fstep = EvalNumericExpressionValue; } if (!checkEOL()) return false; indexVal.val(fstart); // assign start <exp> to Var ForNext desc = // An object to hold values for stack new ForNext(fline, indexVal, fstep, flimit); if (fstep > 0) { // Test the initial condition if (fstart > flimit) { return SkipToNext(); } // If exceeds limit then skip to NEXT } else { if (fstart < flimit) { return SkipToNext(); } } ForNextStack.push(desc); return true; } private boolean SkipToNext() { return skipTo(CMD_NEXT, CMD_FOR, "FOR without NEXT"); } private boolean executeF_N_CONTINUE() { if (ForNextStack.empty()) { // If the stack is empty return RunTimeError("No For Loop Active"); // then we have a misplaced CONTINUE } if (!checkEOL()) return false; if (!SkipToNext()) return false; doNext(); return true; } private boolean executeF_N_BREAK() { if (ForNextStack.empty()) { // If the stack is empty return RunTimeError("No For Loop Active"); // then we have a misplaced BREAK } if (!checkEOL()) return false; if (!SkipToNext()) return false; ForNextStack.pop(); return true; } private boolean executeNEXT() { if (ForNextStack.empty()) { // If the stack is empty return RunTimeError("NEXT without FOR"); // then we have a misplaced NEXT } return doNext(); } private boolean doNext() { ForNext desc = ForNextStack.peek(); // peek at the descriptor if (desc.doStep()) { // update the loop index ForNextStack.pop(); // done: pop the stack } else { ExecutingLineIndex = desc.line(); // not done: repeat the loop } return true; } private boolean executeWHILE() { int LI = LineIndex; if (!evalNumericExpression()) return false; if (!checkEOL()) return false; if (EvalNumericExpressionValue != 0.0) { // true: push line number and index onto while stack WhileStack.push(new WhileRepeat(ExecutingLineIndex, LI)); return true; } return SkipToRepeat(); // false: find the REPEAT for the WHILE } private boolean SkipToRepeat() { return skipTo(CMD_REPEAT, CMD_WHILE, "WHILE without REPEAT"); } private boolean executeW_R_CONTINUE() { if (WhileStack.empty()) { // If the stack is empty return RunTimeError("No While Loop Active"); // then we have a misplaced CONTINUE } if (!checkEOL()) return false; int saveLine = ExecutingLineIndex; // save current line number if (!doRepeat()) return false; // re-execute the WHILE statement if (EvalNumericExpressionValue == 0.0) { ExecutingLineIndex = saveLine; // false: skip to end of loop if (!SkipToRepeat()) return false; WhileStack.pop(); // and pop the stack } return true; } private boolean executeW_R_BREAK() { if (WhileStack.empty()) { // If the stack is empty return RunTimeError("No While Loop Active"); // then we have a misplaced BREAK } if (!checkEOL()) return false; if (!SkipToRepeat()) return false; WhileStack.pop(); return true; } private boolean executeREPEAT() { if (WhileStack.empty()) { // If the stack is empty return RunTimeError("REPEAT without WHILE"); // then we have a misplaced REPEAT } if (!checkEOL()) return false; int repeatLine = ExecutingLineIndex; // save current line number if (!doRepeat()) return false; // re-execute the WHILE statement if (EvalNumericExpressionValue == 0.0) { WhileStack.pop(); // false: pop the stack ExecutingLineIndex = repeatLine; // and exit loop } // else re-execute loop return true; } private boolean doRepeat() { WhileRepeat line = WhileStack.peek(); // re-execute the WHILE statement ExecutingLineIndex = line.line(); ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); LineIndex = line.offset(); return (evalNumericExpression()); } private boolean executeDO() { if (!checkEOL()) return false; DoStack.push(ExecutingLineIndex); // push line number onto DO stack. return true; } private boolean SkipToUntil() { return skipTo(CMD_UNTIL, CMD_DO, "DO without UNTIL"); } private boolean executeD_U_CONTINUE() { if (DoStack.empty()) { // If the stack is empty return RunTimeError("No DO loop active"); // then we have a misplaced CONTINUE } if (!checkEOL()) return false; if (!SkipToUntil()) return false; return doUntil(); } private boolean executeD_U_BREAK() { if (DoStack.empty()) { // If the stack is empty return RunTimeError("No DO loop active"); // then we have a misplaced BREAK } if (!checkEOL()) return false; if (!SkipToUntil()) return false; DoStack.pop(); return true; } private boolean executeUNTIL() { if (DoStack.empty()) { // If the stack is empty return RunTimeError("UNTIL without DO"); // then we have a misplaced UNTIL } return doUntil(); } private boolean doUntil() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; if (EvalNumericExpressionValue == 0) { ExecutingLineIndex = DoStack.peek(); // false: go back to the DO line number } else { DoStack.pop(); // true: pop the stack } return true; } private boolean executeINPUT() { String prompt = ""; boolean isComma = isNext(','); // 1st comma if (!isComma) { if (!getStringArg()) return false; // get user prompt / Dialog title prompt = StringConstant; isComma = isNext(','); // 1st comma } if (!isComma || !getVar()) return false; // 1st comma, variable for return value Var.Val varVal = mVal; boolean isNumeric = varVal.isNumeric(); String inputDefault = ""; Var.Val canceledVal = null; isComma = isNext(','); if (isComma) { // 2nd comma, get input default isComma = isNext(','); // 3rd comma if (!isComma) { if (isNumeric) { if (!evalNumericExpression()) return false; inputDefault = String.valueOf(EvalNumericExpressionValue); if (inputDefault.endsWith(".0")) { inputDefault = inputDefault.replace(".0", ""); } } else { if (!getStringArg()) return false; inputDefault = StringConstant; } isComma = isNext(','); // 3rd comma } } if (isComma) { // 3rd comma if (!getNVar()) return false; canceledVal = mVal; } if (!checkEOL()) return false; DialogArgs args = new DialogArgs(); args.mTitle = prompt; args.mInputDefault = inputDefault; args.mButton1 = "Ok"; args.mReturnVal = varVal; mWaitForLock = true; mInputCancelled = false; sendMessage(MESSAGE_INPUT_DIALOG, args); // signal UI to start the dialog waitForLOCK(); // wait for the user to exit the Dialog if (canceledVal != null) { // use cancel var to report if canceled canceledVal.val(mInputCancelled ? 1.0 : 0.0); } if (mInputCancelled) { if (isNumeric) { // if canceled, listener did not set value varVal.val(0.0); } else { varVal.val(""); } if (canceledVal == null) { // no cancel var, report cancel as error String err = "Input dialog cancelled"; if (mOnErrorInt != null) { // allow program to trap cancel as error mErrorMsg = err; ExecutingLineIndex = mOnErrorInt.line(); } else { // tell user we are stopping PrintShow(err, "Execution halted"); Stop = true; // and stop executing } } } return true; } // ************************************** Dialog Commands ************************************* private boolean executeDIALOG() { // Get Dialog command keyword if it is there return executeSubcommand(Dialog_cmd, "Dialog"); } private boolean executeDIALOG_MESSAGE() { // Show a Dialog with title, message, and 0 - 3 buttons String title = null; String msg = null; String[] btn = new String[3]; if (!isNext(',')) { // 1st comma if (!getStringArg()) return false; // get Dialog title title = StringConstant; if (!isNext(',')) return false; // 1st comma } if (!isNext(',')) { // 2nd comma if (!getStringArg()) return false; // get Dialog message msg = StringConstant; if (!isNext(',')) return false; // 2nd comma } if (!getNVar()) return false; // variable for returned button number Var.Val val = mVal; for (int i = 0; (i < 3) && isNext(','); ++i) { if (!getStringArg()) return false; btn[i] = StringConstant.trim(); if (btn[i].length() == 0) return false; // do not allow empty string as button label } if (!checkEOL()) return false; DialogArgs args = new DialogArgs(); args.mTitle = title; args.mMessage = msg; args.mButton1 = btn[0]; args.mButton2 = btn[1]; args.mButton3 = btn[2]; mWaitForLock = true; sendMessage(MESSAGE_ALERT_DIALOG, args); // signal UI to start the dialog waitForLOCK(); // wait for the user to exit the Dialog val.val(mAlertItemID); return true; } private boolean executeDIALOG_SELECT() { // Show a Dialog with a selection list DialogArgs args = new DialogArgs(); ArrayList<String> selectList = new ArrayList<String>(); if (!parseSelect(args, selectList)) return false; String[] array = new String[selectList.size()]; selectList.toArray(array); args.mList = array; mWaitForLock = true; sendMessage(MESSAGE_ALERT_DIALOG, args); // signal UI to start the dialog waitForLOCK(); // wait for the user to exit the Dialog args.mReturnVal.val(mAlertItemID); return true; } // ************************************** Read Commands *************************************** private boolean executeREAD() { // Get READ command keyword if it is there return executeSubcommand(read_cmd, "Read"); // and execute the command } // Parse and bundle the data list of a READ.DATA statement // Called from PreScan() and NOT from statementExecuter(). private boolean executeREAD_DATA() { do { // Sweep up the data values Var.Val val; if (GetStringConstant()) { // If it is a string val = new Var.StrVal(StringConstant); // create a Val object for it } else { // Should be a number double signum = 1.0; // Assume positive if (isNext('-')) { signum = -1.0; } // Catch minus sign else if (isNext('+')) { ; } // If not negative, eat optional '+' if (getNumber()) { // If it is a number val = new Var.NumVal(signum * GetNumberValue); // create a Val object for it } else { // else is a run time error return RunTimeError("Invalid Data Value"); } } readData.add(val); // add the bundle to the list } while (isNext(',')); // and do again if more data return true; } private boolean executeREAD_NEXT() { do { if (readNext >= readData.size()) { // Insure there is more data to read return RunTimeError("No more data to read"); } Var.Val readval = readData.get(readNext); // get the data object ++readNext; // and increment to next object if (!getVar()) return false; // get the variable Var.Val val = mVal; if (val.isNumeric()) { // if var is numeric val.val(readval.nval()); // copy the numeric value to the variable } else { // else var is string val.val(readval.sval()); // copy the string value to the variable } } while (isNext(',')); // loop while there are variables return checkEOL(); } private boolean executeREAD_FROM() { if (!evalNumericExpression()) return false; int newIndex = EvalNumericExpressionValue.intValue() - 1; if (newIndex < 0 || newIndex >= readData.size()) { return RunTimeError("Index out of range"); } readNext = newIndex; return checkEOL(); } // ********************************** User-Defined Functions ********************************** private boolean executeFN() { // Get User-defined Function (FN) command keyword if it is there return executeSubcommand(fn_cmd, "FN"); } private boolean executeFN_DEF() { // Define User-defined Function Var fnVar = getNewFNVar(); // get the function name and type if (fnVar == null) return false; // Make a list of the parameters to put in the FunctionDefinition. ArrayList<Var.FunctionParameter> fParms = new ArrayList<Var.FunctionParameter>(); if (!isNext(')')) { Set<String> uParms = new HashSet<String>(); // Keep a set of unique parameter names do { // Get each of the parameter names Var var = parseVar(!USER_FN_OK); // without adding any new vars to the symbol table if (var == null) return false; if (var.isArray() && !isNext(']')) { // Process array return RunTimeError(EXPECT_ARRAY_NO_INDEX); // Array must not have any indices } var = var.copy(); // don't use Var from ParseVar directly if (!var.isArray()) { var.newVal(); } // make sure Val of ScalarVar is not null fParms.add(new Var.FunctionParameter(var)); uParms.add(var.name()); } while (isNext(',')); if (!(isNext(')') && checkEOL())) return false; // If there are duplicate parameter names, the list of unique names will be // shorter than the list of parameters. (Bug fix provided by Nicolas Mougin.) if (uParms.size() < fParms.size()) { return RunTimeError("Duplicate parameter names at:"); } } Var.FnDef fnDef = new Var.FnDef(ExecutingLineIndex, fnVar, fParms); fnVar.fnDef(fnDef); // add the definition to the Var if (mFunctionTable.put(fnVar.name(), fnDef) != null) { // and the global Function Table return RunTimeError("Duplicate function name at:"); } insertNewVar(fnVar); // put the Var in the symbol table int max = Basic.lines.size(); while (++ExecutingLineIndex < max) { // Now scan for the fn.end ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); if (ExecutingLineBuffer.startsWith("fn.end")) { // Break out when found return true; } if (ExecutingLineBuffer.startsWith("fn.def")) { // Insure not trying to define function in function return RunTimeError("Can not define a function within a function at:"); } } return RunTimeError("No fn.end for this function"); // end of program, fn.end not found } private boolean executeFN_RTN() { if (FunctionStack.empty()) { // Insure RTN actually called from executing function return RunTimeError("misplaced fn.rtn"); } CallStackFrame frame = FunctionStack.peek(); // Look at the top frame of the stack if (frame.fnDef().type().isNumeric()) { // to determine if function is string or numeric if (!evalNumericExpression()) return false; } else { if (!evalStringExpression()) return false; } if (!checkEOL()) return false; return endUserFunction(); } private boolean executeFN_END() { if (FunctionStack.empty()) { // Insure END actually called from executing function return RunTimeError("misplaced fn.end"); } CallStackFrame frame = FunctionStack.peek(); // Look at the top frame of the stack if (frame.fnDef().type().isNumeric()) { // to determine if function is string or numeric EvalNumericExpressionValue = 0.0; // Set default value } else { StringConstant = ""; // Set default value } if (!checkEOL()) return false; return endUserFunction(); } private boolean endUserFunction() { FunctionStack.pop().restore(); // Function execution done. Restore stuff. mSymbolTables.pop(); // discard the function's symbol table mSymbolTable = mSymbolTables.peek(); // restore the caller's symbol table fnRTN = true; // Signal RunLoop() to return return true; } private boolean executeCALL() { Var.FnDef fnDef = isUserFunction(false, false); // don't check type return ((fnDef != null) && doUserFunction(fnDef) && checkEOL()); } private Var.FnDef isUserFunction(boolean checkType, boolean isNumeric) { // if first arg is false, second is ignored if (mFunctionTable.isEmpty()) return null; // if function table empty, return fail int LI = LineIndex; Var var = parseVar(USER_FN_OK); // duplicate getVarAndType() except USER_FN_OK if ((var != null) && var.isFunction()) { // found a variable and it is a function name Var.FnDef fnDef = mFunctionTable.get(var.name()); if (fnDef != null) { // function must be defined already if (!checkType || (isNumeric == fnDef.type().isNumeric())) {// type matches or match not needed return fnDef; // success: return function definition } } } LineIndex = LI; // no match: rewind LineIndex return null; // and report fail } private boolean doUserFunction(Var.FnDef fnDef) { // execute function named in fnDef CallStackFrame frame = new CallStackFrame(); frame.store(fnDef); // build a stack frame ArrayList<Var.FunctionParameter> parms = fnDef.parms(); // parameters from FN.DEF int nParams = fnDef.nParms(); // number of parameters int nArgs = 0; // number of arguments, also parameter index if (nParams != 0) { // for each parameter do { if (nArgs >= nParams) { // ensure no more parms than defined return RunTimeError("Calling parameter count exceeds defined parameter count"); } Var.FunctionParameter parm = parms.get(nArgs++); // get parm, count the arg Var var = parm.var(); boolean byReference = isNext('&'); // optional for scalars, ignored for arrays parm.byReference(byReference); boolean typeIsNumeric = var.isNumeric(); if (var.isArray()) { // if this parm is an array Var callVar = getExistingArrayVar(); // get caller's array name var if (callVar == null) return false; var.arrayDef(callVar.arrayDef()); // get a reference to the caller's ArrayDef if (!isNext(']')) { // must be no indices return RunTimeError(EXPECT_ARRAY_NO_INDEX); } else if (typeIsNumeric != callVar.isNumeric()) { // insure type (string or number) match return RunTimeError("Array parameter type mismatch at:"); } } // end array else if (byReference) { // if this parm is var passed by reference Var callVar = getVarAndType(); if (callVar == null) return false; // then must be var not expression if (callVar.isNew()) { return RunTimeError("Call by reference vars must be predefined"); } if (typeIsNumeric != callVar.isNumeric()) { // insure type (string or number) match return RunTimeError("Parameter type mismatch at:"); } Var.Val callVal = getVarValue(callVar); // bottom half of getVar() if (callVal == null) return false; var.val(callVal); // get a reference to the caller's Val } // end by-reference else { var = var.copy(); // don't use the same Var for all calls parm.var(var); Var.Val val; // don't use the same Val, either if (!typeIsNumeric) { // if parm is string if (!evalStringExpression()) { // get the string value return RunTimeError("Parameter type mismatch at:"); } else { val = new Var.StrVal(StringConstant); } } else { if (!evalNumericExpression()) { // if parm is number get the numeric value return RunTimeError("Parameter type mismatch at:"); } else { val = new Var.NumVal(EvalNumericExpressionValue); } } var.val(val); // put the value in parm's var } // end scalar passed by value } while (isNext(',')); // keep going while caller parms exist // Loop may have created variables in caller's name space. No more will be created. } // end if if (nArgs != nParams) { return RunTimeError("Too few calling parameters at:"); } if (!isNext(')')) { return false; } // every function must have a closing right parenthesis frame.storeLI(); // passed ')', now save the line buffer index FunctionStack.push(frame); // push the stack frame mSymbolTable = new Var.Table(); // start a new local symbol table mSymbolTables.push(mSymbolTable); // and push it on the stack of tables // Start the new symbol table with the function parameter names. for (Var.FunctionParameter parm : parms) { insertNewVar(parm.var()); } ExecutingLineIndex = fnDef.line() + 1; // set to execute first line after fn.def statement fnRTN = false; // will be set true by fn.rtn // The function body is executed in a recursive call to RunLoop(). // FN.RTN or FN.END will signal RunLoop to exit, returning pass/fail state of the function here. return RunLoop(); // Note that the part of RunLoop after StatementExecuter runs twice, // once after FN.RTN/END and again now, when this method exits. } // doUserFunction // ************************************ Switch Statements ************************************* private boolean executeSW() { // Get Switch (SW) command keyword if it is there return executeSubcommand(sw_cmd, "SW"); } private SwitchDef scanSwitch() { // If this switch has already been scanned, use the stored SwitchDef. SwitchDef sw = (SwitchDef) (ExecutingLineBuffer.mExtras); if (sw != null) { return sw; } // Scan until "sw.end" or end of program. Remember CASE, DEFAULT, and END lines. ExecutingLineBuffer.mExtras = sw = new SwitchDef(); while (++ExecutingLineIndex < Basic.lines.size()) { ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); if (!ExecutingLineBuffer.startsWith("sw.")) { continue; } // not a switch command LineIndex = 3; // length of "sw." if (ExecutingLineBuffer.cmd() == null) { ExecutingLineBuffer.cmd(CMD_SW_GROUP); // same as result of searchCommands() } // cmd may be CMD_SW_GROUP if parsed while skipping THEN or ELSE clause of IF if (ExecutingLineBuffer.cmd() == CMD_SW_GROUP) {// find out which sw command Command c = findSubcommand(sw_cmd, "SW"); // body of executeSubcommand(sw_cmd, "SW") if (c == null) return null; // ... ExecutingLineBuffer.promoteSubCommand(); // ... to here } Command cmd = ExecutingLineBuffer.cmd(); if (cmd == CMD_SW_CASE) { sw.mCases.add(ExecutingLineIndex); } else if (cmd == CMD_SW_DEFAULT) { if (sw.mDefault != -1) { RunTimeError("Extra sw.default at:"); return null; } sw.mDefault = ExecutingLineIndex; LineIndex += ExecutingLineBuffer.offset(); if (!checkEOL()) return null; } else if (cmd == CMD_SW_BREAK) { ExecutingLineBuffer.mExtras = sw; // so BREAK can find END LineIndex += ExecutingLineBuffer.offset(); if (!checkEOL()) return null; } else if (cmd == CMD_SW_END) { sw.mEnd = ExecutingLineIndex; LineIndex += ExecutingLineBuffer.offset(); if (!checkEOL()) return null; return sw; // SUCCESSFUL EXIT } else if (cmd == CMD_SW_BEGIN) { // nested switch if (scanSwitch() == null) return null; } else return null; // ??? } RunTimeError("No sw.end after sw.begin"); return null; } // scanSwitch private boolean executeSW_BEGIN() { boolean isNumeric; double nexp = 0; String sexp = ""; if (evalNumericExpression()) { isNumeric = true; nexp = EvalNumericExpressionValue; } else if (evalStringExpression()) { isNumeric = false; sexp = StringConstant; } else return false; if (!checkEOL()) return false; // Scan until "sw.end" or end of program. // Store CASE, DEFAULT, and END lines. SwitchDef sw = scanSwitch(); if (sw == null) return false; // If any CASE lines were found, test their conditions. // Start execution at the first one that matches the BEGIN expression. for (int line : sw.mCases) { ExecutingLineIndex = line; ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); LineIndex = ExecutingLineBuffer.offset(); String text = ExecutingLineBuffer.text(); boolean found = false; char c = text.charAt(LineIndex); if ((c == '<') || (c == '>')) { // SW.CASE <2, SW.CASE >="a", SW.CASE <>"abc" ... // Create a new, temporary ProgramLine to test the condition: String test = '(' + ((isNumeric) ? String.valueOf(nexp) : quote(sexp)) + text.substring(LineIndex, text.length() - 1) + ")\n"; ExecutingLineBuffer = new ProgramLine(test); LineIndex = 0; boolean validCondition = evalNumericExpression() && checkEOL(); // Now that it's done we can revert back to the real ProgramLine and interpret the result: ExecutingLineBuffer = Basic.lines.get(ExecutingLineIndex); if (!validCondition) return false; LineIndex = text.length(); // Condition was valid -> skip it and go to EOL found = (0.0 != EvalNumericExpressionValue);// found if expression was true } else if (isNumeric) { // SW.CASE 0,1,2,... do { if (!evalNumericExpression()) return false; if (nexp == EvalNumericExpressionValue) { found = true; break; } } while (isNext(',')); } else { // SW.CASE "a","b","c",... do { if (!evalStringExpression()) return false; if (sexp.equals(StringConstant)) { found = true; break; } } while (isNext(',')); } if (found) { // good one -> ignore any remaining values if (!isEOL() && !isNext(',')) return false; // must be followed by comma or EOL return true; } } // for each case // No matching case found. ExecutingLineIndex = (sw.mDefault != -1) // is there DEFAULT? ? sw.mDefault // yes: jump to DEFAULT : sw.mEnd; // no: jump to END return true; } // executeSW_BEGIN private boolean executeSW_CASE() { return true; } private boolean executeSW_BREAK() { if (!checkEOL()) return false; SwitchDef sw = (SwitchDef) (ExecutingLineBuffer.mExtras); if (sw == null) { return RunTimeError("Misplaced SW.BREAK at:"); } ExecutingLineIndex = sw.mEnd; // jump to END return true; } private boolean executeSW_DEFAULT() { return true; } private boolean executeSW_END() { return true; } // **************************** End of core Basic Methods ***************************** // ******************************** Data I/O Operations ******************************** private boolean checkReadFile(int fileNumber) { // Validate input file for read commands if (FileTable.size() == 0) { RunTimeError("No files opened"); } else if (fileNumber < 0) { RunTimeError("Read file did not exist"); } else if (fileNumber >= FileTable.size()) { RunTimeError("Invalid File Number at"); } return !SyntaxError; // SyntaxError is true if RunTimeError was called } private boolean checkFile(int fileNumber) { // Validate input file number for read or write commands if (FileTable.size() == 0) { RunTimeError("No files opened"); } else if (fileNumber >= FileTable.size() || fileNumber < 0) { RunTimeError("Invalid File Number at"); } return !SyntaxError; // SyntaxError is true if RunTimeError was called } private boolean checkFileType(FileInfo fInfo, FileType fType) { switch (fType) { case FILE_TEXT: if (!fInfo.isText()) { return RunTimeError("File not opened for text"); } break; case FILE_BYTE: if (fInfo.isText()) { return RunTimeError("File not opened for byte"); } break; } return true; } private boolean checkReadAttributes(FileInfo fInfo, FileType fType) { // Validate common FileInfo items for commands that read text files if (!checkFileType(fInfo, fType)) return false; // Type TEXT or BYTE as requested? else if (fInfo.isClosed()) { RunTimeError("File is closed"); } else if (fInfo.mode() != FMR) { RunTimeError("File not opened for read at"); } return !SyntaxError; // SyntaxError is true if RunTimeError was called } private boolean checkWriteAttributes(FileInfo fInfo, FileType fType) { // Validate common FileInfo items for commands that write text files if (!checkFileType(fInfo, fType)) return false; // Type TEXT or BYTE as requested? else if (fInfo.isClosed()) { RunTimeError("File is closed"); } else if (fInfo.mode() != FMW) { RunTimeError("File not opened for write at"); } return !SyntaxError; // SyntaxError is true if RunTimeError was called } // ************************************* Text Stream I/O ************************************** private boolean executeTEXT() { // Get Text command keyword if it is there Command c = findSubcommand(text_cmd, "Text"); if (c == null) return false; if (c.id == CID_EX) { // can't do common processing return c.run(); // just run it } if (!evalNumericExpression()) return false; // first parm is the file number expression int fileNumber = EvalNumericExpressionValue.intValue(); if (c.id == CID_READ) { if (!checkReadFile(fileNumber)) return false; // check runtime errors } else if (c.id == CID_WRITE) { if (!checkFile(fileNumber)) return false; // check runtime errors } return c.run(fileNumber); } private boolean executeTEXT_OPEN() { // Open a file boolean append = false; // Assume not append int FileMode = 0; // Default to FMR clearErrorMsg(); switch (ExecutingLineBuffer.text().charAt(LineIndex)) { // First parm is a, w or r case 'a': append = true; // append is a special case of write case 'w': // write FileMode = FMW; ++LineIndex; break; case 'r': // read FileMode = FMR; ++LineIndex; } if (!isNext(',')) return false; if (!getNVar()) return false; // Next parameter is the file number variable Var.Val val = mVal; double fileNumber = FileTable.size(); if (!isNext(',')) return false; if (!getStringArg()) return false; // Final parameter is the filename String fileName = StringConstant; if (!checkEOL()) return false; File file = new File(Basic.getDataPath(fileName)); if (FileMode == FMR) { // Read was selected TextReaderInfo fInfo = new TextReaderInfo(FileMode); // Prepare the FileTable object BufferedReader buf = null; int flen = (int) Math.min(file.length(), Integer.MAX_VALUE); try { buf = Basic.getBufferedReader(Basic.DATA_DIR, fileName, Basic.Encryption.NO_DECRYPTION); if (buf == null) { writeErrorMsg(fileName + " not found"); } else if (buf.markSupported()) { buf.mark(flen); fInfo.mark(1, flen); } } catch (Exception e) { writeErrorMsg(e); } if (buf != null) { fInfo.mTextReader = buf; // store reader in fInfo } else { fileNumber = -1; // change file index to report file does not exist } FileTable.add(fInfo); } else if (FileMode == FMW) { // Write Selected TextWriterInfo fInfo = new TextWriterInfo(FileMode); // Prepare the FileTable object // fInfo.mPosition is 1: absolute if 'w' and relative to old EOF if 'a'. FileWriter writer = null; if (!append || !file.exists()) { // if not appending overwrite existing file try { file.createNewFile(); } // if no file create a new one catch (IOException e) { writeErrorMsg(e); } } if (file.exists() && file.canWrite()) { try { writer = new FileWriter(file, append); } // open the filewriter for the SD Card catch (IOException e) { writeErrorMsg(e); } } if (writer != null) { fInfo.mTextWriter = writer; // store writer in fInfo } else { fileNumber = -1; // change file index to report file does not exist } FileTable.add(fInfo); } val.val(fileNumber); // return the file index return true; // Done } private boolean executeTEXT_READLN(int fileNumber) { // first parameter: file table index List<Var.Val> valList = new ArrayList<Var.Val>(); while (isNext(',')) { // list of zero or more if (!getSVar()) return false; // numeric variable valList.add(mVal); // to hold the data } if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); if (!checkReadAttributes(fInfo, FileType.FILE_TEXT)) return false; int expect = valList.size(); int actual = 0; if (!fInfo.isEOF()) { // if already eof don't read BufferedReader buf = ((TextReaderInfo) fInfo).mTextReader; String[] values = new String[expect]; try { for (; actual < expect; ++actual) { values[actual] = buf.readLine(); // read a line if (values[actual] == null) { // hit eof fInfo.eof(true); // mark fInfo break; } } } catch (IOException e) { return RunTimeError("I/O error at:"); } for (int i = 0; i < actual; ++i) { valList.get(i).val(values[i]); // give the data to the user } fInfo.incPosition(actual); // update position in Bundle } for (int i = actual; i < expect; ++i) { // if any array space is left valList.get(i).val("EOF"); // fill it with EOF markers } return true; } private boolean executeTEXT_WRITELN(int fileNumber) { // first parameter: file table index if (!isNext(',')) return false; // set up to parse the stuff to print if (!buildPrintLine(textPrintLine, "\r\n")) return false; // build up the text line in StringConstant if (!PrintLineReady) { // flag set by buildPrintLine textPrintLine = StringConstant; // not ready to print; hold line return true; // and wait for next Text.Writeln command } textPrintLine = ""; // clear the accumulated text print line FileInfo fInfo = FileTable.get(fileNumber); if (!checkWriteAttributes(fInfo, FileType.FILE_TEXT)) return false; FileWriter writer = ((TextWriterInfo) fInfo).mTextWriter; try { writer.write(StringConstant); } catch (IOException e) { return RunTimeError("I/O error at"); } fInfo.incPosition(); // update position in fInfo return true; } private boolean executeTEXT_INPUT() { if (!getSVar()) return false; Var.Val val = mVal; // variable to hold the data TextInputString = ""; String title = null; if (isNext(',')) { // Check for optional parameter(s) boolean isComma = isNext(','); // Look for second comma, two commas together // mean initial text is skipped, use empty string if (!isComma) { if (!getStringArg()) return false; // One comma so far; get initial input text TextInputString = StringConstant; isComma = isNext(','); // Look again for second comma } if (isComma) { if (!getStringArg()) return false; // Second comma; get title title = StringConstant; } } if (!checkEOL()) return false; Intent intent = new Intent(Run.this, TextInput.class); if (title != null) { intent.putExtra("title", title); } mWaitForLock = true; startActivityForResult(intent, BASIC_GENERAL_INTENT); waitForLOCK(); // Wait for signal from TextInput.java thread val.val(TextInputString); return true; } private boolean executeTEXT_POSITION_SET(int fileNumber) { // first parameter: file table index if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // second parm is the position expression long pto = EvalNumericExpressionValue.longValue(); if (pto < 1) { return RunTimeError("Set position must be >= 1"); } if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); if (!checkReadAttributes(fInfo, FileType.FILE_TEXT)) return false; long pnow = fInfo.position(); boolean eof = fInfo.isEOF(); BufferedReader buf = ((TextReaderInfo) fInfo).mTextReader; if (pto < pnow) { try { buf.reset(); } // back to mark, exception if mark invalid catch (IOException e) { return RunTimeError(e); } eof = false; long pmark = fInfo.mark(); pnow = (pmark > 0) ? pmark : 1; // pmark should not be 0 } String data = null; while ((pnow < pto) && !eof) { try { data = buf.readLine(); } // read a line catch (Exception e) { return RunTimeError(e); } if (data == null) { eof = true; // hit eof, record in fInfo } else { ++pnow; // not eof, update position in fInfo } } fInfo.position(pnow); // update fInfo fInfo.eof(eof); return true; } private boolean executeTGET() { if (!getSVar()) return false; Var.Val val = mVal; // variable to hold the data if (!isNext(',')) return false; if (!getStringArg()) return false; TextInputString = StringConstant; String Prompt = StringConstant; String title = null; if (isNext(',')) { if (!getStringArg()) return false; title = StringConstant; } if (!checkEOL()) return false; checkpointMessage(); // allow any pending Console activity to complete while (mMessagePending) { Thread.yield(); } // wait for checkpointMessage semaphore to clear Intent intent = new Intent(Run.this, TGet.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); if (title != null) { intent.putExtra(TGet.TITLE, title); } synchronized (mConsoleBuffer) { intent.putStringArrayListExtra(TGet.CONSOLE_TEXT, mOutput); } TGet.mMenuStop = false; mWaitForLock = true; startActivityForResult(intent, BASIC_GENERAL_INTENT); waitForLOCK(); // wait for signal from TGet.java thread if (TGet.mMenuStop) { // user selected Stop from TGet menu // code copied from onOptionsItemSelected PrintShow("Stopped by user."); // tell user Stop = true; // signal main loop to stop disableInterrupt(Interrupt.BACK_KEY_BIT); // menu-selected stop is not trappable } else { PrintShow(Prompt + TextInputString); val.val(TextInputString); } return true; } // ************************************* Byte Stream I/O ************************************** private boolean executeBYTE() { // Get Byte command keyword if it is there Command c = findSubcommand(byte_cmd, "Byte"); if (c == null) return false; if (c.id == CID_EX) { // can't do common processing return c.run(); // just run it } if (!evalNumericExpression()) return false; // first parm is the file number expression int fileNumber = EvalNumericExpressionValue.intValue(); if (c.id == CID_READ) { if (!checkReadFile(fileNumber)) return false; // check runtime errors } else if (c.id == CID_WRITE) { if (!checkFile(fileNumber)) return false; // check runtime errors } return c.run(fileNumber); } private boolean executeBYTE_OPEN() { // Open a file boolean append = false; // Assume not append int FileMode = 0; // Default to FMR clearErrorMsg(); switch (ExecutingLineBuffer.text().charAt(LineIndex)) { // First parm is a, w or r case 'a': append = true; // append is a special case of write case 'w': // write FileMode = FMW; ++LineIndex; break; case 'r': // read FileMode = FMR; ++LineIndex; } if (!isNext(',')) return false; if (!getNVar()) return false; // Next parameter is the file number variable Var.Val val = mVal; double fileNumber = FileTable.size(); if (!isNext(',')) return false; if (!getStringArg()) return false; // Final parameter is the filename String fileName = StringConstant; if (!checkEOL()) return false; if (FileMode == FMR) { // Read was selected ByteReaderInfo fInfo = new ByteReaderInfo(FileMode); // Prepare the FileTable object BufferedInputStream buf = null; if (fileName.startsWith("http")) { try { URL url = new URL(fileName); URLConnection connection = url.openConnection(); buf = new BufferedInputStream(connection.getInputStream()); } catch (Exception e) { writeErrorMsg(e); } } else { File file = new File(Basic.getDataPath(fileName)); int flen = (int) Math.min(file.length(), Integer.MAX_VALUE); try { buf = Basic.getBufferedInputStream(Basic.DATA_DIR, fileName); if (buf == null) { writeErrorMsg(file + " not found"); } else if (buf.markSupported()) { buf.mark(flen); fInfo.mark(1, flen); } } catch (Exception e) { writeErrorMsg(e); } } if (buf != null) { fInfo.mByteReader = buf; // store reader in fInfo } else { fileNumber = -1; // change file index to report file does not exist } FileTable.add(fInfo); } else if (FileMode == FMW) { // Write Selected ByteWriterInfo fInfo = new ByteWriterInfo(FileMode); // Prepare the FileTable object // fInfo.mPosition is 1: absolute if 'w' and relative to old EOF if 'a'. FileOutputStream fos = null; File file = new File(Basic.getDataPath(fileName)); if (append && file.exists()) { fInfo.position(file.length() + 1); } else { // if not appending overwrite existing file try { file.createNewFile(); } // if no file create a new one catch (IOException e) { writeErrorMsg(e); } } if (file.exists() && file.canWrite()) { try { fos = new FileOutputStream(file.getAbsolutePath(), append); } catch (Exception e) { writeErrorMsg(e); } } if (fos != null) { fInfo.mByteWriter = fos; // store writer in fInfo } else { fileNumber = -1; // change file index to report file does not exist } FileTable.add(fInfo); } val.val(fileNumber); // return the file index return true; // Done } private boolean executeBYTE_COPY(int srcFileNumber) { // first parameter: source file table index if (!isNext(',')) return false; if (!evalStringExpression()) return false; // second parm is the destination file name if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(srcFileNumber); // source file info if (!checkReadAttributes(fInfo, FileType.FILE_BYTE)) return false; if (fInfo.isEOF()) { return RunTimeError("Attempt to read beyond the EOF at:"); } BufferedInputStream bis = ((ByteReaderInfo) fInfo).mByteReader; String theFileName = StringConstant; File file = new File(Basic.getDataPath(theFileName)); try { file.createNewFile(); } catch (IOException e) { return RunTimeError(theFileName + " I/O Error"); } if (!file.exists() || !file.canWrite()) { return RunTimeError("Problem opening " + theFileName); } return copyFile(bis, file, fInfo); } // Bottom half of Byte.Copy: copy input stream to output file. Close the streams. // For shared use, FileInfo may be null. private boolean copyFile(BufferedInputStream bis, File outFile, FileInfo fInfo) { if ((bis == null) || (outFile == null)) return false; long p = (fInfo != null) ? fInfo.position() : 0; IOException ex = null; BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(outFile), 8192); byte[] buffer = new byte[1024]; int len = 0; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); p += len; } } catch (IOException e) { ex = e; return RunTimeError("Exception: " + e); } finally { ex = FileInfo.closeStream(bis, ex); ex = FileInfo.closeStream(bos, FileInfo.flushStream(bos, ex)); if (ex != null) { return !SyntaxError && RunTimeError(ex); } } if (fInfo != null) { // update fInfo, if there is one fInfo.position(p); fInfo.eof(true); } return true; } private FileInfo getByteReadParams(int fileNumber, List<Var.Val> valList) { // return FileInfo or null if error // return list of variables in varList while (isNext(',')) { // list of zero or more if (!getNVar()) return null; // numeric variable valList.add(mVal); // to hold the data } if (!checkEOL()) return null; FileInfo fInfo = FileTable.get(fileNumber); return (checkReadAttributes(fInfo, FileType.FILE_BYTE)) ? fInfo : null; } private FileInfo getByteWriteParams(int fileNumber, List<Double> nvalList, List<String> svalList) { // return FileInfo or null if error // return list of numeric values in nvalList // or single string value in svalList boolean isComma = isNext(','); if (isComma) { if (evalNumericExpression()) { // second param may be numeric expression nvalList.add(EvalNumericExpressionValue); while (isComma = isNext(',')) { if (!evalNumericExpression()) break; // list of numeric expressions nvalList.add(EvalNumericExpressionValue); } } if (isComma) { // last param may be string expression if (!getStringArg()) return null; // note: potential double-evaluation if (svalList != null) { svalList.add(StringConstant); } } } if (!checkEOL()) return null; FileInfo fInfo = FileTable.get(fileNumber); return (checkWriteAttributes(fInfo, FileType.FILE_BYTE)) ? fInfo : null; } private boolean executeBYTE_READ_BYTE(int fileNumber) { // first parameter: file table index List<Var.Val> valList = new ArrayList<Var.Val>(); FileInfo fInfo = getByteReadParams(fileNumber, valList); // zero or more variables for data bytes if (fInfo == null) return false; int expect = valList.size(); int actual = 0; if (!fInfo.isEOF()) { // if already eof don't read DataInputStream dis = ((ByteReaderInfo) fInfo).getDIS(); byte[] values = new byte[expect]; try { actual = dis.read(values); } // read the byte(s) catch (IOException e) { return RunTimeError(e); } if (actual < 0) { actual = 0; } // -1 if started at EOF if (actual < expect) { fInfo.eof(true); } // hit eof, mark fInfo for (int i = 0; i < actual; ++i) { int datum = values[i] & 0x00FF; // prevent sign-extension valList.get(i).val(datum); // give the data to the user } fInfo.incPosition(actual); // update position in fInfo } for (int i = actual; i < expect; ++i) { valList.get(i).val(-1); // eof markers if needed } return true; } private boolean executeBYTE_READ_NUMBER(int fileNumber) { // first parameter: file table index) List<Var.Val> valList = new ArrayList<Var.Val>(); FileInfo fInfo = getByteReadParams(fileNumber, valList); if (fInfo == null) return false; int expect = valList.size(); int actual = 0; if (!fInfo.isEOF()) { // if already eof don't read DataInputStream dis = ((ByteReaderInfo) fInfo).getDIS(); double[] values = new double[expect]; try { for (; actual < expect; ++actual) { values[actual] = dis.readDouble(); // read a double } } catch (EOFException eof) { fInfo.eof(true); } // hit eof, mark fInfo catch (IOException e) { return RunTimeError("I/O error at:"); } for (int i = 0; i < actual; ++i) { valList.get(i).val(values[i]); // give the data to the user } fInfo.incPosition(actual * 8); // update position in Bundle } for (int i = actual; i < expect; ++i) { // if any array space is left valList.get(i).val(-1); // fill it with EOF markers } return true; } private boolean executeBYTE_READ_BUFFER(int fileNumber) { // first parameter: file table index if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // second parm is the byte count int expect = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; // third parm is the return buffer string variable if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); if (!checkReadAttributes(fInfo, FileType.FILE_BYTE)) return false; String buff = ""; if (!fInfo.isEOF()) { // if already eof don't read DataInputStream dis = ((ByteReaderInfo) fInfo).getDIS(); byte[] values = new byte[expect]; int actual = 0; try { actual = dis.read(values); } // read the bytes catch (IOException e) { return RunTimeError(e); } if (actual < 0) { actual = 0; } // -1 if started at EOF if (actual > 0) { try { buff = new String(values, "ISO-8859-1"); } // convert bytes to string for user catch (UnsupportedEncodingException ex) { return RunTimeError(ex); // can't happen: "ISO-8859-1" is supported } if (actual < expect) { buff = buff.substring(0, actual); fInfo.eof(true); // hit eof, mark fInfo } fInfo.incPosition(actual); // update position in fInfo } } val.val(buff); // give the data to the user return true; } private boolean executeBYTE_WRITE_BYTE(int fileNumber) { // first parameter: file table index List<Double> nvalList = new ArrayList<Double>(); List<String> svalList = new ArrayList<String>(); FileInfo fInfo = getByteWriteParams(fileNumber, nvalList, svalList); // expect zero or more nval and zero or one sval if (fInfo == null) return false; if (svalList.size() > 1) return false; if ((nvalList.size() == 0) && (svalList.size() == 0)) return true; // nothing to do return writeBytes(fInfo, nvalList, svalList); } private boolean executeBYTE_WRITE_NUMBER(int fileNumber) { // first parameter: file table index List<Double> nvalList = new ArrayList<Double>(); List<String> svalList = new ArrayList<String>(); FileInfo fInfo = getByteWriteParams(fileNumber, nvalList, svalList); // expect zero or more nval and no sval if (fInfo == null) return false; if (svalList.size() != 0) return false; if (nvalList.size() == 0) return true; // nothing to do DataOutputStream dos = ((ByteWriterInfo) fInfo).getDOS(); for (Double val : nvalList) { try { dos.writeDouble(val); } // write the number catch (IOException e) { return RunTimeError("I/O error at"); } } fInfo.incPosition(8 * nvalList.size()); // size of numbers written by dos.writeDouble return true; } private boolean executeBYTE_WRITE_BUFFER(int fileNumber) { // first parameter: file table index List<String> svalList = new ArrayList<String>(); FileInfo fInfo = getByteWriteParams(fileNumber, null, svalList); // expect no nvals and a single sval if (fInfo == null) return false; if (svalList.size() == 0) return true; // nothing to do if (svalList.size() != 1) return false; return writeBytes(fInfo, null, svalList); } private boolean writeBytes(FileInfo fInfo, List<Double> nvalList, List<String> svalList) { FileOutputStream fos = ((ByteWriterInfo) fInfo).mByteWriter; if ((nvalList != null) && (nvalList.size() != 0)) { for (Double val : nvalList) { // write the numeric byte(s) try { fos.write(val.byteValue()); } catch (IOException e) { return RunTimeError("I/O error at"); } } fInfo.incPosition(nvalList.size()); } if ((svalList != null) && (svalList.size() != 0)) { for (String s : svalList) { // write the bytes from the string(s) int len = s.length(); for (int k = 0; k < len; ++k) { try { fos.write((byte) s.charAt(k)); } catch (IOException e) { return RunTimeError("I/O error at"); } } fInfo.incPosition(len); } } return true; } private boolean executeBYTE_TRUNCATE(int fileNumber) { // truncate a file opened for write // first parameter: file table index if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // second parm is the truncation length long length = EvalNumericExpressionValue.longValue(); if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); if (!checkWriteAttributes(fInfo, FileType.FILE_BYTE)) return false; try { ((ByteWriterInfo) fInfo).truncateFile(length); } catch (IOException ex) { return RunTimeError(ex); } return true; } private boolean executeBYTE_POSITION_SET(int fileNumber) { // first parameter: file table index if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // second parm is the position expression long pto = EvalNumericExpressionValue.longValue(); if (pto < 1) { return RunTimeError("Set position must be >= 1"); } if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); if (!checkReadAttributes(fInfo, FileType.FILE_BYTE)) return false; BufferedInputStream bis = ((ByteReaderInfo) fInfo).mByteReader; long pnow = fInfo.position(); boolean eof = fInfo.isEOF(); if (pto < pnow) { try { bis.reset(); } // back to mark, exception if mark invalid catch (IOException e) { return RunTimeError(e); } eof = false; long pmark = fInfo.mark(); pnow = (pmark > 0) ? pmark : 1; // pmark should not be 0 } if ((pnow != pto) && !eof) { long skip = pto - pnow - 1; // Skip plus single read will get to target position long skipped = 0; int data = -1; do { try { int avail = bis.available(); // Don't skip past eof skipped += bis.skip(Math.min(skip - skipped, avail)); data = bis.read(); // Read to check eof } catch (Exception e) { return RunTimeError(e); } if (data >= 0) { ++skipped; } // If byte was read, count it else { eof = true; break; } // otherwise mark eof in Bundle } while (skipped < skip); pnow += skipped; // Count bytes skipped } fInfo.position(pnow); // update fInfo fInfo.eof(eof); return true; } // ************************** Text or Byte Stream: Common Operations ************************** private boolean executeCLOSE(FileType fType) { if (FileTable.size() == 0) return true; if (!evalNumericExpression()) return false; // First parm is the file number expression int fileNumber = EvalNumericExpressionValue.intValue(); if (!checkFile(fileNumber)) return false; // Check runtime errors if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkFileType(fInfo, fType)) return false; // Type TEXT or BYTE as requested? if (fInfo.isClosed()) return true; // Already closed IOException e = fInfo.close(fInfo.flush(null)); // flush is no-op on read types if (e != null) { return RunTimeError(e); } return true; } private boolean executeEOF(int fileNumber, FileType fType) { if (!isNext(',') || !getNVar()) return false; // Second parm is the logical (numeric) variable Var.Val val = mVal; // to hold the return value if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkFileType(fInfo, fType)) return false; // Type TEXT or BYTE as requested? boolean eof = fInfo.isClosed() || fInfo.isEOF(); // if closed or eof return true, else false val.val(eof ? 1.0 : 0.0); // return boolean as numeric return true; } private boolean executePOSITION_GET(int fileNumber, FileType fType) { if (!isNext(',')) return false; // Second parm is the position var if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkFileType(fInfo, fType)) return false; // Type TEXT or BYTE as requested? val.val(fInfo.position()); return true; } private boolean executePOSITION_MARK(FileType fType) { int fileNumber; int markLimit = -1; boolean isComma = isNext(','); if (!isComma && !isEOL()) { // there is a file pointer arg if (!evalNumericExpression()) return false; fileNumber = EvalNumericExpressionValue.intValue(); isComma = isNext(','); } else { fileNumber = FileTable.size() - 1; // default if no file pointer arg } if (!checkReadFile(fileNumber)) return false; // check runtime errors if (isComma) { // second parm is the mark limit if (!evalNumericExpression()) return false; markLimit = EvalNumericExpressionValue.intValue(); if (markLimit < 0) { markLimit = 0; } } if (!checkEOL()) return false; FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkReadAttributes(fInfo, fType)) return false; // Check runtime errors if (markLimit < 0) { markLimit = fInfo.markLimit(); // retrieve mark limit from fInfo } switch (fType) { case FILE_TEXT: BufferedReader buf = ((TextReaderInfo) fInfo).mTextReader; try { buf.mark(markLimit); } // set the mark catch (IOException e) { return RunTimeError("I/O error at:"); } break; case FILE_BYTE: BufferedInputStream bis = ((ByteReaderInfo) fInfo).mByteReader; bis.mark(markLimit); // set the mark break; } fInfo.markCurrentPosition(markLimit); // update the fInfo return true; } // ************************************* File Operations ************************************** private boolean checkSDCARD(char mount) { // mount is 'w' for writable, // 'r' for either readable or writable return Basic.checkSDCARD(mount) ? true : RunTimeError("SDCARD not available."); } private boolean executeMKDIR() { if (!getStringArg()) return false; // get the path String path = StringConstant; if (!checkEOL()) return false; File file = new File(Basic.getDataPath(path)); file.mkdirs(); if (!file.exists()) { // did we get a dir? return RunTimeError(path + " mkdir failed"); } return true; } private boolean executeRENAME() { if (!getStringArg()) return false; // get the old file name String Old = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the new file name String New = StringConstant; if (!checkEOL()) return false; if (!checkSDCARD('w')) return false; File oldFile = new File(Basic.getDataPath(Old)); if (!oldFile.exists()) { // does the file exist? return RunTimeError(Old + " directory/file not in this path"); } File newFile = new File(Basic.getDataPath(New)); if (!oldFile.renameTo(newFile)) { // try to rename it return RunTimeError("Rename of " + Old + " to " + New + " failed"); } return true; } private boolean executeDELETE() { if (!getNVar()) return false; // get the var to put the result into Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file name String fileName = StringConstant; if (!checkEOL()) return false; if (!checkSDCARD('w')) return false; File file = new File(Basic.getDataPath(fileName)); double result = file.delete() ? 1 : 0; // try to delete it val.val(result); return true; } private boolean executeFILE() { // Get File command keyword if it is there return executeSubcommand(file_cmd, "File"); } private boolean executeFILE_EXISTS() { if (!getNVar()) return false; // get the var to put the result into Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file name String fileName = StringConstant; if (!checkEOL()) return false; if (!checkSDCARD('r')) return false; double exists = 0.0; // "false" if (!fileName.equals("")) { // empty file name would report parent dir exists; catch it and report false File file = new File(Basic.getDataPath(fileName)); if (file.exists()) exists = 1.0; // if file exists, report "true" } val.val(exists); return true; } private boolean isResourceFile(int resID) { boolean isFile = false; if (resID != 0) { InputStream inputStream = null; try { inputStream = getResources().openRawResource(resID); isFile = true; } catch (Exception ex) { } // eat exception and return false finally { FileInfo.closeStream(inputStream, null); } } return isFile; } private String getAssetType(String assetPath) { // return type "d" or "f", or null if not found AssetManager am = getAssets(); try { String[] list = am.list(assetPath); if (list.length != 0) { return "d"; } // it's a directory (no empty directories in assets) } catch (IOException e) { } try { // only works for some file extensions AssetFileDescriptor afd = am.openFd(assetPath); try { afd.close(); } catch (IOException e) { } // clean up return "f"; // it's a file } catch (IOException e) { Log.d(LOGTAG, "getAssetType:openFD:" + e); } try { // last ditch, should always work InputStream is = am.open(assetPath); try { is.close(); } catch (IOException e) { } // clean up return "f"; // it's a file } catch (IOException e) { Log.d(LOGTAG, "getAssetType:open:" + e); } return null; // asset not found } private long getResourceSize(String fileName) { long size = -1; int resID = Basic.getRawResourceID(fileName); if (resID != 0) { InputStream inputStream = null; try { inputStream = getResources().openRawResource(resID); size = inputStream.available(); } catch (Exception ex) { } // eat exception and return -1 finally { FileInfo.closeStream(inputStream, null); } } return size; } private long getAssetSize(String fileName) { // get the size of a file in assets String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, fileName); AssetManager am = getAssets(); try { String[] list = am.list(assetPath); if (list.length != 0) { return 0; } // it's a directory (no empty directories in assets) } catch (IOException e) { } long size = AssetFileDescriptor.UNKNOWN_LENGTH; try { // try the easy way: AssetFileDescriptor afd = am.openFd(assetPath); // get afd size = afd.getLength(); // and ask it for the length try { afd.close(); } catch (IOException e) { } // clean up if (size != AssetFileDescriptor.UNKNOWN_LENGTH) { return size; } } catch (IOException e) { Log.d(LOGTAG, "getAssetSize:openFD:" + e); } BufferedInputStream bis = null; try { // no afd or length unknown InputStream is = am.open(assetPath); // open the file size = is.available(); // and check available if (size != AssetFileDescriptor.UNKNOWN_LENGTH) { return size; } // Opened file but length is unknown. // Last ditch attempt: read the file and count its bytes byte[] bytes = new byte[8192]; long bytesRead = 0; bis = new BufferedInputStream(is); for (int count = 0; count != -1; count = bis.read(bytes, 0, 8192)) { bytesRead += count; } size = bytesRead; } catch (IOException e) { Log.d(LOGTAG, "getAssetSize:open:" + e); } finally { FileInfo.closeStream(bis, null); } return ((size == AssetFileDescriptor.UNKNOWN_LENGTH) ? -1 : size); } private boolean executeFILE_SIZE() { if (!getNVar()) return false; // get the var to put the size value into Var.Val val = mVal; long size = -1; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file name String fileName = StringConstant; if (!checkEOL()) return false; if (!checkSDCARD('r')) return false; File file = new File(Basic.getDataPath(fileName)); if (file.exists()) { size = file.length(); // the file exists } else { // the file does not exist if (Basic.isAPK) { // we are in APK size = getResourceSize(fileName); // try to get it from raw resource if (size == -1) { // resource not found size = getAssetSize(fileName); // try to get it from assets } } } if (size == -1) { // not file, resource, or asset return RunTimeError(fileName + " not found"); } val.val(size); // Put the file size into the var return true; } private boolean executeFILE_TYPE() { if (!getSVar()) return false; // get the var to put the type info into Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file name String fileName = StringConstant; if (!checkEOL()) return false; if (!checkSDCARD('r')) return false; String type = "x"; // assume does not exist File file = new File(Basic.getDataPath(fileName)); if (file.exists()) { type = file.isDirectory() ? "d" : file.isFile() ? "f" : "o"; } else { // does not exist in file system if (Basic.isAPK) { // we are in APK int resID = Basic.getRawResourceID(fileName); if (resID != 0) { type = isResourceFile(resID) ? "f" : "o"; // file or other, can't be a directory } else { String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, fileName); String aType = getAssetType(assetPath); // file or directory, can't be other if (aType != null) { type = aType; } // else "x", does not exist } } // else "x", does not exist } val.val(type); // put the file type into the var return true; } private boolean executeFILE_ROOT() { if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; if (!checkSDCARD('r')) return false; val.val(Basic.getDataPath(null)); // return canonical path to default data directory return true; } private boolean executeDIR() { if (!getStringArg()) return false; // get the path String filePath = StringConstant; if (!isNext(',')) return false; // parse the ,D$[] Var var = getArrayVarForWrite(TYPE_STRING); // get the result array variable if (var == null) return false; // must name a new string array variable String dirMark = "(d)"; if (isNext(',')) { // optional directory marker if (!getStringArg()) return false; dirMark = StringConstant; } if (!checkEOL()) return false; // line must end with ']' File lbDir = new File(Basic.getDataPath(filePath)); if (!lbDir.exists()) { // error if directory does not exist return RunTimeError(filePath + " is invalid path"); } ArrayList<String> files = new ArrayList<String>(); ArrayList<String> dirs = new ArrayList<String>(); String FL[] = lbDir.list(); // get the list of files in the dir if (FL == null) { // if not a dir dirs.add(" "); // make list with one element } else { // Go through the file list and mark directory entries with dirMark String absPath = lbDir.getAbsolutePath() + '/'; for (String s : FL) { File test = new File(absPath + s); if (test.isDirectory()) { // If file is a directory dirs.add(s + dirMark); // mark it and add it to display list } else { files.add(s); // else add name without the directory mark } } Collections.sort(dirs); // Sort the directory list Collections.sort(files); // Sort the file list dirs.addAll(files); // copy the file list to end of dir list } int length = dirs.size(); // number of directories and files in list if (length == 0) { length = 1; } // make at least one element if dir is empty // it will be an empty string return ListToBasicStringArray(var, dirs, length); // Copy the list to a BASIC! array } private boolean executeGRABFILE() { clearErrorMsg(); if (!getSVar()) return false; // First parm is string var Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // Second parm is the filename String theFileName = StringConstant; boolean textFlag = false; // Default: assume ASCII/binary if (isNext(',')) { // Optional third parm if (!evalNumericExpression()) return false; textFlag = (EvalNumericExpressionValue != 0.0); // is the text flag: unicode if non-zero } if (!checkEOL()) return false; if (!checkSDCARD('r')) return false; BufferedInputStream bis = null; String result = ""; IOException ioex = null; Exception ex = null; try { bis = Basic.getBufferedInputStream(Basic.DATA_DIR, theFileName); if (bis != null) { result = grabStream(bis, textFlag); } else { writeErrorMsg("File not found"); } } catch (FileNotFoundException fnfe) { writeErrorMsg("File not found"); } catch (IOException ie) { ioex = ie; } catch (NotFoundException nfe) { writeErrorMsg("Resource not found"); } catch (Exception e) { ex = e; } finally { ioex = FileInfo.closeStream(bis, ioex); if (ioex != null) { return RunTimeError(ioex); } // Report first exception, if any, and if no previous RTE set if (ex != null) { return RunTimeError(ex); } } val.val(result); return true; } private boolean executeGRABURL() { if (!getSVar()) return false; // First parm is string var Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // Second parm is the url int timeoutMillis = 0; // Default: assume infinite timeout if (isNext(',')) { // Optional third parm if (!evalNumericExpression()) return false; timeoutMillis = EvalNumericExpressionValue.intValue(); // is the timeout: infinite if 0 if (timeoutMillis < 0) { timeoutMillis *= -1; } // negative value would throw an exception } if (!checkEOL()) return false; BufferedInputStream bis = null; String result = ""; URL url = null; try { // This assumes that you have a URL from which the response will come url = new URL(StringConstant); // Open a connection to the URL and obtain a buffered input stream URLConnection connection = url.openConnection(); if (timeoutMillis != 0) { connection.setConnectTimeout(timeoutMillis); connection.setReadTimeout(timeoutMillis); } InputStream inputStream = connection.getInputStream(); bis = new BufferedInputStream(inputStream); if (bis != null) { result = grabStream(bis, true); } // Read as encoded text stream, not byte stream else { writeErrorMsg("Problem opening or reading URL"); } // Alternate implementation: uncomment this catch block to handle Timeout explicitly. // } catch (SocketTimeoutException ste) { // Connect or Read timeout // result = ""; // Empty result, finally will close stream but will not return } catch (Exception e) { // Report exception in run time error writeErrorMsg(e); } finally { IOException ex = FileInfo.closeStream(bis, null); // Close stream if not already closed if (ex != null) { return RunTimeError(ex); } // Report first exception, if any, and if no previous RTE set } val.val(result); return true; } private String grabStream(BufferedInputStream bis, boolean textFlag) throws IOException { ByteArrayBuffer byteArray = new ByteArrayBuffer(1024 * 8); int current = 0; // Read from the stream into a byte array while ((current = bis.read()) != -1) { byteArray.append((byte) current); } // Construct a String object from the byte array containing the response if (textFlag) { return new String(byteArray.toByteArray()); // Text: keep full two-byte encoding, assumes input is UTF-8 } else { return new String(byteArray.toByteArray(), "ISO-8859-1");// ASCII or binary: force upper byte 0 } } // ************************************** Time and TimeZone commands ************************** private boolean executeTIME() { // Get the date and time Time time = theTimeZone.equals("") ? new Time() : new Time(theTimeZone); // If user has set a time zone, use it if (evalNumericExpression()) { // If there is a numeric argument it is a time in ms if (!isNext(',')) { return checkEOL(); } // Done if no other arguments time.set(EvalNumericExpressionValue.longValue()); // Use the time argument } else { time.setToNow(); // No arg, or first arg is not numeric: time is now } String theTime[] = time.format("%Y:%m:%d:%H:%M:%S").split(":"); int i = 0; do { // String vars for time components if (getSVar()) { // Commas hold places for up to six svars. mVal.val(theTime[i]); // If svar, use it; if nothing, skip to next comma. } } while ((++i < 6) && isNext(',')); // Anything else will get caught by checkEOL if (isNext(',') && getNVar()) { // Another comma holds a place for an optional nvar double weekDay = time.weekDay + 1; // for day of week: 1 is Sunday mVal.val(weekDay); } if (isNext(',') && getNVar()) { // Another comma holds a place for an optional nvar // For Daylight Saving Time flag mVal.val(Math.signum(time.isDst)); // 1 yes, 0 no, -1 unknown } return checkEOL(); } private boolean executeTIMEZONE() { // Get TimeZone command keyword if it is there return executeSubcommand(TimeZone_cmd, "TimeZone"); } private boolean executeTIMEZONE_SET() { // Set a global Time Zone string for TIME and TIME( String zone = Time.getCurrentTimezone(); // default to local time zone if (getStringArg()) { TimeZone tz = TimeZone.getTimeZone(StringConstant); // if arg, use it as TimeZone ID zone = tz.getID(); // read back ID, "GMT" if user-string invalid } if (!checkEOL()) return false; theTimeZone = zone; return true; } private boolean executeTIMEZONE_GET() { // Get the time zone setting if (!(getSVar() && checkEOL())) return false; String zone = theTimeZone; if (zone.equals("")) { zone = Time.getCurrentTimezone(); // If user never set a time zone, use local } mVal.val(zone); return true; } private boolean executeTIMEZONE_LIST() { // Get a list of all valid time zone strings int listIndex = getListArg(Var.Type.STR); // get a reusable List pointer - may create new list if (listIndex < 0) return false; // failed to get or create a list if (!checkEOL()) return false; ArrayList<String> theList = new ArrayList<String>(); theLists.set(listIndex, theList); for (String zone : TimeZone.getAvailableIDs()) { // get all the zones the system knows theList.add(zone); // put them in the list } return true; } // ************************************** Miscellaneous Non-core commands ************************** private boolean executeBACK_RESUME() { return doResume("Back key not hit"); } private boolean executeKB_RESUME() { return doResume("No keyboard change"); } private boolean executeLOWMEM_RESUME() { return doResume("No Low Memory signal"); } private boolean executeMENUKEY_RESUME() { return doResume("Menu key not hit"); } private boolean executePAUSE() { if (!evalNumericExpression()) return false; // Get pause duration value if (!checkEOL()) return false; long dur = EvalNumericExpressionValue.longValue(); if (dur < 1) { return RunTimeError("Pause must be greater than zero"); } try { Thread.sleep(dur); } catch (InterruptedException e) { } return true; } private boolean executeBROWSE() { if (!getStringArg()) return false; if (!checkEOL()) return false; String url = StringConstant; Intent i = new Intent(Intent.ACTION_VIEW); // Intent to launch browser i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setData(Uri.parse(url)); // try { Thread.sleep(500); } // Sleep here stopped forced stop exceptions // catch (InterruptedException e) { } try { startActivity(i); } // Launch browser at url catch (Exception e) { return RunTimeError(e); } return true; } private boolean executeINKEY() { if (!getSVar()) return false; // get the var to put the key value into Var.Val val = mVal; if (!checkEOL()) return false; val.val(InChar.isEmpty() ? "@" : InChar.remove(0)); return true; } private boolean executeKEY_RESUME() { return doResume("No Current Key Interrupt"); } private boolean executePOPUP() { if (!getStringArg()) return false; // get the message String msg = StringConstant; int[] args = { 0, 0, 0 }; // default x, y, duration if (isNext(',')) { // any optional args? if (!getOptExprs(args)) return false; // get the optional args args[2] = (args[2] == 0) ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG; // convert boolean to int } if (!checkEOL()) return false; Message m = mHandler.obtainMessage(MESSAGE_TOAST, args[0], args[1], msg); // If duration is Long, send it in a Bundle, otherwise let message default to SHORT. if (args[2] == Toast.LENGTH_LONG) { Bundle b = new Bundle(); b.putInt("dur", args[2]); m.setData(b); } m.sendToTarget(); // tell the UI thread to pop the toast return true; } public boolean executeCLS() { // Clear Screen if (!checkEOL()) return false; sendMessage(MESSAGE_CLEAR_CONSOLE); // tell the UI thread to clear the Console return true; } private boolean parseSelect(DialogArgs args, ArrayList<String> selectList) { // get SELECT parameters from program line if (!getNVar()) return false; // get the var to put the key value into Var.Val returnVal = mVal; if (!isNext(',')) return false; int saveLineIndex = LineIndex; Var var = getVarAndType(); if ((var != null) && var.isArray()) { if (!isNext(']')) { return RunTimeError(EXPECT_ARRAY_NO_INDEX); } // Array must not have any indices if (var.isNumeric()) { return RunTimeError(EXPECT_STRING_ARRAY); } if (var.isNew()) { return RunTimeError(EXPECT_ARRAY_EXISTS); } Var.ArrayDef array = var.arrayDef(); // get the array int length = array.length(); // get the array length for (int i = 0; i < length; ++i) { // copy the array values into the list selectList.add(array.sval(i)); // Caution! No range checking! } } else { LineIndex = saveLineIndex; if (!evalNumericExpression()) return false; int listIndex = EvalNumericExpressionValue.intValue(); if ((listIndex < 1) || (listIndex >= theLists.size())) { return RunTimeError("Invalid List Pointer"); } if (theListsType.get(listIndex) != Var.Type.STR) { return RunTimeError("Not a string list"); } selectList.addAll(theLists.get(listIndex)); } String title = null; // set defaults String msg = null; Var.Val isLongClickVal = null; if (isNext(',')) { // comma indicates optional arguments boolean isComma = true; if (!isNext(',') && !isEOL() && getStringArg()) { title = msg = StringConstant; // user provided a title argument isComma = isNext(','); } if (isComma) { if (isNext(',')) { msg = ""; // user suppressed message } else if (isEOL()) { isComma = false; } else if (getStringArg()) { msg = StringConstant; // user provided a message argument isComma = isNext(','); } } if (isComma) { if (!getNVar()) return false; // get the long press var isLongClickVal = mVal; } } if (!checkEOL()) return false; args.mTitle = title; args.mMessage = msg; args.mReturnVal = returnVal; args.mLongClickVal = isLongClickVal; return true; } private boolean executeSELECT() { DialogArgs args = new DialogArgs(); ArrayList<String> selectList = new ArrayList<String>(); if (!parseSelect(args, selectList)) return false; String title = args.mTitle; // default null String msg = args.mMessage; // default null SelectedItem = 0; // intialize return values SelectLongClick = false; mWaitForLock = true; Intent intent = new Intent(Run.this, Select.class); if (title != null) { intent.putExtra(Select.EXTRA_TITLE, title); } if (msg != null) { intent.putExtra(Select.EXTRA_MSG, msg); } intent.putStringArrayListExtra(Select.EXTRA_LIST, selectList); startActivityForResult(intent, BASIC_GENERAL_INTENT); waitForLOCK(); // Wait for signal from Selected.java thread Var.Val val = args.mReturnVal; if (val != null) { val.val(SelectedItem); } // Set the return value val = args.mLongClickVal; if (val != null) { val.val(SelectLongClick ? 1 : 0); } // Set the LongClick return value return true; } private boolean executeSPLIT(int limit) { Var var = getArrayVarForWrite(TYPE_STRING); // get the result array variable if (var == null) return false; // must name a new string array variable if (!isNext(',')) return false; if (!getStringArg()) return false; // Get the string to split String SearchString = StringConstant; String r[] = doSplit(SearchString, limit); // Get regex arg, if any, and split the string. if (!checkEOL()) return false; int length = r.length; // Get the number of strings generated if (length == 0) return false; // error in doSplit() return ListToBasicStringArray(var, Arrays.asList(r), length); } private String[] doSplit(String SearchString, int limit) { // Split a string // If limit < 0, keep all fields. If limit is 0, trim trailing blank fields. // If limit > 0, keep only up to limit fields. String r[] = new String[0]; // If error, return zero-length string String REString = null; if (isNext(',')) { // If user command supplied a regex if (!getStringArg()) { return r; } // get it REString = StringConstant; } else { REString = "\\s+"; // Otherwise split on whitespace } try { r = SearchString.split(REString, limit); if (r.length == 0) { // Special case: REString same as SearchString r = new String[1]; // Return non-empty array r[0] = ""; // with one empty String } } catch (PatternSyntaxException pse) { RunTimeError(REString + " is invalid argument at"); } return r; } private boolean executeJOIN(boolean keepAll) { // opposite of SPLIT // if keepAll is true, keep empty array elements Var var = getExistingArrayVar(); // get the array whose elements will be joined if (var == null) return false; if (var.isNumeric()) { return RunTimeError(EXPECT_STRING_ARRAY); } Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // Get values inside [], if any if (!isNext(',')) return false; if (!getSVar()) return false; // get the string variable to hold the result Var.Val val = mVal; String args[] = { "", "" }; // optional separator and wrapper if (isNext(',')) { // any optional args? if (!getOptExprs(args)) return false; // get the optional args } if (!checkEOL()) return false; Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); String[] array = arrayDef.getStrArray(); // raw array access String sep = args[0]; String wrap = args[1]; StringBuilder result = new StringBuilder(wrap); // start with wrapper boolean doSep = false; for (int i = 0; i < length; ++i) { String s = array[base + i]; // get each array element if ((s.length() == 0) && !keepAll) continue; // skip empty strings if told to do so if (doSep) { result.append(sep); } // separator before all but first substring result.append(s); // append substring doSep = true; } result.append(wrap); // end with wrapper val.val(result.toString()); return true; } // ************************************ Keyboard Commands ************************************* private boolean executeKB() { // Get kb command keyword if it is there return executeSubcommand(KB_cmd, "KB"); // and execute the command } private KeyboardManager getKeyboardManager() { KeyboardManager kb = GRFront ? GR.drawView.mKB : lv.mKB; if (kb == null) { RunTimeError("No keyboard manager"); } return kb; } private boolean executeKB_TOGGLE() { if (!checkEOL()) return false; KeyboardManager kb = getKeyboardManager(); return (kb != null) && (kb.showing() ? kbHide(kb) : kbShow(kb)); } private boolean executeKB_HIDE() { if (!checkEOL()) return false; return kbHide(getKeyboardManager()); } private boolean kbHide(KeyboardManager kb) { if (kb == null) return false; kb.hide(); return true; // even if kb.hide() return false } private boolean executeKB_SHOW() { if (!checkEOL()) return false; return kbShow(getKeyboardManager()); } private boolean kbShow(KeyboardManager kb) { if (kb == null) return false; if (!kb.show()) { checkpointMessage(); // allow any pending Console activity to complete while (mMessagePending) { Thread.yield(); } // wait for checkpointMessage semaphore to clear if (mConsole.getCount() == 0) { return RunTimeError("You must PRINT before you can show the soft keyboard"); } } return true; } private boolean executeKB_SHOWING() { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; KeyboardManager kb = getKeyboardManager(); if (kb == null) return false; val.val(kb.showing() ? 1.0 : 0.0); return true; } // ******************************************************************************************** private boolean executeWAKELOCK() { if (!evalNumericExpression()) return false; // Get setting int code = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; if (theWakeLock != null) { theWakeLock.release(); theWakeLock = null; } PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); String tag = "BASIC!"; switch (code) { case partial: theWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag); theWakeLock.acquire(); break; case dim: theWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, tag); theWakeLock.acquire(); break; case bright: theWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag); theWakeLock.acquire(); break; case full: theWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, tag); theWakeLock.acquire(); break; case release: break; default: return RunTimeError("WakeLock code not 1 - 5"); } return true; } private boolean executeWIFI_INFO() { if (isEOL()) return true; // user asked for no data // First three return variables are strings. The IP address may be either string or numeric. // The last variable is numeric. byte[] types = { 2, 2, 2, 3, 1 }; // type of each variable int nArgs = types.length; Var.Val[] args = new Var.Val[nArgs]; // Val object for each variable WifiInfo wi = null; if (!getOptVars(types, args)) return false; try { WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); wi = wm.getConnectionInfo(); } catch (Exception e) { Log.d(LOGTAG, e.toString()); return RunTimeError("Cannot get WifiInfo:", e); } int arg = 0; if (args[arg] != null) { args[arg].val(wi.getSSID()); } if (args[++arg] != null) { args[arg].val(wi.getBSSID()); } if (args[++arg] != null) { args[arg].val(wi.getMacAddress()); } if (args[++arg] != null) { Var.Val val = args[arg]; // IP address variable int ip = wi.getIpAddress(); if (types[arg] == 1) { // IP address variable is numeric val.val(ip); } else { // convert to string String ipString = ""; byte[] ipbytes = { (byte) (ip), (byte) (ip >>> 8), (byte) (ip >>> 16), (byte) (ip >>> 24) }; try { ipString = InetAddress.getByAddress(ipbytes).getHostAddress(); } catch (Exception e) { /* can't happen */ } val.val(ipString); } } if (args[++arg] != null) { args[arg].val(wi.getLinkSpeed()); } return (++arg == nArgs); // sanity-check arg count } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private boolean executeWIFILOCK() { if (!evalNumericExpression()) return false; // Get setting int code = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; if (theWifiLock != null) { theWifiLock.release(); theWifiLock = null; } WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); String tag = "BASIC!"; switch (code) { case wifi_mode_high: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { // >= 12 theWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, tag); theWifiLock.acquire(); break; } // Lower API versions fall through to MODE_FULL case wifi_mode_full: theWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, tag); theWifiLock.acquire(); break; case wifi_mode_scan: theWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, tag); theWifiLock.acquire(); break; case wifi_release: break; default: return RunTimeError("WifiLock code not 1 - 4"); } return true; } private boolean executeTONE() { double duration = 1; // seconds double freqOfTone = 1000; // hz int sampleRate = 8000; // a number if (!evalNumericExpression()) return false; // Get frequency freqOfTone = EvalNumericExpressionValue; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get duration duration = EvalNumericExpressionValue / 1000; double dnumSamples = duration * sampleRate; dnumSamples = Math.ceil(dnumSamples); int numSamples = (int) dnumSamples; double sample[] = new double[numSamples]; ByteBuffer generatedSnd = null; try { generatedSnd = ByteBuffer.allocate(2 * numSamples); } catch (OutOfMemoryError oom) { return RunTimeError(oom); } generatedSnd.order(ByteOrder.LITTLE_ENDIAN); ShortBuffer shortView = generatedSnd.asShortBuffer(); boolean flagMinBuff = true; // Optionally skip checking min buffer size if (isNext(',')) { if (!evalNumericExpression()) return false; if (EvalNumericExpressionValue == 0) { flagMinBuff = false; } } if (flagMinBuff) { int minBuffer = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); if (2 * numSamples < minBuffer) { double minDuration = Math.ceil(1000 * (double) minBuffer / (2 * (double) sampleRate)); return RunTimeError( "Minimum tone duration for this device: " + (int) minDuration + " milliseconds"); } } if (!checkEOL()) return false; // No more parameters expected for (int i = 0; i < numSamples; ++i) { // Fill the sample array sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate)); } // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. int i = 0; int ramp = numSamples / 20; // Amplitude ramp as a percent of sample count for (i = 0; i < ramp; ++i) { // Ramp amplitude up to max (to avoid clicks) short val = (short) (sample[i] * 32767 * i / ramp); shortView.put(val); } for (; i < numSamples - ramp; ++i) { // Max amplitude for most of the samples short val = (short) (sample[i] * 32767); // scale to maximum amplitude shortView.put(val); } for (; i < numSamples; ++i) { // Ramp amplitude down to 0 short val = (short) (sample[i] * 32767 * (numSamples - i) / ramp); shortView.put(val); } AudioTrack audioTrack = null; // Get audio track try { audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, numSamples * 2, AudioTrack.MODE_STATIC); audioTrack.write(generatedSnd.array(), 0, numSamples * 2); // Load the track audioTrack.play(); // Play the track } catch (Exception e) { return RunTimeError(e); } int x = 0; do { // Monitor playback to find when done x = (audioTrack != null) ? audioTrack.getPlaybackHeadPosition() : numSamples; } while (x < numSamples); if (audioTrack != null) audioTrack.release(); // Track play done. Release track. audioTrack = null; // Release storage shortView = null; generatedSnd = null; sample = null; return true; } private boolean executeVIBRATE() { Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; if (!var.isNumeric()) { return RunTimeError(EXPECT_NUM_ARRAY); } // Insure that it is a numeric array Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any if (!isNext(',')) return false; // get the repeat value if (!evalNumericExpression()) return false; int repeat = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); double[] array = arrayDef.getNumArray(); // raw array access long Pattern[] = new long[length]; // pattern array for (int i = 0; i < length; ++i) { // copy user array into pattern Pattern[i] = (long) array[base + i]; } try { if (myVib == null) { // if no vibrator, go get it myVib = (Vibrator) Run.this.getSystemService(VIBRATOR_SERVICE); } if (repeat > 0) myVib.cancel(); else myVib.vibrate(Pattern, repeat); // do the vibrate } catch (SecurityException ex) { Log.d(LOGTAG, "SecurityException on VIBRATE: do you have android.permission.VIBRATE in your Manifest?"); myVib = null; return RunTimeError("Your app is not permitted to use VIBRATE."); } return true; } private void cancelVibrator() { if (myVib != null) { myVib.cancel(); myVib = null; } } private boolean executeDEVICE() { if (!getVar() || !checkEOL()) return false; // variable to hold the returned bundle pointer or string value Var.Val val = mVal; Locale loc = Locale.getDefault(); TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); String failMsg = "Not available"; String[] keys = { "Brand", "Model", "Device", "Product", "OS", "Language", "Locale", "PhoneType", "PhoneNumber", "DeviceID", "SIM SN", "SIM MCC/MNC", "SIM Provider" }; String[] vals = { Build.BRAND, Build.MODEL, Build.DEVICE, Build.PRODUCT, Build.VERSION.RELEASE, loc.getDisplayLanguage(), loc.toString(), getPhoneType(tm), getPhoneNumber(tm, failMsg), getDeviceID(tm, failMsg), getSimSN(tm, failMsg), getSimOperator(tm, failMsg), getSimOpName(tm, failMsg) }; int len = keys.length; int i = 0; if (val.isNumeric()) { // bundle format Bundle b = new Bundle(); int bundleIndex = theBundles.size(); theBundles.add(b); for (; i < len; ++i) { b.putString(keys[i], vals[i]); } val.val(bundleIndex); } else { // string format StringBuilder s = new StringBuilder(); while (true) { s.append(keys[i]).append(" = ").append(vals[i]); if (++i == len) break; s.append('\n'); } val.val(s.toString()); } return true; } // ************************************* Screen Commands ************************************** private boolean executeSCREEN() { // Get SCREEN command keyword if it is there return executeSubcommand(screen_cmd, "Screen"); // and execute the command } private boolean executeSCREEN_ROTATION() { if (!getNVar()) return false; // get var for rotation Var.Val val = mVal; if (!checkEOL()) return false; Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay(); val.val(getScreenRotation(display)); return true; } private boolean executeSCREEN_SIZE() { if (isEOL()) return true; // no arguments Var sizeVar = null; // return array for size Var realVar = null; // return array for realsize Var.Val densVal = null; // return variable for density boolean isComma = isNext(','); if (!isComma) { sizeVar = getArrayVarForWrite(TYPE_NUMERIC); // get array for size if (sizeVar == null) return false; // must name a numeric array variable isComma = isNext(','); } if (isComma) { isComma = isNext(','); if (!isComma) { realVar = getArrayVarForWrite(TYPE_NUMERIC); // get array for realsize if (realVar == null) return false; // must name a numeric array variable isComma = isNext(','); } } if (isComma) { isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // get var for density densVal = mVal; } } if (!checkEOL()) return false; // have all parameters Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay(); if ((sizeVar != null) || (realVar != null)) { if (!getScreenSize(display, sizeVar, realVar)) return false; } if (densVal != null) { densVal.val(getScreenDensity(display)); } return true; } @SuppressLint("NewApi") @SuppressWarnings("deprecation") public int getScreenRotation(Display display) { return (Build.VERSION.SDK_INT < 8) ? display.getOrientation() : display.getRotation(); } public int getScreenDensity(Display display) { DisplayMetrics dm = new DisplayMetrics(); display.getMetrics(dm); return dm.densityDpi; } @SuppressLint("NewApi") @SuppressWarnings("deprecation") public boolean getScreenSize(Display display, Var size, Var realsize) { Point pSize = new Point(); if (Build.VERSION.SDK_INT < 13) { pSize.set(display.getWidth(), display.getHeight()); } else { display.getSize(pSize); // "application" screen size } if (size != null) { if (!BuildBasicArray(size, 2)) return false; // build the array double[] array = size.arrayDef().getNumArray(); // raw array access array[0] = pSize.x; array[1] = pSize.y; // stuff the array } if (realsize != null) { if (Build.VERSION.SDK_INT >= 17) { display.getRealSize(pSize); // "real" screen size } if (!BuildBasicArray(realsize, 2)) return false; // build the array double[] array = realsize.arrayDef().getNumArray(); // raw array access array[0] = pSize.x; array[1] = pSize.y; // stuff the array } return true; } private boolean executeHTTP_POST() { if (!getStringArg()) return false; String url = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; int theListIndex = EvalNumericExpressionValue.intValue(); if (theListIndex < 1 || theListIndex >= theLists.size()) { return RunTimeError("Invalid list pointer"); } if (theListsType.get(theListIndex) != Var.Type.STR) { return RunTimeError("List must be of string type."); } List<String> thisList = theLists.get(theListIndex); int r = thisList.size() % 2; if (r != 0) { return RunTimeError("List must have even number of elements"); } if (!isNext(',')) return false; if (!getSVar()) return false; Var.Val val = mVal; // variable to hold Result if (!checkEOL()) return false; List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); for (int i = 0; i < thisList.size(); ++i) { nameValuePairs.add(new BasicNameValuePair(thisList.get(i), thisList.get(++i))); } String Result = ""; HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(url); try { post.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); // HttpResponse response = client.execute(post); ResponseHandler<String> responseHandler = new BasicResponseHandler(); Result = client.execute(post, responseHandler); } catch (Exception e) { return RunTimeError("!", e); } val.val(Result); return true; } // ************************************************ SQL Package *************************************** private boolean executeSQL() { // Get SQL command keyword if it is there return executeSubcommand(SQL_cmd, "SQL"); } private boolean getDbPtrArg() { // first arg of command is DB Pointer Variable // "return value" is global theValueIndex String errStr = "Database not opened at:"; if (DataBases.isEmpty()) { return RunTimeError(errStr); } if (!getNVar()) return false; // variable that holds the DB table pointer int i = (int) mVal.nval(); if (i == 0) { // if pointer is zero return RunTimeError(errStr); // DB has been closed } if (i < 0 || i > DataBases.size()) { return RunTimeError("Invalid Database Pointer at:"); } return true; } private boolean getVarAndDbPtrArgs(Var.Val[] args) { // first arg of command is a numeric variable // and second is a DB table pointer expression String errStr = "Database not opened at:"; if (DataBases.isEmpty()) { return RunTimeError(errStr); } if (!getNVar()) return false; // user's nvar args[0] = mVal; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // expression that specifies the DB table pointer int db = EvalNumericExpressionValue.intValue(); args[1] = new Var.NumVal(db); // create a temporary Val to hold the value if (db == 0) { // if pointer is zero return RunTimeError(errStr); // DB has been closed } if (db < 0 || db > DataBases.size()) { return RunTimeError("Invalid Database Pointer at:"); } return true; } private boolean getVarAndCursorPtrArgs(Var.Val[] args) { // first arg of command is a numeric variable // and second is a DB cursor pointer if (Cursors.isEmpty()) { // Make sure there is at least one cursor return RunTimeError("Cursor not available at:"); } if (!getNVar()) return false; // user's nvar args[0] = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; // the DB cursor pointer args[1] = mVal; int i = (int) mVal.nval(); if (i == 0) { // If pointer is zero return RunTimeError("Cursor done at:"); // then cursor is used up } if (i < 0 || i > Cursors.size()) { return RunTimeError("Invalid Cursor Pointer at:"); } return true; } private boolean getColumnValuePairs(ContentValues values) { // Get column/value pairs from user command if (!isNext(',')) return false; do { if (!getStringArg()) return false; // Column String Column = StringConstant; if (!isNext(',') || !getStringArg()) return false; // Value String Value = StringConstant; values.put(Column, Value); // Store the pair } while (isNext(',')); return true; } private boolean execute_sql_open() { if (!getNVar()) return false; // DB Pointer Variable Var.Val val = mVal; // for the DB table pointer if (!isNext(',')) return false; if (!getStringArg()) return false; // Get Data Base Name String DBname = StringConstant; if (!checkEOL()) return false; String fn; if (DBname.startsWith(":")) { fn = DBname; } else { if (!checkSDCARD('w')) return false; fn = Basic.getDataBasePath(DBname); } SQLiteDatabase db; try { // Do the open or create db = SQLiteDatabase.openOrCreateDatabase(new File(fn), null); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } // The newly opened data base is added to the DataBases list. // The list index of the new data base is added returned to the user val.val(DataBases.size() + 1); DataBases.add(db); return true; } private boolean execute_sql_close() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer Var.Val val = mVal; if (!checkEOL()) return false; int i = (int) val.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base try { db.close(); } // Try closing it catch (Exception e) { return RunTimeError("SQL Exception:", e); } val.val(0.0); // Set the pointer to 0 to indicate closed. return true; } private boolean execute_sql_insert() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; ContentValues values = new ContentValues(); if (!getColumnValuePairs(values)) return false; // Get column/value pairs from user command if (!checkEOL()) return false; try { db.insertOrThrow(TableName, null, values); } // Now insert the pairs into the named table catch (Exception e) { return RunTimeError("SQL Exception:", e); } return true; } private boolean execute_sql_query() { Var.Val[] args = new Var.Val[2]; // get the first two args: if (!getVarAndDbPtrArgs(args)) return false; Var.Val cursorVar = args[0]; // Query Cursor Variable int dbPtr = (int) args[1].nval(); // one-based DB table pointer SQLiteDatabase db = DataBases.get(dbPtr - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // String of comma separated columns to get String RawColumns = StringConstant; // Must convert string to an array of columns ArrayList<String> cList = new ArrayList<String>(); // start by creating an ArrayList for column names String cTemp = ""; // Parse the Raw Columns for (int j = 0; j < RawColumns.length(); ++j) { char t = RawColumns.charAt(j); if (t != ',') { // while tossing out blanks if (t != ' ') { cTemp += t; } // add characters to the column name } else { cList.add(cTemp); // comma terminates a column name, add name to array list cTemp = ""; // and start a new column name } } cList.add(cTemp); // add last column to the list String[] Q_Columns = new String[cList.size()]; // Finally, convert the array list cList.toArray(Q_Columns); // to a String Array. String Where = ""; // if no Where given, set empty String Order = ""; // if no Order given, set empty if (isNext(',')) { // if no comma, then no optional Where if (!getStringArg()) return false; // Where Value Where = StringConstant; if (isNext(',')) { // if no comma, then no optional Order if (!getStringArg()) return false; // Order Value Order = StringConstant; } } if (!checkEOL()) return false; Cursor cursor; try { // Do the query and get the cursor cursor = db.query(TableName, Q_Columns, Where, null, null, null, Order); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } cursorVar.val(Cursors.size() + 1); // save the Cursor index into the var Cursors.add(cursor); // and save the cursor. return true; } // Extended or unknown number of columns by GitHub poster @JasonFruit. See Issue #141. private boolean execute_sql_next() { // get data from next row of cursor Var.Val[] args = new Var.Val[2]; // get the first two args: if (!getVarAndCursorPtrArgs(args)) return false; Var.Val doneVar = args[0]; // Done Flag variable Var.Val cursorVar = args[1]; // DB Cursor pointer variable ArrayList<Var> vars = new ArrayList<Var>(); if (isNext(',')) { // if comma, expect vars to receive column data if (!getSVars(vars)) return false; // get list of string vars to hold column(s), last may be array } int nVars = vars.size(); Var colArrayVar = (nVars != 0) ? vars.get(nVars - 1) : null; // last var MAY be array boolean isArray = (colArrayVar != null) && colArrayVar.isArray(); // is it? Var.Val nColVal = null; // for optional column count if (isArray) { --nVars; // count of non-array vars if (isNext(',')) { if (!getNVar()) return false; // get optional column count var nColVal = mVal; } } if (!checkEOL()) return false; doneVar.val(0.0); // set Not Done int i = (int) cursorVar.nval(); // get the cursor pointer Cursor cursor = Cursors.get(i - 1); // get the cursor if (cursor.moveToNext()) { // if there is another row, go there int nCols = cursor.getColumnCount(); // num columns of data available if (nColVal != null) { nColVal.val(nCols); } // if requested, return total number of columns if (nCols < nVars) { nVars = nCols; } // don't try to store more columns than we have else if (!isArray && (nVars < nCols)) { nCols = nVars; } // don't read more columns than we can return ArrayList<String> colData = new ArrayList<String>(nCols); for (int index = 0; index < nCols; ++index) { String result; try { result = cursor.getString(index); } // get the result catch (Exception e) { return RunTimeError("SQL Exception:", e); } colData.add((result != null) ? result : ""); } for (int index = 0; index < nVars; ++index) { // write column data to scalar vars vars.get(index).val().val(colData.remove(0)); } if (isArray) { // write rest of column data to array var if (colData.size() == 0) { colData.add(""); } // don't allow empty array if (!ListToBasicStringArray(colArrayVar, colData, colData.size())) return false; } } else { // no next row, cursor is used up cursor.close(); doneVar.val(1.0); cursorVar.val(0.0); } return true; } private boolean execute_sql_query_length() { // Report the number of rows in a query result Var.Val[] args = new Var.Val[2]; // Get the first two args: if (!getVarAndCursorPtrArgs(args)) return false; Var.Val resultVar = args[0]; // variable for number of rows Var.Val cursorVar = args[1]; // DB Cursor pointer variable if (!checkEOL()) return false; int i = (int) cursorVar.nval(); // get the cursor pointer Cursor cursor = Cursors.get(i - 1); // get the cursor double nRows = cursor.getCount(); resultVar.val(nRows); // return number of rows to user return true; } private boolean execute_sql_query_position() { // Report current position in query results Var.Val[] args = new Var.Val[2]; // Get the first two args: if (!getVarAndCursorPtrArgs(args)) return false; Var.Val resultVar = args[0]; // variable for position Var.Val cursorVar = args[1]; // DB Cursor pointer variable if (!checkEOL()) return false; int i = (int) cursorVar.nval(); // get the cursor pointer Cursor cursor = Cursors.get(i - 1); // get the cursor double position = cursor.getPosition(); resultVar.val(position + 1); // return position to user, 1-based return true; } private boolean execute_sql_delete() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; Var.Val resultVar = null; String Where = null; // if no Where given, set null if (isNext(',')) { // if no comma, then no optional Where if (!getStringArg()) return false; // Where Value Where = StringConstant; if (isNext(',')) { // if there's a where if (!getNVar()) return false; // there can be a return value resultVar = mVal; } } if (!checkEOL()) return false; int count = 0; try { count = db.delete(TableName, Where, null); // do the deletes } catch (Exception e) { return RunTimeError("SQL Exception:", e); } if (resultVar != null) { resultVar.val(count); // return the number of rows deleted } return true; } private boolean execute_sql_update() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; ContentValues values = new ContentValues(); if (!getColumnValuePairs(values)) return false; // Get column/value pairs from user command String Where = null; // Where is optional if (isNext(':')) { // Colon indicates Where follows if (!getStringArg()) return false; // Where Value Where = StringConstant; } if (!checkEOL()) return false; try { db.update(TableName, values, Where, null); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } return true; } private boolean execute_sql_exec() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Command string String CommandString = StringConstant; if (!checkEOL()) return false; try { db.execSQL(CommandString); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } return true; } private boolean execute_sql_raw_query() { Var.Val[] args = new Var.Val[2]; // get the first two args: if (!getVarAndDbPtrArgs(args)) return false; Var.Val cursorVar = args[0]; // Query Cursor Variable int dbPtr = (int) args[1].nval(); // one-based DB table pointer SQLiteDatabase db = DataBases.get(dbPtr - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // query string String QueryString = StringConstant; if (!checkEOL()) return false; Cursor cursor; try { // Do the query and get the cursor cursor = db.rawQuery(QueryString, null); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } cursorVar.val(Cursors.size() + 1); // Save the Cursor index into the var Cursors.add(cursor); // and save the cursor. return true; } private boolean execute_sql_drop_table() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; if (!checkEOL()) return false; String CommandString = "DROP TABLE IF EXISTS " + TableName; try { db.execSQL(CommandString); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } return true; } private boolean execute_sql_new_table() { if (!getDbPtrArg()) return false; // get variable for the DB table pointer int i = (int) mVal.nval(); // get the pointer SQLiteDatabase db = DataBases.get(i - 1); // get the data base if (!isNext(',')) return false; if (!getStringArg()) return false; // Table Name String TableName = StringConstant; if (!isNext(',')) return false; ArrayList<String> Columns = new ArrayList<String>(); do { if (!getStringArg()) return false; // Columns Columns.add(StringConstant); } while (isNext(',')); if (!checkEOL()) return false; String columns = ""; int cc = Columns.size(); for (int j = 0; j < cc; ++j) { columns += Columns.get(j) + " TEXT"; if (j != cc - 1) { columns += " , "; } } String CommandString = StringConstant; CommandString = "CREATE TABLE " + TableName + "( " + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + columns + " )"; try { db.execSQL(CommandString); } catch (Exception e) { return RunTimeError("SQL Exception:", e); } return true; } // ************************************ Graphics Package *********************************** private boolean executeGR() { Command c = findSubcommand(GR_cmd, "GR"); if (c == null) return false; if (!GRopen && (c.id != CID_OPEN)) { return RunTimeError("Graphics not opened at:"); } return c.run(); } private boolean executeGR_BITMAP() { // GR group, BITMAP subgroup return executeSubgroupCommand(GrBitmap_cmd, "Gr.Bitmap"); } private boolean executeGR_CAMERA() { // GR group, CAMERA subgroup return executeSubgroupCommand(GrCamera_cmd, "Gr.Camera"); } private boolean executeGR_GET() { // GR group, GET subgroup return executeSubgroupCommand(GrGet_cmd, "Gr.Get"); } private boolean executeGR_GROUP() { // GR group, GROUP subgroup return executeSubgroupCommand(GrGroup_cmd, "Gr.Group"); } private boolean executeGR_PAINT() { // GR group, PAINT subgroup return executeSubgroupCommand(GrPaint_cmd, "Gr.Paint"); } private boolean executeGR_TEXT() { // GR group, TEXT subgroup return executeSubgroupCommand(GrText_cmd, "Gr.Text"); } private void DisplayListAdd(GR.BDraw b) { b.common((PaintList.size() - 1), 256); // paint and alpha for this object if (drawintoCanvas != null) { GR.drawView.doDraw(drawintoCanvas, b); return; } synchronized (DisplayList) { RealDisplayList.add(DisplayList.size()); DisplayList.add(b); } } private boolean execute_gr_bitmap_drawinto_start() { int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; if (!checkEOL()) return false; Bitmap bitmap = BitmapList.get(bitmapPtr); // get the bitmap if (bitmap == null) { return RunTimeError("Bitmap was deleted"); } if (!bitmap.isMutable()) { return RunTimeError("Bitmaps loaded from files not changeable."); } if (bitmap.isRecycled()) { return RunTimeError("Bitmap was recycled"); } drawintoCanvas = new Canvas(bitmap); return true; } private boolean execute_gr_bitmap_drawinto_end() { drawintoCanvas = null; return true; } private Paint newPaint(Paint fromPaint) { // Android bug workaround? Typeface tf = fromPaint.getTypeface(); Paint rPaint = new Paint(fromPaint); // creates a new Paint rPaint.setTypeface(tf); // while preserving the type face return rPaint; } private Paint initPaint() { return initPaint(255, 0, 0, 0); // new opaque black Paint with default settings } private Paint initPaint(int a, int r, int g, int b) { Paint paint = new Paint(); paint.setARGB(a, r, g, b); // set the colors, etc paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setStrokeWidth(0.0f); // int f = paint.getFlags(); // paint.setFlags(f | Paint.FILTER_BITMAP_FLAG); return paint; } private void DisplayListClear(GR.Type type) { Log.d(LOGTAG, "DisplayListClear"); synchronized (DisplayList) { DisplayList.clear(); // Clear the Display List DisplayList.trimToSize(); RealDisplayList.clear(); RealDisplayList.trimToSize(); PaintListClear(); // clear and initialize the PaintList GR.BDraw b = new GR.BDraw(type); // Start Display List DisplayListAdd(b); // with specified first entry } } private void PaintListClear() { // Preserve user's current and working Paints if they exist. int nPaints = PaintList.size(); Paint workingPaint = (nPaints == 0) ? initPaint() : PaintList.get(0); Paint currentPaint = (nPaints == 0) ? initPaint() : PaintList.get(nPaints - 1); PaintList.clear(); PaintList.trimToSize(); PaintList.add(workingPaint); // restore user's PaintList.add(currentPaint); // add user's current Paint at entry 1 } private void BitmapListClear() { if (BitmapList != null) { for (int i = 0; i < BitmapList.size(); ++i) { Bitmap bitmap = BitmapList.get(i); if (bitmap != null) { bitmap.recycle(); BitmapList.set(i, null); } } BitmapList.clear(); } } private boolean execute_gr_getdl() { Var var = getArrayVarForWrite(TYPE_NUMERIC); // get the result array variable if (var == null) return false; // must name a numeric array variable boolean keepHiddenObjects = false; if (isNext(',')) { // Optional "hidden" flag if (!evalNumericExpression()) return false; keepHiddenObjects = (EvalNumericExpressionValue != 0.0); } if (!checkEOL()) return false; // line must end with ']' double[] list = new double[RealDisplayList.size() + 1]; int count = 0; for (Integer Idx : RealDisplayList) { // For each object index int idx = (Idx == null) ? 0 : Idx.intValue(); boolean include = ((idx != 0) && // If not null or index of null object... (keepHiddenObjects || // ... and either keeping all objects... DisplayList.get(idx).isVisible())); // ... or object is not hidden... if (include) { list[count++] = idx; } // ... then put index in the new list } if (count == 0) { count = 1; } // if no objects, make a list with // one entry that indexes the null object if (!BuildBasicArray(var, count)) return false; // build the array double[] array = var.arrayDef().getNumArray(); // raw array access for (int i = 0; i < count; ++i) { // stuff the array array[i] = list[i]; // count may be < list.length } return true; } private boolean execute_gr_newdl() { Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; if (!var.isNumeric()) { return RunTimeError(EXPECT_NUM_ARRAY); } // insure that it is a numeric array Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any if (!checkEOL()) return false; // line must end with ']' Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); double[] array = arrayDef.getNumArray(); // raw array access RealDisplayList.clear(); RealDisplayList.add(0); // first entry points to null object synchronized (DisplayList) { for (int i = 0; i < length; ++i) { // copy the object pointers int id = (int) array[base + i]; if (id < 0 || id >= DisplayList.size()) { return RunTimeError("Invalid Object Number"); } RealDisplayList.add(id); } } return true; } private boolean execute_gr_open() { if (GRopen) { return RunTimeError("Graphics already opened"); } int[] args = { 255, 255, 255, 255, 0, 0 }; // default to opaque white, no status bar, landscape if (!getOptExprs(args)) return false; int a = args[0]; int r = args[1]; int g = args[2]; int b = args[3]; int showStatusBar = args[4]; int orientation = args[5]; int backgroundColor = a * 0x1000000 + // Set the appropriate bytes r * 0x10000 + g * 0x100 + b; mShowStatusBar = (showStatusBar != 0); // record choice for GR.StatusBar command synchronized (DisplayList) { drawintoCanvas = null; DisplayListClear(GR.Type.Open); BitmapListClear(); BitmapList.add(null); // Set Zero entry as null Paint paint = initPaint(a, r, g, b); // Create a new Paint object, default except color PaintList.add(paint); // Add to the Paint List as element 2 } mGrIntent = new Intent(Run.this, GR.class); // Set up parameters for the Graphics Activity mGrIntent.putExtra(GR.EXTRA_SHOW_STATUSBAR, showStatusBar); mGrIntent.putExtra(GR.EXTRA_ORIENTATION, orientation); mGrIntent.putExtra(GR.EXTRA_BACKGROUND_COLOR, backgroundColor); GR.Running = false; // Set up the signals GR.waitForLock = true; startActivityForResult(mGrIntent, BASIC_GENERAL_INTENT); // Start the Graphics Activity waitForGrLOCK(); // Do not continue until GR signals it is running GRopen = true; // Set some more signals initTouchVars(); GR.doSTT = false; CameraNumber = -1; setVolumeControlStream(AudioManager.STREAM_MUSIC); return true; } private void initTouchVars() { for (int i = 0; i < NewTouch.length; ++i) { TouchX[i] = TouchY[i] = -1; NewTouch[i] = false; } } private boolean execute_gr_paint_get() { // get the index of the current (latest) Paint if (!getNVar()) return false; if (!checkEOL()) return false; mVal.val(PaintList.size() - 1); return true; } private boolean execute_gr_paint_copy() { // copy a Paint or make a new one int[] args = { -1, -1 }; // default: src, dst both current Paint if (!getOptExprs(args)) return false; int current = PaintList.size() - 1; int src = args[0]; int dst = args[1]; if (src != -1) { if (!checkPaintIndex(src)) return false; // ERROR: out of range } else { src = current; } Paint srcPaint = newPaint(PaintList.get(src)); // make a copy if (dst != -1) { if (!checkPaintIndex(dst)) return false; // ERROR: out of range PaintList.set(dst, srcPaint); // replace destination Paint with the copy } else { PaintList.add(srcPaint); // add the copy to the Paint list } return true; } private boolean execute_gr_paint_reset() { int[] args = { -1 }; // default: reset current Paint; if (!getOptExprs(args)) return false; Paint paint = initPaint(); // create a new default Paint int index = args[0]; if (index == -1) { PaintList.add(paint); // add the new Paint to the List } else { if (!checkPaintIndex(index)) return false; PaintList.set(index, paint); // replace the existing Paint } return true; } private void GRstop() { // stop GR, but don't wait for lock if (GR.drawView != null) { // create a new display list DisplayListClear(GR.Type.Close); // that commands GR.java to close GR.drawView.postInvalidate(); // start the draw so the command will get executed } GRopen = false; } private boolean execute_gr_close() { if (!checkEOL()) return false; if (GR.drawView != null) { // create a new display list DisplayListClear(GR.Type.Close); // that commands GR.java to close GR.waitForLock = true; GR.drawView.postInvalidate(); // start the draw so the command will get executed waitForGrLOCK(); } GRopen = false; return true; } private boolean execute_gr_render() { if (!checkEOL()) return false; if (GR.drawView == null) { // make sure drawView has not gone null Stop = true; return false; } synchronized (DisplayList) { GR.waitForLock = true; } GR.NullBitMap = false; GR.drawView.postInvalidate(); // Start GR drawing. waitForGrLOCK(); if (GR.NullBitMap) { GR.NullBitMap = false; return RunTimeError("Display List had deleted bitmap."); } return true; } private boolean checkPaintIndex(int index) { return ((index >= 0) && (index < PaintList.size())) ? true : RunTimeError("Paint Number out of range"); } private Paint getWorkingPaint(int index) { // get specified Paint // if none specified, clone current Paint as new current Paint Paint paint; if (index != -1) { // get an existing Paint if (!checkPaintIndex(index)) return null; // ERROR: out of range paint = PaintList.get(index); } else { paint = newPaint(PaintList.get(PaintList.size() - 1)); // get a copy of the latest Paint PaintList.add(paint); // add the copy to the Paint list } return paint; } private boolean execute_gr_color() { int[] args = { -1, -1, -1, -1, -1, -1 }; // default to current color and style, new Paint if (!getOptExprs(args)) return false; int paintIdx = args[5]; Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range int color = paint.getColor(); int a = (args[0] != -1) ? args[0] : 255 & (color >>> 24); int r = (args[1] != -1) ? args[1] : 255 & (color >>> 16); int g = (args[2] != -1) ? args[2] : 255 & (color >>> 8); int b = (args[3] != -1) ? args[3] : 255 & (color); paint.setARGB(a, r, g, b); // set the colors int style = args[4]; if (style == 0) { paint.setStyle(Paint.Style.STROKE); } else if (style == 1) { paint.setStyle(Paint.Style.FILL); } else if (style != -1) { paint.setStyle(Paint.Style.FILL_AND_STROKE); } return true; } private boolean execute_gr_antialias() { int[] args = { -1, -1 }; // default to toggle and new Paint if (!getOptExprs(args)) return false; int select = args[0]; int paintIdx = args[1]; Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range // -1 toggles antialias, 0 clears it, anything else sets it. boolean flag = (select == -1) ? !paint.isAntiAlias() : (select != 0); paint.setAntiAlias(flag); return true; } private boolean execute_gr_stroke_width() { byte[] type = { 1, 1 }; // two optional numeric arguments Double[] args = { null, null }; String[] dummy = { null, null }; // not used, no String arguments if (!getOptExprs(type, args, dummy)) return false; int paintIdx = (args[1] != null) ? args[1].intValue() : -1; // default to current Paint Paint paint = getWorkingPaint(paintIdx); // null if invalid index if (args[0] != null) { // if width provided float width = args[0].floatValue(); // get width if (width < 0.0f) { return RunTimeError("Width must be >= 0"); } if (paint != null) paint.setStrokeWidth(width); } if (paint == null) return false; // error reporting is in parameter order return true; } // Common processing for the beginning of a command that creates a graphical object. // Expect a numeric variable on the command line. Leave its Val object in mVal. TODO: ELIMINATE mVal private GR.BDraw createGrObj_start(GR.Type type) { if (!getNVar()) return null; GR.BDraw b = new GR.BDraw(type); return b; } // Common processing for the end of a command that creates a graphical object. private boolean createGrObj_finish(GR.BDraw b, Var.Val val) { if (!checkEOL()) return false; synchronized (DisplayList) { val.val(DisplayList.size()); // save the object index into the var DisplayListAdd(b); // add the object to the Display List } return true; } // Common processing for the end of a command that creates a bitmap. private boolean createBitmap_finish(Bitmap bitmap, Var.Val val, String errMsg) { int value; if (bitmap != null) { value = BitmapList.size(); // save the bitmap index BitmapList.add(bitmap); // add the new bitmap to the bitmap list } else { value = -1; writeErrorMsg((errMsg != null) ? errMsg : "Failed to create bitmap"); } if (val != null) { val.val(value); } // give the bitmap pointer to the user return true; } private boolean execute_gr_point() { GR.BDraw b = createGrObj_start(GR.Type.Point); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] xy = getArgsII(); if (xy == null) return false; b.xy(xy); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_line() { GR.BDraw b = createGrObj_start(GR.Type.Line); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] x2y2 = getArgs4I(); // in x1, y1, x2, y2 order if (x2y2 == null) return false; b.ltrb(x2y2); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_rect() { GR.BDraw b = createGrObj_start(GR.Type.Rect); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] ltrb = getArgs4I(); if (ltrb == null) return false; b.ltrb(ltrb); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_arc() { GR.BDraw b = createGrObj_start(GR.Type.Arc); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] ltrb = getArgs4I(); if (ltrb == null) return false; if (!isNext(',')) return false; double[] angles = getArgsDD(); if (angles == null) return false; if (!isNext(',') || !evalNumericExpression()) return false; int fillMode = EvalNumericExpressionValue.intValue(); b.arc(ltrb, (float) angles[0], (float) angles[1], fillMode); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_circle() { GR.BDraw b = createGrObj_start(GR.Type.Circle); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] xy = getArgsII(); if (xy == null) return false; if (!isNext(',') || !evalNumericExpression()) return false; int radius = EvalNumericExpressionValue.intValue(); b.circle(xy, radius); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_oval() { GR.BDraw b = createGrObj_start(GR.Type.Oval); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] ltrb = getArgs4I(); if (ltrb == null) return false; b.ltrb(ltrb); return createGrObj_finish(b, saveVal); // store the object and return its index } private double gr_collide(int obj1Num, int obj2Num) { double fail = -1; double xfalse = 0; double xtrue = 1; if (obj1Num < 0 || obj1Num >= DisplayList.size()) { RunTimeError("Object 1 Number out of range"); return fail; } GR.BDraw obj1 = DisplayList.get(obj1Num); // Get the first object if (obj1.isHidden()) return xfalse; // If hidden then no collide if (obj2Num < 0 || obj2Num >= DisplayList.size()) { RunTimeError("Object 2 Number out of range"); return fail; } GR.BDraw obj2 = DisplayList.get(obj2Num); // Get the second object if (obj2.isHidden()) return xfalse; // If hidden then no collide Rect r1 = gr_getBounds(obj1); if (r1 == null) return fail; Rect r2 = gr_getBounds(obj2); if (r2 == null) return fail; // Note: Rect.intersects(Rect, Rect) does not consider right edge to be in the Rect // so we can't use it. return ((r1.bottom < r2.top) || // Test for collision (r2.bottom < r1.top) || (r1.right < r2.left) || (r2.right < r1.left)) ? xfalse : xtrue; } @SuppressLint("RtlHardcoded") // suppress false warning on Paint.Align.LEFT and RIGHT private Rect gr_getBounds(GR.BDraw b) { // get the bounding box of a graphical object int left, top, right, bottom; Rect bounds = null; GR.Type type = b.type(); switch (type) { case Circle: int cx = b.x(); int cy = b.y(); int cr = b.radius(); bounds = new Rect(cx - cr, cy - cr, cx + cr, cy + cr); break; case Arc: case Oval: case Point: case Rect: left = b.left(); top = b.top(); right = b.right(); bottom = b.bottom(); bounds = new Rect(left, top, right, bottom); if (bounds.width() < 0) { bounds.set(right, top, left, bottom); } if (bounds.height() < 0) { bounds.set(left, bottom, right, top); } break; case Bitmap: left = b.left(); top = b.top(); Bitmap theBitmap = BitmapList.get(b.bitmap()); right = left + theBitmap.getWidth(); bottom = top + theBitmap.getHeight(); bounds = new Rect(left, top, right, bottom); break; case Text: bounds = new Rect(); Paint paint = PaintList.get(b.paint()); String text = b.text(); paint.getTextBounds(text, 0, text.length(), bounds); // returns the minimum bounding box // from implied origin (0,0) int tx = b.x(); int ty = b.y(); float tw = paint.measureText(text); // width used for alignment // generally more than theRect.width() Paint.Align align = paint.getTextAlign(); switch (align) { // adjust origin for alignment case LEFT: bounds.offset(tx, ty); break; case CENTER: bounds.offset((int) (tx - tw / 2), ty); break; case RIGHT: bounds.offset((int) (tx - tw), ty); break; } break; default: break; } return bounds; } private boolean execute_gr_cls() { if (!checkEOL()) return false; DisplayListClear(GR.Type.Null); return true; } private int getBitmapArg() { // get the bitmap number return getBitmapArg("Invalid Bitmap Pointer"); // with the default error message } private int getBitmapArg(String errMsg) { // get and validate the bitmap number if (!evalNumericExpression()) return -1; int bitmapPtr = EvalNumericExpressionValue.intValue(); if (bitmapPtr < 1 | bitmapPtr >= BitmapList.size()) { RunTimeError(errMsg); bitmapPtr = -1; } return bitmapPtr; } private int getObjectNumber() { // get the Graphics Object Number return getObjectNumber("Object out of range"); // with the default error message } private int getObjectNumber(String errMsg) { // get Object Number, set RTE if out of range int obj = getGraphicsObjectNumber(); if (obj == -1) { RunTimeError(errMsg); } // forced to -1 if out of range return obj; } private int getGraphicsObjectNumber() { // get and validate the Graphics Object Number if (!evalNumericExpression()) return -1; int obj = EvalNumericExpressionValue.intValue(); if (obj < 0 || obj >= DisplayList.size()) { // if invalid Object Number obj = -1; // force to standard error value (-1) } return obj; } private boolean execute_gr_group_objs() { // create a Group from the object numbers on the command line GR.BDraw b = createGrObj_start(GR.Type.Group); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; ArrayList<Double> list = new ArrayList<Double>(); // get object numbers, put them in a list boolean isComma = isNext(','); while (isComma) { double lObj = getObjectNumber(); if (lObj < 0.0) return false; list.add(lObj); isComma = isNext(','); } if (!checkEOL()) return false; int listIndex = theLists.size(); // store as a new numeric list theLists.add(list); theListsType.add(Var.Type.NUM); b.list(listIndex, list); // attach the list to the Group Object return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_group_list() { // create a group from a list GR.BDraw b = createGrObj_start(GR.Type.Group); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int listIndex = getListArg(Var.Type.NUM); // reuse old list or create new one if (listIndex < 0) return false; if (!checkEOL()) return false; ArrayList<Double> list = theLists.get(listIndex); // retrieve the list b.list(listIndex, list); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_group_getdl() { // create a group from the current Display List GR.BDraw b = createGrObj_start(GR.Type.Group); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; boolean keepHiddenObjects = false; if (isNext(',')) { // Optional "hidden" flag if (!evalNumericExpression()) { return false; } keepHiddenObjects = (EvalNumericExpressionValue != 0.0); } if (!checkEOL()) { return false; } // line must end with ']' ArrayList<Double> list = new ArrayList<Double>(); // copy Display List to a list for (Integer Idx : RealDisplayList) { // for each object index int idx = (Idx == null) ? 0 : Idx.intValue(); boolean include = ((idx != 0) && // if not null or index of null object... (keepHiddenObjects || // ... and either keeping all objects... DisplayList.get(idx).isVisible())); // ... or object is not hidden... if (include) { list.add((double) idx); } // ... then put index in the new list } int listIndex = theLists.size(); // store as a new numeric list theLists.add(list); theListsType.add(Var.Type.NUM); b.list(listIndex, list); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_group_newdl() { // set a new display list from a group int obj = getObjectNumber(); // get the Group Object number if (obj < 0) return false; if (!checkEOL()) return false; synchronized (DisplayList) { GR.BDraw b = DisplayList.get(obj); // get Group Object if (b.type() != GR.Type.Group) { return false; } // make sure it's a group ArrayList<Double> list = b.list(); // retrive the list RealDisplayList.clear(); RealDisplayList.add(0); // first entry points to null object for (Double id : list) { // copy the group list to the Display List RealDisplayList.add(id.intValue()); } } return true; } private boolean execute_gr_show(GR.VISIBLE show) { int obj = getObjectNumber(); // get the Graphics Object number if (obj < 0) return false; if (!checkEOL()) return false; GR.BDraw b = DisplayList.get(obj); // get Graphics Object if (b.type() != GR.Type.Group) { // if it is not a Group b.show(show); // hide or show it return true; } ArrayList<Double> list = b.list(); // Group: get the list of objects to change if (list == null) return true; // nothing to do int dlSize = DisplayList.size(); for (Double d : list) { obj = d.intValue(); // get each index from the list if ((obj < 0) || (obj >= dlSize)) { return RunTimeError("Object out of range"); } b = DisplayList.get(obj); // get each Graphics Object to change b.show(show); // show or hide it } return true; } private boolean execute_gr_move() { int obj = getObjectNumber(); // get the Graphics Object number if (obj < 0) return false; int[] dxdy = { 0, 0 }; // default: deltas both zero if (isNext(',') ? !getOptExprs(dxdy) // get the deltas if there are any : !checkEOL()) return false; GR.BDraw b = DisplayList.get(obj); // get Graphics Object if (b.type() != GR.Type.Group) { // if it is not a Group b.move(dxdy); // move it return true; } ArrayList<Double> list = b.list(); // Group: get the list of objects to change if (list == null) return true; // nothing to do int dlSize = DisplayList.size(); for (Double d : list) { obj = d.intValue(); // get each index from the list if ((obj < 0) || (obj >= dlSize)) { return RunTimeError("Object out of range"); } b = DisplayList.get(obj); // get each Graphics Object to change b.move(dxdy); // move it } return true; } private boolean execute_gr_get_position() { int obj = getObjectNumber(); if (obj < 0) return false; if (!isNext(',') || !getNVar()) return false; Var.Val xVar = mVal; if (!isNext(',') || !getNVar()) return false; Var.Val yVar = mVal; if (!checkEOL()) return false; GR.BDraw b = DisplayList.get(obj); // get the Graphics Object xVar.val(b.x()); yVar.val(b.y()); return true; } private boolean execute_gr_get_value() { int obj = getObjectNumber(); if (obj < 0) return false; GR.BDraw b = DisplayList.get(obj); // get the Graphics Object while (isNext(',')) { // collect tag/var pairs if (!getStringArg()) return false; // get each parameter string String parm = StringConstant; if (!b.type().hasParameter(parm)) { return RunTimeError("Object does not contain " + parm); } if (!isNext(',') || !getVar()) return false; // var for value Var.Val val = mVal; boolean varIsNumeric = val.isNumeric(); if (varIsNumeric == parm.equals("text")) { // error if numeric var and "text" tag return RunTimeError("Wrong var type for tag: " + parm); // or string var and not "text" tag } if (varIsNumeric) { double value = b.getValue(parm); val.val(value); } else { String theText = b.text(); val.val(theText); } } return checkEOL(); } private boolean execute_gr_get_type() { Var.Val val = null; int obj = getGraphicsObjectNumber(); // forced to -1 if out of range if (obj == -1) { writeErrorMsg("Not a graphical object"); } // message for GetError$() function else { clearErrorMsg(); } if (isNext(',')) { if (!getSVar()) return false; // var for type string val = mVal; } if (!checkEOL()) return false; if (val != null) { // if there is a return variable String type; // value to return if (obj != -1) { // if there is a valid object number GR.BDraw b = DisplayList.get(obj); // get the Graphics Object type = b.type().type(); // get its type } else { type = ""; } // else no type (invalid object number) val.val(type); } return true; } private boolean execute_gr_get_params() { int obj = getObjectNumber(); if (obj < 0) return false; GR.BDraw b = DisplayList.get(obj); // get the Graphics Object if (!isNext(',')) return false; Var var = getArrayVarForWrite(TYPE_STRING); // get the result array variable if (var == null) return false; // must name a new string array variable if (!checkEOL()) return false; // line must end with ']' String[] params = b.type().parameters(); int length = params.length; /* Puts the list of keys into a new array */ return ListToBasicStringArray(var, Arrays.asList(params), length); } private boolean execute_gr_touch(int p) { if (!getNVar()) return false; // boolean variable Var.Val flagVar = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; // x variable Var.Val xVar = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; // y variable Var.Val yVar = mVal; if (!checkEOL()) return false; flagVar.val(NewTouch[p] ? 1.0 : 0.0); // return touched flag as numerical value if (TouchX[p] != -1) { // if ever touched xVar.val(TouchX[p]); // then report the last touch yVar.val(TouchY[p]); } return true; } private boolean execute_gr_bound_touch(int p) { if (!getNVar()) return false; // boolean variable Var.Val flagVar = mVal; if (!isNext(',')) return false; int[] bounds = getArgs4I(); // [left, top, right, bottom] if (bounds == null) return false; // error getting values if (!checkEOL()) return false; int left = bounds[0]; int top = bounds[1]; int right = bounds[2]; int bottom = bounds[3]; if (right < left) { left = bounds[2]; right = bounds[0]; } if (bottom < top) { top = bounds[3]; bottom = bounds[1]; } boolean flag = false; if (NewTouch[p]) { // if currently being touched flag = (TouchX[p] >= left && TouchX[p] <= right && // true iff touch was in bounding rect TouchY[p] >= top && TouchY[p] <= bottom); } flagVar.val(flag ? 1.0 : 0.0); // return flag as numerical value return true; } private boolean execute_gr_text_draw() { GR.BDraw b = createGrObj_start(GR.Type.Text); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] xy = getArgsII(); if (xy == null) return false; if (!isNext(',') || !getStringArg()) return false; b.xy(xy); b.text(StringConstant); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_text_align() { int[] args = { -1, -1 }; // default to unchanged alignment and new Paint if (!getOptExprs(args)) return false; int alignCode = args[0]; int paintIdx = args[1]; Paint.Align align = null; // validate and convert alignment parameter if (alignCode == 1) { align = Paint.Align.LEFT; } else if (alignCode == 2) { align = Paint.Align.CENTER; } else if (alignCode == 3) { align = Paint.Align.RIGHT; } else if (alignCode != -1) { return RunTimeError("Align value not 1, 2 or 3 at "); } Paint paint = getWorkingPaint(paintIdx); // validate Paint index parameter and get Paint if (paint == null) return false; // index out of range if (align != null) { paint.setTextAlign(align); } return true; } private boolean execute_gr_text_size() { byte[] type = { 1, 1 }; // two optional numeric arguments Double[] args = { null, null }; String[] dummy = { null, null }; // not used, no String arguments if (!getOptExprs(type, args, dummy)) return false; int paintIdx = (args[1] != null) ? args[1].intValue() : -1; // default to current Paint Paint paint = getWorkingPaint(paintIdx); // null if invalid index if (args[0] != null) { // if size provided float size = args[0].floatValue(); // get size if (size <= 0.0f) { return RunTimeError("Size must be > 0"); } if (paint != null) paint.setTextSize(size); } if (paint == null) return false; // error reporting is in parameter order return true; } private boolean execute_gr_text_underline() { int[] args = { -1, -1 }; // default to toggle and new Paint if (!getOptExprs(args)) return false; int select = args[0]; int paintIdx = args[1]; Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range // -1 toggles underlining, 0 clears it, anything else sets it. boolean flag = (select == -1) ? !paint.isUnderlineText() : (select != 0); paint.setUnderlineText(flag); return true; } private boolean execute_gr_text_skew() { byte[] type = { 1, 1 }; // two optional numeric arguments Double[] args = { null, null }; String[] dummy = { null, null }; // not used, no String arguments if (!getOptExprs(type, args, dummy)) return false; int paintIdx = (args[1] != null) ? args[1].intValue() : -1; // default to current Paint Paint paint = getWorkingPaint(paintIdx); // null if invalid index if (paint == null) return false; if (args[0] != null) { // if skew provided float skew = args[0].floatValue(); // get skew paint.setTextSkewX(skew); } return true; } private boolean execute_gr_text_bold() { int[] args = { -1, -1 }; // default to toggle and new Paint if (!getOptExprs(args)) return false; int select = args[0]; int paintIdx = args[1]; Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range // -1 toggles bold, 0 clears it, anything else sets it. boolean flag = (select == -1) ? !paint.isFakeBoldText() : (select != 0); paint.setFakeBoldText(flag); return true; } private boolean execute_gr_text_strike() { int[] args = { -1, -1 }; // default to toggle and new Paint if (!getOptExprs(args)) return false; int select = args[0]; int paintIdx = args[1]; Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range // -1 toggles strike, 0 clears it, anything else sets it. boolean flag = (select == -1) ? !paint.isStrikeThruText() : (select != 0); paint.setStrikeThruText(flag); return true; } private GR.BDraw getTextObject(int dlIndex) { if (dlIndex < 0 || dlIndex >= DisplayList.size()) { RunTimeError("Object Number out of range"); return null; } GR.BDraw b = DisplayList.get(dlIndex); if (b.type() != GR.Type.Text) { RunTimeError("Not a text object"); return null; } return b; } private boolean execute_gr_get_textbounds() { Paint paint; String text; if (evalNumericExpression()) { // if argument is an object number int index = EvalNumericExpressionValue.intValue(); GR.BDraw b = getTextObject(index); if (b == null) return false; paint = PaintList.get(b.paint()); // use the text object's paint text = b.text(); // get text from the text object } else { if (SyntaxError) return false; if (!getStringArg()) return false; text = StringConstant; // argument is the text to measure paint = PaintList.get(PaintList.size() - 1); // use current Paint } if (!isNext(',')) return false; Var.Val[] val = getArgs4NVar(); // [left, top, right, bottom] if (val == null) return false; // error getting variables if (!checkEOL()) return false; Rect bounds = new Rect(); paint.getTextBounds(text, 0, text.length(), bounds); val[0].val(bounds.left); val[1].val(bounds.top); val[2].val(bounds.right); val[3].val(bounds.bottom); return true; } private boolean execute_gr_text_height() { if (isEOL()) return true; // user asked for no data // Three optional numeric variables for height, up, and down values byte[] types = { 1, 1, 1 }; // type of each variable int nArgs = types.length; Var.Val[] args = new Var.Val[nArgs]; // Val object for each variable if (!getOptVars(types, args)) return false; Paint currentPaint = PaintList.get(PaintList.size() - 1); Paint.FontMetrics fm = currentPaint.getFontMetrics(); float height = currentPaint.getTextSize(); float ascent = fm.ascent; float descent = fm.descent; float[] vals = { height, ascent, descent }; for (int arg = 0; arg < nArgs; ++arg) { Var.Val argval = args[arg]; // Val object to return each value if (argval != null) { argval.val(vals[arg]); } } return true; } private boolean execute_gr_text_width() { if (!getNVar()) return false; // width return variable Var.Val val = mVal; if (!isNext(',')) return false; Paint paint; String text; if (evalNumericExpression()) { // if argument is an object number int index = EvalNumericExpressionValue.intValue(); GR.BDraw b = getTextObject(index); if (b == null) return false; paint = PaintList.get(b.paint()); // use the text object's paint text = b.text(); // get text from the text object } else { if (SyntaxError) return false; if (!getStringArg()) return false; text = StringConstant; // argument is the text to measure paint = PaintList.get(PaintList.size() - 1); // use current Paint } if (!checkEOL()) return false; double w = paint.measureText(text); // get the string's width val.val(w); // save the width into the var return true; } private boolean execute_gr_bitmap_load() { clearErrorMsg(); if (!getNVar()) return false; // bitmap pointer variable Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file path if (!checkEOL()) return false; String errMsg = null; Bitmap bitmap = null; String fileName = StringConstant; // the filename as given by the user BufferedInputStream bis = null; // establish an input stream try { bis = Basic.getBufferedInputStream(Basic.DATA_DIR, fileName); } catch (Exception e) { errMsg = e.getMessage(); } if (bis == null) { if (errMsg == null) { errMsg = "No bitmap found"; } // can this happen? } else { try { bitmap = BitmapFactory.decodeStream(bis); } // create bitmap from the input stream catch (OutOfMemoryError oom) { errMsg = oom.getMessage(); } finally { try { bis.close(); } catch (IOException e) { return RunTimeError(e); } } } return createBitmap_finish(bitmap, val, errMsg); // store the bitmap and return its index } private boolean execute_gr_bitmap_delete() { int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; if (!checkEOL()) return false; Bitmap bitmap = BitmapList.get(bitmapPtr); // get the bitmap if (bitmap != null) { bitmap.recycle(); } BitmapList.set(bitmapPtr, null); return true; } private boolean execute_gr_bitmap_scale() { clearErrorMsg(); if (!getNVar()) return false; // destination bitmap pointer variable Var.Val val = mVal; if (!isNext(',')) return false; int bitmapPtr = getBitmapArg(); // get source bitmap number if (bitmapPtr < 0) return false; Bitmap srcBitmap = BitmapList.get(bitmapPtr); // get the bitmap if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // get width int Width = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // get height int Height = EvalNumericExpressionValue.intValue(); boolean parm = true; if (isNext(',')) { // optional scale parameter if (!evalNumericExpression()) return false; if (EvalNumericExpressionValue == 0.0) parm = false; } if (!checkEOL()) return false; if (srcBitmap == null) { return RunTimeError("Bitmap was deleted"); } if (Width == 0 || Height == 0) { return RunTimeError("Width and Height must not be zero"); } String errMsg = null; Bitmap bitmap = null; try { bitmap = Bitmap.createScaledBitmap(srcBitmap, Width, Height, parm); } catch (OutOfMemoryError oom) { errMsg = oom.getMessage(); } if (bitmap == srcBitmap) { // Scale 1:1 does not create a new bitmap. Make a copy. bitmap = srcBitmap.copy(srcBitmap.getConfig(), false); } return createBitmap_finish(bitmap, val, errMsg); // store the bitmap and return its index } private boolean execute_gr_bitmap_size() { int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; Bitmap srcBitmap = BitmapList.get(bitmapPtr); // access the bitmap if (srcBitmap == null) { return RunTimeError("Bitmap was deleted"); } if (!isNext(',')) return false; if (!getNVar()) return false; // get the width variable Var.Val wVar = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; // get the height variable Var.Val hVar = mVal; if (!checkEOL()) return false; int w = srcBitmap.getWidth(); // get the image width int h = srcBitmap.getHeight(); // get the image height wVar.val(w); // set the width value hVar.val(h); // set the height value return true; } private boolean execute_gr_bitmap_crop() { clearErrorMsg(); if (!getNVar()) return false; // dest Graphic Object variable Var.Val val = mVal; if (!isNext(',')) return false; int bitmapPtr = getBitmapArg("Invalid Source Bitmap Pointer"); // get source bitmap number if (bitmapPtr < 0) return false; Bitmap srcBitmap = BitmapList.get(bitmapPtr); // get source bitmap if (!isNext(',')) return false; int[] bounds = getArgs4I(); // [x, y, width, height] if (bounds == null) return false; // error getting values if (!checkEOL()) return false; if (srcBitmap == null) { return RunTimeError("Bitmap was deleted"); } String errMsg = null; Bitmap bitmap = null; try { bitmap = Bitmap.createBitmap(srcBitmap, bounds[0], bounds[1], bounds[2], bounds[3]); } catch (OutOfMemoryError oom) { errMsg = oom.getMessage(); } if (bitmap == srcBitmap) { // "Crop" to full image does not create a new bitmap. Make a copy. bitmap = srcBitmap.copy(srcBitmap.getConfig(), false); } return createBitmap_finish(bitmap, val, errMsg); // store the bitmap and return its index } private void fillscan(int[] arrPixels, int color, int sx, int ex, int y, int width, Stack<Point> q) { if ((arrPixels == null) || (q == null)) return; // Scan part of a line for points of the target color, // pushing one new seed for each segment of connected points found. boolean open = false; // if true, have seeded a segment int x0 = width * y; // first point of line for (int x = sx; x <= ex; ++x) { boolean match = arrPixels[x0 + x] == color; if (!open && match) { q.push(new Point(x, y)); // start a new segment open = true; } else if (open && !match) { open = false; // close this segment } } } // 2015-05-31 Algorithm by George Matei, implemented by Chaand. private boolean execute_gr_bitmap_fill() { // floodfill a region of a bitmap int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; if (!isNext(',')) return false; int[] xy = getArgsII(); // x, y coordinates of point in region to color if (xy == null) return false; if (!checkEOL()) return false; Bitmap bmp = BitmapList.get(bitmapPtr); // get the bitmap if (!checkBitmapPoint(bmp, xy)) return false; // is point in bitmap? int targetColor = bmp.getPixel(xy[0], xy[1]); // get the original color of the bitmap pixel Paint currentPaint = PaintList.get(PaintList.size() - 1); int replacementColor = currentPaint.getColor(); // get the color to change to if (targetColor == replacementColor) return true; // nothing to do int width = bmp.getWidth(); // get the bitmap dimensions int xmax = width - 1; int height = bmp.getHeight(); int ymax = height - 1; int[] arrPixels = new int[width * height]; // array for the bitmap pixels [TODO: too much memory?] bmp.getPixels(arrPixels, 0, width, 0, 0, width, height); // get the bitmap pixels Stack<Point> q = new Stack<Point>(); q.push(new Point(xy[0], xy[1])); // first seed while (!q.empty()) { Point p = q.pop(); int y = p.y; int x0 = width * p.y; // index of point (0, y) // Change all connected points of the old color to the new color int sx = p.x; // start point int ex = sx; // end point arrPixels[x0 + sx] = replacementColor; while ((sx > 0) && (arrPixels[x0 + sx - 1] == targetColor)) { arrPixels[x0 + --sx] = replacementColor; } while ((ex < xmax) && (arrPixels[x0 + ex + 1] == targetColor)) { arrPixels[x0 + ++ex] = replacementColor; } bmp.setPixels(arrPixels, x0 + sx, width, sx, y, ex - sx + 1, 1); if (y > 0) { // if there is a line above fillscan(arrPixels, targetColor, sx, ex, y - 1, width, q); // push a point for each segment of matching color } if (y < ymax) { // if there is a line below fillscan(arrPixels, targetColor, sx, ex, y + 1, width, q); // push a point for each segment of matching color } } return true; } private boolean execute_gr_bitmap_draw() { GR.BDraw b = createGrObj_start(GR.Type.Bitmap); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; if (BitmapList.get(bitmapPtr) == null) { // check the bitmap return RunTimeError("Bitmap was deleted"); } b.bitmap(bitmapPtr); // store the bitmap number if (!isNext(',')) return false; int[] xy = getArgsII(); if (xy == null) return false; b.xy(xy); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_bitmap_create() { clearErrorMsg(); if (!getNVar()) return false; // get bitmap pointer variable Var.Val val = mVal; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // get the width int width = EvalNumericExpressionValue.intValue(); if (width <= 0) { return RunTimeError("Width must be >= 0"); } if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // get the height int height = EvalNumericExpressionValue.intValue(); if (height <= 0) { return RunTimeError("Height must be >= 0"); } if (!checkEOL()) return false; String errMsg = null; Bitmap bitmap = null; try { bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError oom) { errMsg = oom.getMessage(); } return createBitmap_finish(bitmap, val, errMsg); // store the bitmap and return its index } private boolean execute_gr_rotate_start() { GR.BDraw b = new GR.BDraw(GR.Type.RotateStart); // create a new object of type Rotate Start if (!evalNumericExpression()) return false; // get angle b.angle(EvalNumericExpressionValue.floatValue()); if (!isNext(',')) return false; int[] xy = getArgsII(); if (xy == null) return false; b.xy(xy); Var.Val val = null; if (isNext(',')) { // optional graphic object pointer variable if (!getNVar()) return false; val = mVal; } if (!checkEOL()) return false; if (val != null) { val.val(DisplayList.size()); } // save the object number into the var DisplayListAdd(b); // put the new object into the display list return true; } private boolean execute_gr_rotate_end() { GR.BDraw b = new GR.BDraw(GR.Type.RotateEnd); // create a new object of type Rotate End Var.Val val = null; if (!isEOL()) { if (!getNVar()) return false; val = mVal; if (!checkEOL()) return false; } if (val != null) { val.val(DisplayList.size()); } // save the object number into the var DisplayListAdd(b); // add the object to the display list return true; } private boolean execute_gr_modify() { int obj = getObjectNumber("Object Number out of range"); if (obj < 0) return false; GR.BDraw b = DisplayList.get(obj); // get the object to change GR.Type type = b.type(); while (isNext(',')) { if (!getStringArg()) return false; // get the parameter string if (!isNext(',')) return false; String parm = StringConstant; String sVal = ""; int iVal = 0; float fVal = 0.0f; if (parm.equals("text")) { if (!getStringArg()) return false; // get the parameter string sVal = StringConstant; } else { if (!evalNumericExpression()) return false; // get parameter value fVal = EvalNumericExpressionValue.floatValue(); iVal = EvalNumericExpressionValue.intValue(); } // For now, these list validations must be done here and not in BDraw.modify() if (parm.equals("paint")) { if ((iVal < 1) || (iVal >= PaintList.size())) { return RunTimeError("Invalid Paint object number"); } if (type == GR.Type.Group) { // Experiment: modify group Paint means modify Paint of all group objects. // TODO: This is very clumsy. Fix it. int dlSize = DisplayList.size(); for (Double d : b.list()) { obj = d.intValue(); // get each index from the list if ((obj < 0) || (obj >= dlSize)) { return RunTimeError("Object out of range"); } GR.BDraw toMod = DisplayList.get(obj); // get each Graphics Object to change toMod.paint(iVal); // modify it } // note: the group's Paint gets changed, too } b.paint(iVal); continue; // next parameter } switch (type) { case Bitmap: if (parm.equals("bitmap")) { if ((iVal < 0) | (iVal >= BitmapList.size())) { return RunTimeError("Bitmap pointer out of range"); } b.bitmap(iVal); continue; // next parameter } break; case Group: // Experiment: modify group alpha means modify alpha of all group objects. // TODO: This is very clumsy. Fix it. if (parm.equals("alpha")) { int dlSize = DisplayList.size(); for (Double d : b.list()) { obj = d.intValue(); // get each index from the list if ((obj < 0) || (obj >= dlSize)) { return RunTimeError("Object out of range"); } GR.BDraw toMod = DisplayList.get(obj); // get each Graphics Object to change toMod.alpha(iVal); // modify it } b.alpha(iVal); continue; // next parameter } /* FALL THROUGH to handle "list" */ case Poly: if (parm.equals("list")) { // For Now, list parm must be done here. Validate and attach list to poly object. if (!setPolyList(b, iVal)) return false; continue; // next parameter } default: break; } if (!b.modify(parm, iVal, fVal, sVal)) { return RunTimeError(b.errorMsg()); } } return checkEOL(); } private boolean execute_gr_orientation() { if (!evalNumericExpression()) return false; // get the mode (landscape or portrait) if (!checkEOL()) return false; int mode = EvalNumericExpressionValue.intValue(); // Log.d(LOGTAG, "GR.Orientation " + mode); GR.drawView.setOrientation(mode); return true; } private boolean execute_gr_screen() { if (!getNVar()) return false; // width variable Var.Val wVar = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; // height variable Var.Val hVar = mVal; Var.Val dVar = null; if (isNext(',')) { if (!getNVar()) return false; // optional density variable dVar = mVal; } if (!checkEOL()) return false; Point size = new Point(); int densityDpi = GR.drawView.getWindowMetrics(size); wVar.val(size.x); hVar.val(size.y); if (dVar != null) { dVar.val(densityDpi); } return true; } private boolean execute_gr_statusbar() { Var.Val heightVar = null; Var.Val showingVar = null; boolean isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // height variable heightVar = mVal; isComma = isNext(','); } if (isComma) { if (!getNVar()) return false; // showing variable showingVar = mVal; } if (!checkEOL()) return false; if (heightVar != null) { double height = 0.0; Resources res = getResources(); int resID = res.getIdentifier("status_bar_height", "dimen", "android"); if (resID > 0) { height = res.getDimensionPixelSize(resID); } heightVar.val(height); } if (showingVar != null) { showingVar.val(mShowStatusBar ? 1.0 : 0.0); } return true; } private boolean execute_gr_front() { if (!evalNumericExpression()) return false; // get flag if (!checkEOL()) return false; if (EvalNumericExpressionValue == 0) { mConsoleIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(mConsoleIntent); } else { mGrIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(mGrIntent); } try { Thread.sleep(100); } catch (InterruptedException e) { } return true; } private boolean execute_gr_set_pixels() { GR.BDraw b = createGrObj_start(GR.Type.SetPixels); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; if (!var.isNumeric()) { return RunTimeError(EXPECT_NUM_ARRAY); } Integer[] pair = new Integer[2]; if (!getIndexPair(pair)) return false; // get values inside [], if any int[] xy = { 0, 0 }; if (isNext(',')) { if (!evalNumericExpression()) return false; xy[0] = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; if (!evalNumericExpression()) return false; xy[1] = EvalNumericExpressionValue.intValue(); } if (!checkEOL()) return false; Var.ArrayDef array = var.arrayDef(); if (!getArraySegment(array, pair)) return false; // get segment base and length int base = pair[0].intValue(); int length = pair[1].intValue(); if ((length % 2) != 0) { return RunTimeError("Not an even number of elements in pixel array"); } b.array(array, base, length); b.xy(xy); return createGrObj_finish(b, saveVal); // store the object and return its index } private boolean execute_gr_get_bmpixel() { int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; if (!isNext(',')) { return false; } Bitmap SourceBitmap = BitmapList.get(bitmapPtr); // get the bitmap return getTheBMpixel(SourceBitmap); } private boolean execute_gr_get_pixel() { Bitmap b = getTheBitmap(); // get the DrawingCache bitmap boolean retval = (b != null) ? getTheBMpixel(b) // get the requested pixel : RunTimeError("Could not capture screen bitmap. Sorry."); b = null; GR.drawView.destroyDrawingCache(); // clean up DrawingCache return retval; } // After it's done with the Bitmap, caller should call destroyDrawingCache() private Bitmap getTheBitmap() { synchronized (GR.drawView) { GR.drawView.setDrawingCacheEnabled(true); GR.drawView.buildDrawingCache(); // Build the cache return GR.drawView.getDrawingCache(); // get the bitmap } } private boolean getTheBMpixel(Bitmap b) { int[] xy = getArgsII(); // get x and y if (xy == null) return false; // error getting coordinate values if (!isNext(',')) return false; Var.Val[] argb = getArgs4NVar(); // [a, r, g, b] if (argb == null) return false; // error getting variables if (!checkEOL()) return false; // end of syntax checks if (!checkBitmapPoint(b, xy)) return false; // is point in bitmap? int pixel = b.getPixel(xy[0], xy[1]); // get the pixel from the bitmap argb[0].val(Color.alpha(pixel)); // get the components of the pixel argb[1].val(Color.red(pixel)); argb[2].val(Color.green(pixel)); argb[3].val(Color.blue(pixel)); return true; } private boolean checkBitmapPoint(Bitmap b, int xy[]) { if (b == null) { return RunTimeError("Bitmap was deleted"); } if (xy == null) { return false; } int x = xy[0]; int y = xy[1]; if ((x < 0) || (x >= b.getWidth()) || (y < 0) || (y >= b.getHeight())) { return RunTimeError("x or y exceeds size of bitmap"); } return true; // point is in bitmap } private boolean writeBitmapToFile(Bitmap b, String fn, int quality) { CompressFormat format = CompressFormat.PNG; // assume png String tFN = fn.toUpperCase(Locale.getDefault()); // temp convert fn to upper case if (tFN.endsWith(".JPG")) format = CompressFormat.JPEG; // test jpg else if (!tFN.endsWith(".PNG")) fn += ".png"; // test png File file = new File(Basic.getDataPath(fn)); // build full path FileOutputStream ostream = null; try { // write the file file.createNewFile(); ostream = new FileOutputStream(file); b.compress(format, quality, ostream); // write png or jpg ostream.close(); } catch (Exception e) { FileInfo.closeStream(ostream, null); return RunTimeError(e); } return true; } private boolean execute_gr_save() { if (!getStringArg()) return false; // Get the filename String fn = StringConstant; int quality = 50; // set default jpeg quality if (isNext(',')) // if there is an optional quality parm { if (!evalNumericExpression()) return false; // evaluate it quality = EvalNumericExpressionValue.intValue(); if (quality < 0 || quality > 100) { return RunTimeError("Quality must be between 0 and 100"); } } if (!checkEOL()) return false; Bitmap b = getTheBitmap(); // get the DrawingCache bitmap boolean retval = (b != null) ? writeBitmapToFile(b, fn, quality) : RunTimeError("Problem creating bitmap"); b = null; GR.drawView.destroyDrawingCache(); // clean up DrawingCache return retval; } private boolean execute_screen_to_bitmap() { clearErrorMsg(); if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; String errMsg = null; Bitmap b = getTheBitmap(); // get the DrawingCache bitmap if (b == null) { errMsg = "Could not capture screen bitmap. Sorry."; } else { try { b = b.copy(Bitmap.Config.ARGB_8888, true); } // copy the bitmap from the DrawingCache catch (Exception e) { errMsg = e.getMessage(); } catch (OutOfMemoryError oom) { errMsg = oom.getMessage(); } } createBitmap_finish(b, val, errMsg); // store the bitmap and return its index GR.drawView.destroyDrawingCache(); // clean up DrawingCache return true; } private boolean execute_bitmap_save() { int bitmapPtr = getBitmapArg(); // get the bitmap number if (bitmapPtr < 0) return false; Bitmap SrcBitMap = BitmapList.get(bitmapPtr); // get the bitmap if (!isNext(',')) return false; if (!getStringArg()) return false; // get the filename String fn = StringConstant; int quality = 50; // set default jpeg quality if (isNext(',')) { // if there is an optional quality parm if (!evalNumericExpression()) return false; // evaluate it quality = EvalNumericExpressionValue.intValue(); } if (!checkEOL()) return false; if (SrcBitMap == null) { return RunTimeError("Bitmap was deleted"); } if (quality < 0 || quality > 100) { return RunTimeError("Quality must be between 0 and 100"); } return writeBitmapToFile(SrcBitMap, fn, quality); } private boolean execute_gr_scale() { if (!evalNumericExpression()) return false; // get x double x = EvalNumericExpressionValue; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // get y double y = EvalNumericExpressionValue; if (!checkEOL()) return false; GR.scaleX = (float) x; GR.scaleY = (float) y; return true; } private boolean execute_gr_clip() { GR.BDraw b = createGrObj_start(GR.Type.Clip); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; int[] ltrb = getArgs4I(); if (ltrb == null) return false; b.ltrb(ltrb); int RegionOp = 0; if (isNext(',')) { if (!evalNumericExpression()) return false; RegionOp = EvalNumericExpressionValue.intValue(); if (RegionOp < 0 || RegionOp > 5) { return RunTimeError("Region Operator not 0 to 5"); } } b.clipOp(RegionOp); return createGrObj_finish(b, saveVal); // store the object and return its index } // Validate list and attach it to a Poly-type BDraw object. // Do it here, not in GR, for access to RunTimeError(). private boolean setPolyList(GR.BDraw poly, int listIndex) { if ((listIndex < 1) || (listIndex >= theLists.size())) { return RunTimeError("Invalid list pointer"); } if (theListsType.get(listIndex) != Var.Type.NUM) { return RunTimeError("List must be numeric"); } ArrayList<Double> list = theLists.get(listIndex); int size = list.size(); if (size < 4) { return RunTimeError("List must have at least two points"); } if ((size % 2) != 0) { return RunTimeError("List must have even number of elements"); } poly.list(listIndex, list); return true; } private boolean execute_gr_poly() { GR.BDraw b = createGrObj_start(GR.Type.Poly); // create Graphic Object and get variable if (b == null) return false; Var.Val saveVal = mVal; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // list pointer int theListIndex = EvalNumericExpressionValue.intValue(); if (!setPolyList(b, theListIndex)) return false; // validate and attach list to poly object int[] xy = { 0, 0 }; if (isNext(',')) { if (!evalNumericExpression()) return false; xy[0] = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; if (!evalNumericExpression()) return false; xy[1] = EvalNumericExpressionValue.intValue(); } b.xy(xy); return createGrObj_finish(b, saveVal); // store the object and return its index } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private int getNumberOfCameras() { int cameraCount; int level = Build.VERSION.SDK_INT; if (level < Build.VERSION_CODES.GINGERBREAD) { // if SDK < 9 there can be only one camera Camera tCamera = Camera.open(); // Check to see if there is any camera at all cameraCount = (tCamera == null) ? 0 : 1; tCamera.release(); } else { cameraCount = Camera.getNumberOfCameras(); // May be more than one camera } return cameraCount; } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private boolean execute_gr_camera_select() { if (NumberOfCameras < 0) { NumberOfCameras = getNumberOfCameras(); } if (NumberOfCameras == 0) { return RunTimeError("This device does not have a camera."); } int level = Build.VERSION.SDK_INT; if (level < Build.VERSION_CODES.GINGERBREAD) { // if SDK < 9 there can be only one camera CameraNumber = -1; // so no selection is possible return (NumberOfCameras != 0); } int BACK = 1; int FRONT = 2; if (!evalNumericExpression()) { return false; } // Get user's choice if (!checkEOL()) { return false; } int choice = EvalNumericExpressionValue.intValue(); if (choice != BACK && choice != FRONT) { return RunTimeError("Select value must be 1 (Back) or 2 (Front)."); } Camera.CameraInfo CI = new Camera.CameraInfo(); // Determine which camera number is BACK Camera.getCameraInfo(0, CI); // Assume 0 is BACK boolean zero_is_back = (CI.facing == CameraInfo.CAMERA_FACING_BACK); if (NumberOfCameras == 1) { // Camera 0 is the only camera CameraNumber = 0; if ((choice == BACK) && !zero_is_back) { return RunTimeError("Device has no back camera"); } if ((choice == FRONT) && zero_is_back) { return RunTimeError("Device has no front camera"); } } else { CameraNumber = ((choice == BACK) == zero_is_back) ? 0 : 1; } return true; } private boolean execute_camera_shoot(int pictureMode) { if (NumberOfCameras < 0) { NumberOfCameras = getNumberOfCameras(); } if (NumberOfCameras == 0) { return RunTimeError("This device does not have a camera."); } int flashMode = 0; int focusMode = 0; if (!getNVar()) return false; Var.Val val = mVal; if (isNext(',')) { if (!evalNumericExpression()) { return false; } flashMode = EvalNumericExpressionValue.intValue(); if (isNext(',')) { // Focus/2013-07-25 gt if (!evalNumericExpression()) { return false; } focusMode = EvalNumericExpressionValue.intValue(); } } if (!checkEOL()) { return false; } ; CameraBitmap = null; CameraDone = false; Intent cameraIntent = new Intent(Run.this, CameraView.class); // Start the Camera cameraIntent.putExtra(CameraView.EXTRA_PICTURE_MODE, pictureMode); cameraIntent.putExtra(CameraView.EXTRA_CAMERA_NUMBER, CameraNumber); cameraIntent.putExtra(CameraView.EXTRA_FLASH_MODE, flashMode); cameraIntent.putExtra(CameraView.EXTRA_FOCUS_MODE, focusMode); try { startActivityForResult(cameraIntent, BASIC_GENERAL_INTENT); } catch (Exception e) { return RunTimeError(e); } while (!CameraDone) Thread.yield(); double bitmapIndex = 0.0; if (CameraBitmap != null) { bitmapIndex = BitmapList.size(); BitmapList.add(CameraBitmap); } val.val(bitmapIndex); // Save the GR Object index into the var CameraBitmap = null; return true; } private boolean execute_statusbar_show() { String[] msg = { "This command deprecated.", // First line is base of errorMsg "To show status bar, use:", "gr.open alpha, red, green, blue, 1" }; return RunTimeError(msg); } private boolean execute_brightness() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; float value = EvalNumericExpressionValue.floatValue(); if (value < 0.01f) { value = 0.01f; } if (value > 1.0f) { value = 1.0f; } GR.Brightness = value; return true; } private boolean execute_gr_text_typeface() { int[] args = { 1, 1, -1 }; // default typeface and style, new Paint if (!getOptExprs(args)) return false; int face = args[0]; int style = args[1]; int paintIdx = args[2]; Typeface tf; // interpret typeface switch (face) { case 1: tf = Typeface.DEFAULT; break; case 2: tf = Typeface.MONOSPACE; break; case 3: tf = Typeface.SANS_SERIF; break; case 4: tf = Typeface.SERIF; break; default: return RunTimeError("Typeface must be 1, 2, 3 or 4"); } switch (style) { // interpret style case 1: style = Typeface.NORMAL; break; case 2: style = Typeface.BOLD; break; case 3: style = Typeface.ITALIC; break; case 4: style = Typeface.BOLD_ITALIC; break; default: return RunTimeError("Style must be 1, 2, 3 or 4"); } Paint paint = getWorkingPaint(paintIdx); if (paint == null) return false; // index out of range // Done with error messages. tf = Typeface.create(tf, style); paint.setTypeface(tf); // put the typeface into Paint return true; } private boolean execute_gr_touch_resume() { return doResume("No onTouch Interrupt"); } private Typeface getTypeface(String fileName) { Typeface tf = null; File file = new File(Basic.getDataPath(fileName)); if (file.exists()) { tf = Typeface.createFromFile(file); // Create a new Typeface } else { // the file does not exist if (Basic.isAPK) { // we are in APK int resID = Basic.getRawResourceID(fileName); // try to load the file from a raw resource if (resID != 0) { InputStream is = getResources().openRawResource(resID); String outPath = getCacheDir() + "/tmp" + System.currentTimeMillis() + ".raw"; File outFile = new File(outPath); if (copyFile(new BufferedInputStream(is), outFile, null)) { tf = Typeface.createFromFile(outPath); outFile.delete(); // clean up } } else { // try to load the file from assets String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, fileName); tf = Typeface.createFromAsset(getAssets(), assetPath); } } } return tf; } private boolean execute_gr_text_setfont() { int fontPtr = 0; String familyName = null; // default if no font arg int style = Typeface.NORMAL; // default if no style arg int paintIdx = -1; // default to current Paint boolean isComma = isNext(','); if (!isComma && !isEOL()) { // there is a font arg int saveLI = LineIndex; fontPtr = getFontArg(); // get the font number if (fontPtr == -1) return false; // invalid font pointer if (fontPtr == -2) { // not a numeric argument LineIndex = saveLI; if (!getStringArg()) return false; // get the font family name familyName = StringConstant.trim(); } isComma = isNext(','); } if (isComma) { if (!getStringArg()) return false; // get the optional style String str = StringConstant.trim().toLowerCase(Locale.US); if (str.equals("b") || str.equals("bold")) { style = Typeface.BOLD; } else if (str.equals("i") || str.equals("italic")) { style = Typeface.ITALIC; } else if (str.equals("bi") || str.equals("bold_italic")) { style = Typeface.BOLD_ITALIC; } isComma = isNext(','); } if (isComma) { if (!evalNumericExpression()) return false; paintIdx = EvalNumericExpressionValue.intValue(); } if (!checkEOL()) return false; Typeface tf = null; if (fontPtr > 0) { tf = FontList.get(fontPtr); // get specified custom font if (tf == null) { return RunTimeError("Font was deleted"); } } else if (fontPtr == 0) { // no font arg for (int fp = FontList.size() - 1; (fp > 0) && (tf == null); --fp) { tf = FontList.get(fp); // get last font loaded and not deleted } // if no such font, will set default family } if (tf == null) { // font family arg, or no font arg and no fonts loaded tf = Typeface.create(familyName, style); // get the system font for this family name } // null family name sets system default Paint paint = getWorkingPaint(paintIdx); // null if invalid index if (paint == null) return false; paint.setTypeface(tf); return true; } // ****************************************** Audio ******************************************* private boolean executeAUDIO() { // Get Audio command keyword if it is there return executeSubcommand(audio_cmd, "Audio"); // and execute the command } private MediaPlayer getMP(String fileName) { String errMsg = null; MediaPlayer mp = null; Context context = getApplicationContext(); File file = new File(Basic.getDataPath(fileName)); if (file.exists()) { Uri uri = Uri.fromFile(file); // Create Uri for the file if (uri != null) { mp = MediaPlayer.create(context, uri); // Create a new Media Player } } else { // the file does not exist if (Basic.isAPK) { // we are in APK int resID = Basic.getRawResourceID(fileName); // try to load the file from a raw resource if (resID != 0) { mp = MediaPlayer.create(context, resID); } else { // try to load the file from assets AssetFileDescriptor afd = null; try { String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, fileName); afd = getAssets().openFd(assetPath); mp = new MediaPlayer(); mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mp.prepare(); } catch (IOException e) { errMsg = e.getMessage(); if (mp != null) { mp.release(); mp = null; } } finally { if (afd != null) { try { afd.close(); } catch (IOException e) { } } } } } else { errMsg = "Audio file not found"; } } if (mp == null) { writeErrorMsg((errMsg != null) ? errMsg : "Unable to load audio file"); } return mp; } private boolean execute_audio_load() { /* If there is already an audio running, * then stop it and * release its resources. */ /* if (theMP != null) { try {theMP.stop();} catch (IllegalStateException e) {} theMP.release(); theMP = null; } */ setVolumeControlStream(AudioManager.STREAM_MUSIC); // AudioManager audioSM = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); clearErrorMsg(); if (!getNVar()) return false; // get the Player Number variable Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file path if (!checkEOL()) return false; String fileName = StringConstant; // the filename as given by the user MediaPlayer aMP = getMP(fileName); if (aMP == null) { val.val(0); // indicate error with 0 in Player Number var } else { aMP.setAudioStreamType(AudioManager.STREAM_MUSIC); setVolumeControlStream(AudioManager.STREAM_MUSIC); val.val(theMPList.size()); // indicate success with list index in Player Number var theMPList.add(aMP); theMPNameList.add(fileName); } return true; } private boolean checkAudioFileTableIndex(int index) { if ((index <= 0) || (index >= theMPList.size())) { return RunTimeError("Invalid Audio File Table index"); } return true; } private boolean execute_audio_release() { if (!evalNumericExpression()) return false; int index = EvalNumericExpressionValue.intValue(); if (!checkAudioFileTableIndex(index)) return false; if (!checkEOL()) return false; MediaPlayer aMP = theMPList.get(index); if (aMP == null) return true; if (theMP == aMP) { return RunTimeError("Must stop player before releasing"); } aMP.release(); theMPList.set(index, null); return true; } private boolean execute_audio_play() { clearErrorMsg(); if (!evalNumericExpression()) return false; int index = EvalNumericExpressionValue.intValue(); if (!checkAudioFileTableIndex(index)) return false; if (!checkEOL()) return false; MediaPlayer aMP = theMPList.get(index); if (aMP == null) { return RunTimeError("Audio not loaded at:"); } if (theMP != null) { return RunTimeError("Stop Current Audio Before Starting New Audio"); } setVolumeControlStream(AudioManager.STREAM_MUSIC); // Log.v(LOGTAG, "play " + aMP); try { aMP.prepare(); } catch (Exception e) { } aMP.start(); if (!aMP.isPlaying()) { // Somehow lost the player. Make a new one. aMP.release(); aMP = getMP(theMPNameList.get(index)); if (aMP == null) { RunTimeError("Media player synchronous problem."); return true; // TODO: Is this correct? } theMPList.set(index, aMP); aMP.start(); } aMP.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { PlayIsDone = true; } }); // Log.v(LOGTAG, "is playing " + theMP.isPlaying()); PlayIsDone = false; theMP = aMP; return true; } private boolean execute_audio_isdone() { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; if (theMP == null) { PlayIsDone = true; } val.val((PlayIsDone) ? 1 : 0); return true; } private boolean execute_audio_loop() { if (theMP == null) { return RunTimeError("Audio not playing at:"); } if (!checkEOL()) return false; theMP.setLooping(true); return true; } private boolean execute_audio_stop() { if (!checkEOL()) return false; if (theMP == null) return true; // if theMP is null, Media player has stopped // MediaPlayer.setOnSeekCompleteListener(mSeekListener); // try {Thread.sleep(1000);}catch(InterruptedException e) {} try { theMP.stop(); } catch (Exception e) { return RunTimeError(e); } finally { theMP = null; } // Signal MP stopped return true; } private boolean execute_audio_pause() { if (!checkEOL()) return false; if (theMP == null) return true; // if theMP is null, Media player has stopped try { theMP.pause(); } catch (Exception e) { return RunTimeError(e); } finally { theMP = null; } // Signal MP stopped return true; } private boolean execute_audio_volume() { if (theMP == null) { return RunTimeError("Audio not playing at:"); } if (!evalNumericExpression()) return false; float left = EvalNumericExpressionValue.floatValue(); if (!isNext(',')) return false; if (!evalNumericExpression()) return false; float right = EvalNumericExpressionValue.floatValue(); if (!checkEOL()) return false; setVolumeControlStream(AudioManager.STREAM_MUSIC); theMP.setVolume(left, right); return true; } private boolean execute_audio_pcurrent() { if (theMP == null) { return RunTimeError("Audio not playing at:"); } if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; val.val(theMP.getCurrentPosition()); return true; } private boolean execute_audio_pseek() { if (theMP == null) { return RunTimeError("Audio not playing at:"); } if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int pos = EvalNumericExpressionValue.intValue(); theMP.seekTo(pos); return true; } private boolean execute_audio_length() { if (!getNVar()) return false; // Get the Player Number Var Var.Val val = mVal; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; int index = EvalNumericExpressionValue.intValue(); if (!checkAudioFileTableIndex(index)) return false; if (!checkEOL()) return false; MediaPlayer aMP = theMPList.get(index); if (aMP == null) { return RunTimeError("Audio not loaded at:"); } val.val(aMP.getDuration()); return true; } private boolean execute_audio_record_start() { if (!getStringArg()) return false; String recordFileName = Basic.getDataPath(StringConstant); int source = 0; // Get optional source if (isNext(',')) { if (!evalNumericExpression()) return false; source = EvalNumericExpressionValue.intValue(); } if (!checkEOL()) return false; try { mRecorder = new MediaRecorder(); switch (source) { case 0: mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); break; case 1: mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); break; case 2: mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); break; case 3: mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_DOWNLINK); break; case 4: mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_UPLINK); break; } mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setOutputFile(recordFileName); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mRecorder.prepare(); mRecorder.start(); } catch (Exception e) { return RunTimeError("Audio record error: " + e); } return true; } private boolean execute_audio_record_stop() { return checkEOL() && audioRecordStop(); } private boolean audioRecordStop() { if (mRecorder == null) return true; try { mRecorder.stop(); mRecorder.release(); } catch (Exception e) { } mRecorder = null; return true; } // ************************************* Sensors Package ************************************** private boolean executeSENSORS() { // Get Sensor command keyword if it is there return executeSubcommand(sensors_cmd, "Sensors"); // and execute the command } private boolean execute_sensors_list() { Var var = getArrayVarForWrite(TYPE_STRING); // get the result array variable if (var == null) return false; // must name a string array variable if (!checkEOL()) return false; // line must end with ']' if (theSensors == null) { theSensors = new SensorActivity(Run.this); } ArrayList<String> census = theSensors.takeCensus(); int nSensors = census.size(); if (nSensors == 0) { return RunTimeError("This device reports no Sensors"); } /* Puts the list of sensors into a new array */ return ListToBasicStringArray(var, census, nSensors); } private boolean execute_sensors_open() { if (theSensors == null) { theSensors = new SensorActivity(Run.this); } if (isEOL()) return false; boolean toOpen = false; // anything to open? do { // get the user's list of sensors to open if (!evalNumericExpression()) return false; // get the sensor number int s = EvalNumericExpressionValue.intValue(); int d = SensorManager.SENSOR_DELAY_NORMAL; if (isNext(':')) { if (!evalNumericExpression()) return false; // get the sensor delay value d = EvalNumericExpressionValue.intValue(); } toOpen |= theSensors.markForOpen(s, d); // record selection } while (isNext(',')); if (!checkEOL()) return false; if (toOpen) { // if any valid selections theSensors.doOpen(); // open selected sensors } return true; } private boolean execute_sensors_read() { if (theSensors == null) { return RunTimeError("Sensors not opened at:"); } if (!evalNumericExpression()) return false; // Get Sensor Type int type = EvalNumericExpressionValue.intValue(); if (type < 0 || type > SensorActivity.MaxSensors) { return RunTimeError("Sensor type not 0 to " + SensorActivity.MaxSensors); } Var.Val[] val = new Var.Val[4]; // 4 because that's what SensorActivity returns for (int i = 1; i < 4; ++i) { if (!isNext(',')) return false; if (!getNVar()) return false; // Sensor Variable val[i] = mVal; } if (!checkEOL()) return false; double[] SensorValues = theSensors.getValues(type); for (int i = 1; i < 4; ++i) { val[i].val(SensorValues[i]); } return true; } private boolean execute_sensors_rotate() { /* This is a test. * It has failed so far * This command has not been exposed to the users. * Someday....? */ float mOrientation[] = new float[3]; double SensorValues[]; SensorValues = theSensors.getValues(1); float g[] = new float[3]; g[0] = (float) SensorValues[1]; g[1] = (float) SensorValues[2]; g[2] = (float) SensorValues[3]; SensorValues = theSensors.getValues(1); float m[] = new float[3]; m[0] = (float) SensorValues[1]; m[1] = (float) SensorValues[2]; m[2] = (float) SensorValues[3]; float r[] = new float[16]; float R[] = new float[16]; float i[] = new float[16]; SensorManager.getRotationMatrix(r, i, g, m); /* SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X, SensorManager.AXIS_Z, r);*/ SensorManager.getOrientation(r, mOrientation); final float rad2deg = (float) (180.0f / Math.PI); if (!getNVar()) return false; mVal.val(mOrientation[0]); if (!isNext(',')) return false; if (!getNVar()) return false; mVal.val(mOrientation[1]); if (!isNext(',')) return false; if (!getNVar()) return false; mVal.val(mOrientation[2]); if (!checkEOL()) return false; return true; } private boolean execute_sensors_close() { if (!checkEOL()) return false; if (theSensors != null) { theSensors.stop(); theSensors = null; // Tell everyone that Sensors are closed } return true; } // *************************************** GPS Package **************************************** private boolean executeGPS() { Command c = findSubcommand(GPS_cmd, "GPS"); if (c == null) return false; if ((theGPS == null) && (c.id != CID_OPEN)) { return RunTimeError("GPS not opened at:"); } return c.run(); } public boolean execute_gps_open() { Var.Val statusVar = null; double errorCode = 1.0; long minTime = 0l; float minDistance = 0.0f; clearErrorMsg(); boolean isComma = isNext(','); if (!isComma && !isEOL()) { // there is a status var if (!getNVar()) return false; statusVar = mVal; isComma = isNext(','); } if (isComma) { isComma = isNext(','); if (!isComma) { if (!evalNumericExpression()) return false; // get the minTime arg minTime = EvalNumericExpressionValue.longValue(); if (minTime < 0) { return RunTimeError("Time less than zero"); } isComma = isNext(','); } } if (isComma) { if (!evalNumericExpression()) return false; // get the minDistance arg minDistance = EvalNumericExpressionValue.longValue(); if (minDistance < 0) { return RunTimeError("Distance less than zero"); } } if (!checkEOL()) return false; if (theGPS != null) return true; // already opened try { theGPS = new GPS(Run.this, minTime, minDistance); // start GPS } catch (Exception e) { writeErrorMsg(e); errorCode = 0.0; } if (statusVar != null) { statusVar.val(errorCode); } return true; } // execute_gps_open public boolean execute_gps_close() { if (!checkEOL()) return false; if (theGPS != null) { Log.d(LOGTAG, "Stopping GPS on command"); theGPS.stop(); // close GPS theGPS = null; } return true; } private boolean execute_gps_num(GPS.GpsData type) { if (!getNVar()) return false; // Variable for returned value if (!checkEOL()) return false; double value = theGPS.getNumericValue(type); mVal.val(value); // Set value into variable return true; } private boolean execute_gps_string(GPS.GpsData type) { if (!getSVar()) return false; if (!checkEOL()) return false; String value = theGPS.getStringValue(type); mVal.val((value == null) ? "" : value); return true; } private boolean execute_gps_satellites() { if (isEOL()) return true; // user asked for no data Var.Val satCountVar = null; ArrayList<Object> sats = null; // list of satellite bundles boolean isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // variable for returned satellite count value satCountVar = mVal; isComma = isNext(','); } if (isComma) { int listIndex = getListArg(Var.Type.NUM); // reuse old list or create new one if (listIndex < 0) return false; sats = theLists.get(listIndex); } if (!checkEOL()) return false; if (satCountVar != null) { double count = theGPS.getNumericValue(GpsData.SATELLITES); satCountVar.val(count); // set satellite count into variable } return updateGPSSatelliteList(sats); // update list if user requested it } // execute_gps_satellites private boolean execute_gps_location() { if (isEOL()) return true; // user asked for no data // returns time, provider, satellites, accuracy, latitude, longitude, altitude, bearing, and speed GpsData[] data = { GpsData.TIME, GpsData.PROVIDER, GpsData.SATELLITES, GpsData.ACCURACY, GpsData.LATITUDE, GpsData.LONGITUDE, GpsData.ALTITUDE, GpsData.BEARING, GpsData.SPEED }; int nArgs = data.length; // PROVIDER is string, the rest are numeric byte[] types = { 1, 2, 1, 1, 1, 1, 1, 1, 1 }; // type of each variable Var.Val[] args = new Var.Val[nArgs]; // Val object for each variable if (!getOptVars(types, args)) return false; for (int arg = 0; arg < nArgs; ++arg) { Var.Val val = args[arg]; if (val != null) { if (types[arg] == 1) { // if numeric type val.val(theGPS.getNumericValue(data[arg])); } else { // else string type val.val(theGPS.getStringValue(data[arg])); } } } return true; } // execute_gps_location private boolean execute_gps_status() { if (isEOL()) return true; // user asked for no data // returns status, count of satellites in fix, count of satellites in view, // and list of satellite bundles Var.Val statusVar = null; boolean statusIsNumeric = true; Var.Val inFixVar = null; Var.Val inViewVar = null; ArrayList<Object> sats = null; // list of satellite bundles boolean isComma = isNext(','); if (!isComma) { if (!getVar()) return false; // status variable statusVar = mVal; statusIsNumeric = statusVar.isNumeric(); // may be either numeric or string isComma = isNext(','); } if (isComma) { isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // inFix variable, numeric inFixVar = mVal; isComma = isNext(','); } } if (isComma) { isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // inView variable, numeric inViewVar = mVal; isComma = isNext(','); } } if (isComma) { int listIndex = getListArg(Var.Type.NUM); // reuse old list or create new one if (listIndex < 0) return false; sats = theLists.get(listIndex); } if (!checkEOL()) return false; if (statusVar != null) { if (statusIsNumeric) { // if type is numeric statusVar.val(theGPS.getNumericValue(GpsData.STATUS)); } else { // else type is string statusVar.val(theGPS.getStringValue(GpsData.STATUS)); } } if (inFixVar != null) { inFixVar.val(theGPS.getNumericValue(GpsData.SATS_IN_FIX)); } if (inViewVar != null) { inViewVar.val(theGPS.getNumericValue(GpsData.SATS_IN_VIEW)); } return updateGPSSatelliteList(sats); // update list if user requested it } // execute_gps_status private boolean updateGPSSatelliteList(ArrayList<Object> satList) { if (satList == null) return true; // No list, nothing to update // satList is a List of zero or more Bundle pointers (indices into theBundles). // Any Bundles present must contain satellite data. // We clear the stale data, but keep a history list of bundle pointers, // indexed by satellite PRN. When get new satellite bundles from GPS, // we match them by PRN to existing bundles, putting the new bundle in theBundles // where the old bundle was. New satellites add new bundles to theBundles. // Anything left in the history list is copied to the end of the user list. // That way we don't create a new bundle next time the same satellite reappears. // This is pretty convoluted, but it's all about re-using the satellite bundles, // or more accurately, reusing slots in theBundles list. // We use the user's list to keep pointers to every satellite ever seen. HashMap<Double, Double> oldList = validateSatelliteList(satList); if (oldList == null) return false; // list not valid satList.clear(); // erase the user's list getSatelliteBundles(oldList, satList); // get latest satellite data from GPS // builds satList, removes items from oldList for (Double prn : oldList.keySet()) { // if any satellite remain in the history list satList.add(oldList.get(prn)); // copy each index to the end of the user's list } return true; } // updateGPSSatelliteList // Validate user's satellite list: it must contain valid satellite bundles. // Build a history list that maps each satellite PRN to a bundle pointer (theBundles index). // Clear the user's list and return the history list. If error, return null. private HashMap<Double, Double> validateSatelliteList(ArrayList<Object> satList) { if (satList == null) return null; if (!satList.isEmpty() && !(satList.get(0) instanceof Double)) { RunTimeError("Invalid Satellite List"); return null; } HashMap<Double, Double> history = new HashMap<Double, Double>(); for (Object o : satList) { // for each satellite in the user's list Double sat = (Double) o; // index of BASIC! bundle for one satellite int bSatIdx = sat.intValue(); // same thing as int if ((bSatIdx > 0) && (bSatIdx < theBundles.size())) { Bundle bSat = theBundles.get(bSatIdx); // get the satellite bundle if (bSat.containsKey(GPS.KEY_PRN)) { Double prn = (Double) bSat.get(GPS.KEY_PRN); // get the satellite's PRN history.put(prn, sat); // map PRN to bundle index bSat.clear(); // clear stale data bSat.putDouble(GPS.KEY_PRN, prn); // put the PRN back in continue; // all conditions met; next satellite } // else no PRN } // else bad bundle index RunTimeError("Invalid Satellite Bundle"); // one of the conditions was not met history = null; } return history; } // validateSatelliteList // Get the GPS satellite data bundles. // Try to match them up by PRN to the pool of existing satellite bundles. // If match found, put the new bundle in theBundles at the old index and // remove the history item. If no bundle exists for a PRN, create a new one. // This is intended to allow a user to poll GPS without proliferating bundles! private void getSatelliteBundles(HashMap<Double, Double> pool, List<Object> list) { HashMap<Double, Bundle> satMap = theGPS.getSatellites();// map of new bundles of satellite data in Java format for (Double prn : satMap.keySet()) { Bundle jSat = satMap.get(prn); // get each satellite's bundle Double sat = pool.get(prn); // look for a corresponding bundle index if (sat != null) { // existing satellite bundle int bSatIdx = sat.intValue(); theBundles.set(bSatIdx, jSat); // put the new bundle at the known index } else { // no BASIC! bundle, create a new one int bSatIdx = theBundles.size(); theBundles.add(jSat); sat = (double) bSatIdx; } list.add(sat); // put the bundle index into the list pool.remove(prn); // remove it from the history list } } // getSatelliteBundles // ************************************* Array Package **************************************** private boolean executeARRAY() { // Get array command keyword if it is there return executeSubcommand(array_cmd, "Array"); // and execute the command } private boolean execute_array_dims() { // get the dimensions of an array and put them in another array Var srcvar = getExistingArrayVar(); // get the source array variable (required) if (srcvar == null) return false; if (!isNext(']')) { return RunTimeError(EXPECT_ARRAY_NO_INDEX); } if (isEOL()) return true; // user supplied no desination variable(s) Var dimsVar = null; // variable for array of dimensions Var.Val lenVal = null; // variable for number of dimensions boolean isComma = isNext(','); if (isComma) { isComma = isNext(','); if (!isComma) { dimsVar = getArrayVarForWrite(TYPE_NUMERIC); // get the array variable for dimensions isComma = isNext(','); } } if (isComma) { if (!getNVar()) return false; // get the variable for number of dimensions lenVal = mVal; } if (!checkEOL()) return false; int[] dimList = ((Var.ArrayVar) srcvar).arrayDef().dimList(); int size = dimList.length; if (dimsVar != null) { ArrayList<Double> dims = new ArrayList<Double>(size); for (int i = 0; i < size; ++i) { dims.add(Double.valueOf(dimList[i])); } if (!ListToBasicNumericArray(dimsVar, dims, size)) return false; // copy the list to a BASIC! array } if (lenVal != null) { lenVal.val(size); } return true; } private boolean execute_array_length() { if (!getNVar()) return false; // Length Variable Var.Val val = mVal; if (!isNext(',')) return false; Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // Get values inside [], if any if (!checkEOL()) return false; // line must end with ']' if (!getArraySegment(var.arrayDef(), p)) return false; // get segment base and length int length = p[1].intValue(); val.val(length); // set the length into the var value return true; } private boolean execute_array_load() { Var var = getArrayVarForWrite(); // get the result array variable if (var == null) return false; // must name a new array variable if (!isNext(',')) return false; // Comma before the list if (var.isNumeric()) { // If the array is numeric ArrayList<Double> Values = new ArrayList<Double>(); // Create a list for the numeric values if (!LoadNumericList(Values)) return false; // load numeric list if (!checkEOL()) return false; return ListToBasicNumericArray(var, Values, Values.size()); // Copy the list to a BASIC! array } else { ArrayList<String> Values = new ArrayList<String>(); // Create a list for the numeric values if (!LoadStringList(Values)) return false; // load string list if (!checkEOL()) return false; return ListToBasicStringArray(var, Values, Values.size()); // Copy the list to a BASIC! array } } private boolean LoadNumericList(ArrayList<Double> Values) { while (true) { // loop through the list if (!evalNumericExpression()) return false; // values may be expressions Values.add(EvalNumericExpressionValue); String text = ExecutingLineBuffer.text(); if (LineIndex >= text.length()) return false; char c = text.charAt(LineIndex); // get the next character if (c == ',') { // if it is a comma ++LineIndex; // skip it and continue looping } else break; // else no more values in the list } return true; } private boolean LoadStringList(ArrayList<String> Values) { while (true) { // loop through the list if (!getStringArg()) return false; // values may be expressions Values.add(StringConstant); String text = ExecutingLineBuffer.text(); if (LineIndex >= text.length()) return false; char c = text.charAt(LineIndex); // get the next character if (c == ',') { // if it is a comma ++LineIndex; // skip it and continue looping } else break; // else no more values in the list } return true; } private boolean execute_array_collection(ArrayOrderOps op) { // This method implements several array commands Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // Get values inside [], if any if (!checkEOL()) return false; // line must end with ']' Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); if (var.isNumeric()) { // Numeric Array double[] array = arrayDef.getNumArray(); // raw array access ArrayList<Double> Values = new ArrayList<Double>(); // Create a list to copy array values into for (int i = 0; i < length; ++i) { // Copy the array values into that list Values.add(array[base + i]); } switch (op) { // Execute the command specific procedure case DoReverse: Collections.reverse(Values); break; case DoSort: Collections.sort(Values); break; case DoShuffle: Collections.shuffle(Values); break; } for (int i = 0; i < length; ++i) { // Copy the results back to the array array[base + i] = Values.get(i); } } else { // Do the same stuff for a string array String[] array = arrayDef.getStrArray(); // raw array access ArrayList<String> Values = new ArrayList<String>(); for (int i = 0; i < length; ++i) { Values.add(array[base + i]); } switch (op) { // Execute the command specific procedure case DoReverse: Collections.reverse(Values); break; case DoSort: Collections.sort(Values); break; case DoShuffle: Collections.shuffle(Values); break; } for (int i = 0; i < length; ++i) { array[base + i] = Values.get(i); } } return true; } private boolean execute_array_sum(ArrayMathOps op) { if (!getNVar()) return false; // get the value return variable Var.Val val = mVal; if (!isNext(',')) return false; Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; if (!var.isNumeric()) { return RunTimeError(EXPECT_NUM_ARRAY); } Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any if (!checkEOL()) return false; // line must end with ']' Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); double[] array = arrayDef.getNumArray(); // raw array access double Sum = array[base]; double Min = Sum; double Max = Min; for (int i = 1; i < length; ++i) { // loop through the array values double d = array[base + i]; // pick up the element's value Sum += d; // build the Sum if (d < Min) { Min = d; } // find the minimum value if (d > Max) { Max = d; } // and the maxium value } double Average = Sum / length; // calculate the average switch (op) { // set the return value according to the command case DoAverage: val.val(Average); break; case DoSum: val.val(Sum); break; case DoMax: val.val(Max); break; case DoMin: val.val(Min); break; case DoVariance: case DoStdDev: double T = 0; double W = 0; for (int i = 0; i < length; ++i) { double d = array[base + i]; // pick up the element's value W = d - Average; T = T + W * W; } double variance = T / (length - 1); if (op == ArrayMathOps.DoVariance) { val.val(variance); } else { // DoStdDev val.val(Math.sqrt(variance)); } break; } return true; } private boolean execute_array_fill() { Var dstVar = getExistingArrayVar(); // get the array variable if (dstVar == null) return false; boolean isNumeric = dstVar.isNumeric(); Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any Var.ArrayDef arrayDef = dstVar.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get array segment base and length int start = p[0].intValue(); int length = p[1].intValue(); if (!isNext(',')) return false; // move to the value if (!isNumeric) { // string type array if (!getStringArg()) return RunTimeError("Array and fill must be same type"); String fill = StringConstant; // get the string to put in array if (!checkEOL()) return false; String[] array = dstVar.arrayDef().getStrArray(); // Caution! Raw array access! Arrays.fill(array, start, start + length, fill); // fill the array } else { // numeric type array if (!evalNumericExpression()) return RunTimeError("Array and fill must be same type"); double fill = EvalNumericExpressionValue; // get the value to put in array if (!checkEOL()) return false; double[] array = dstVar.arrayDef().getNumArray(); // Caution! Raw array access! Arrays.fill(array, start, start + length, fill); // fill the array } return true; } private boolean execute_array_copy() { // **** Source Array **** Var var = getVarAndType(); if (var == null) return false; // get the array variable if (!var.isArray()) return RunTimeError("Source not array"); if (var.isNew()) return RunTimeError("Source array does not exist"); boolean srcNumeric = var.isNumeric(); Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any Var.ArrayDef srcArray = var.arrayDef(); if (!getArraySegment(srcArray, p)) return false; // get source segment base and length int srcBase = p[0].intValue(); int srcLength = p[1].intValue(); if (!isNext(',')) return false; // *** Destination Array *** var = getVarAndType(); // get the source array variable if (var == null) return false; if (!var.isArray()) return RunTimeError("Destination not array"); if (srcNumeric != var.isNumeric()) return RunTimeError("Arrays not of same type"); boolean dstIsNew = var.isNew(); Var dstVar = var; Var.ArrayDef dstArray; int extras = 0; // get the extras parameter if (!isNext(']')) { if (!evalNumericExpression()) return false; if (!isNext(']')) return false; extras = EvalNumericExpressionValue.intValue(); } if (!checkEOL()) return false; int dstStart = 0; int totalLength = 0; if (dstIsNew) { // copy to new array, optional extras arg adds element(s) if (extras == 0) { // check for cases that would create empty array if (srcLength < 1) return RunTimeError("Source array [Start,Length] must specify at least one element"); } totalLength = srcLength + Math.abs(extras); // go build a new array of the proper size and type if (!BuildBasicArray(dstVar, totalLength)) return false; dstArray = dstVar.arrayDef(); // get the destination array if (extras < 0) { dstStart -= extras; } // if negative extras, add absolute value to start index } else { // copy over old array, optional extras arg is start index if (srcLength < 1) return true; // nothing to do dstArray = dstVar.arrayDef(); // get the destination array int dstLength = dstArray.length(); // get the destination array length if (extras > dstLength) return true; // dest start is past end of array, nothing to do if (--extras < 0) { extras = 0; } // convert to 0-based index, ignore if less than 1 if (extras + srcLength > dstLength) { // start index + length to copy > space available srcLength = dstLength - extras; // limit copy } else { dstLength = extras + srcLength; } dstStart = extras; totalLength = dstLength - extras; } if (srcNumeric) { // do numeric array double[] sarray = srcArray.getNumArray(); // raw array access double[] darray = dstArray.getNumArray(); // raw array access for (int i = 0; i < srcLength; ++i) { // copy the source array values darray[dstStart++] = sarray[srcBase++]; } } else { // do String array String[] sarray = srcArray.getStrArray(); // raw array access String[] darray = dstArray.getStrArray(); // raw array access for (int i = 0; i < srcLength; ++i) { // copy the source array values darray[dstStart++] = sarray[srcBase++]; } } return true; } private boolean execute_array_search() { Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); if (!isNext(',')) return false; // move to the value Var.Val val = null; int start = 0; int found = -1; if (!var.isNumeric()) { // String type array if (!getStringArg()) return false; // get the string to search for String sfind = StringConstant; if (!isNext(',')) return false; // move to the result var if (!getNVar()) return false; val = mVal; if (isNext(',')) { // move to the start index if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); if (--start < 0) { start = 0; } // convert to zero-based index } if (!checkEOL()) return false; String[] array = arrayDef.getStrArray(); // raw array access for (int i = start; i < length; ++i) { // Search the list for a match if (sfind.equals(array[base + i])) { found = i; break; } } } else { // Numeric type array if (!evalNumericExpression()) return false; // Get the value to search for double nfind = EvalNumericExpressionValue; if (!isNext(',')) return false; // move to the result var if (!getNVar()) return false; val = mVal; if (isNext(',')) { // move to the start index if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); if (--start < 0) { start = 0; } // convert to zero-based index } if (!checkEOL()) return false; double[] array = arrayDef.getNumArray(); // raw array access for (int i = start; i < length; ++i) { // Search the list for a match if (nfind == array[base + i]) { found = i; break; } } } val.val(++found); // return found as 1-based index return true; } // *************************************** List Package *************************************** private boolean executeLIST() { // Get list command keyword if it is there return executeSubcommand(list_cmd, "List"); // and execute the command } private boolean execute_LIST_NEW() { char c = ExecutingLineBuffer.text().charAt(LineIndex); // Get the type, s or n ++LineIndex; Var.Type type = Var.Type.typeOf(c); if (type == Var.Type.NOVAR) return false; // Unknown type, don't create anything if (!isNext(',')) return false; if (!getNVar()) return false; // List pointer variable Var.Val val = mVal; if (!checkEOL()) return false; int theIndex = createNewList(type, val); // Try to create list, -1 if fail return (theIndex >= 0); // true if create succeeded } private int createNewList(Var.Type type, Var.Val val) { // Put a new ArrayList in global theLists // Put its type in global theListsType // Write its index to user variable var int listIndex = theLists.size(); switch (type) { case NUM: theLists.add(new ArrayList<Double>()); break; // Create a numeric list case STR: theLists.add(new ArrayList<String>()); break; // Create a string list default: return -1; // Unknown type, don't create anything } theListsType.add(type); // Add the type val.val(listIndex); // tell the user where it is return listIndex; } private int getListArg() { // Get the List pointer return getListArg("Invalid List Pointer"); } private int getListArg(String errorMsg) { // Get the List pointer if (evalNumericExpression()) { int listIndex = EvalNumericExpressionValue.intValue(); if ((listIndex > 0) && (listIndex < theLists.size())) { return listIndex; } RunTimeError(errorMsg); } return -1; } private int getListArg(Var.Type type) { // Type-restricted auto-create: if the command argument is a valid list pointer (index) // of the correct type, return the list pointer for re-use. If it is not (i.e., it is // out of range or the wrong type), and the command line argument is a simple numeric // variable, create a new list and return its index. // Otherwise post a RunTimeError and return -1. // NOTE: if this method auto-creates a list, it writes the index to the user's variable. int startLI = LineIndex; if (evalNumericExpression()) { int listIndex = EvalNumericExpressionValue.intValue(); if ((listIndex > 0) && (listIndex < theLists.size()) && // in range (theListsType.get(listIndex) == type)) { // type ok return listIndex; // expression is valid string List pointer } int endLI = LineIndex; LineIndex = startLI; if (getNVar() && (LineIndex == endLI)) { // if NVar is entire expression Var.Val val = mVal; return createNewList(type, val); // try to create list, -1 if fail } // create writes index to user variable LineIndex = endLI; RunTimeError("Invalid " + type.toString() + " List Pointer"); } return -1; } private boolean execute_LIST_ADDARRAY() { int listIndex = getListArg(); // get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; Var var = getExistingArrayVar(); // get the array variable if (var == null) return false; Integer[] p = new Integer[2]; if (!getIndexPair(p)) return false; // get values inside [], if any if (!checkEOL()) return false; boolean isListNumeric = (theListsType.get(listIndex) == Var.Type.NUM); if (isListNumeric != var.isNumeric()) { return RunTimeError("Type mismatch"); } Var.ArrayDef arrayDef = var.arrayDef(); if (!getArraySegment(arrayDef, p)) return false; // get segment base and length int base = p[0].intValue(); int length = p[1].intValue(); if (isListNumeric) { ArrayList<Double> destList = theLists.get(listIndex); // copy array to list double[] array = arrayDef.getNumArray(); // raw array access for (int i = 0; i < length; ++i) { destList.add(array[base + i]); // Caution! No range checking! } } else { ArrayList<String> destList = theLists.get(listIndex); // copy array to list String[] array = arrayDef.getStrArray(); // raw array access for (int i = 0; i < length; ++i) { destList.add(array[base + i]); // Caution! No range checking! } } return true; } private boolean execute_LIST_ADDLIST() { int destListIndex = getListArg("Invalid Destination List Pointer"); // Get the destination list pointer if (destListIndex < 0) return false; if (!isNext(',')) return false; int sourceListIndex = getListArg("Invalid Source List Pointer"); // Get the source list pointer if (sourceListIndex < 0) return false; if (!checkEOL()) return false; Var.Type destType = theListsType.get(destListIndex); Var.Type sourceType = theListsType.get(sourceListIndex); if (destType != sourceType) { return RunTimeError("Type mismatch"); } theLists.get(destListIndex).addAll(theLists.get(sourceListIndex)); return true; } private boolean execute_LIST_SEARCH() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; // move to the value Var.Val val = null; int start = 0; int found = -1; Var.Type type; type = theListsType.get(listIndex).isNS(); // ensure either numeric or sring if (type == Var.Type.STR) { // String type list ArrayList<String> SValues = theLists.get(listIndex); // Get the string list if (!getStringArg()) return false; // values may be expressions String sfind = StringConstant; if (!isNext(',')) return false; // move to the result var if (!getNVar()) return false; val = mVal; if (isNext(',')) { // move to the start index if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); if (--start < 0) { start = 0; } // convert to zero-based index } if (!checkEOL()) return false; for (int i = start; i < SValues.size(); ++i) { // search the list for a match if (sfind.equals(SValues.get(i))) { found = i; break; } } } else { // Numeric type list ArrayList<Double> NValues = theLists.get(listIndex); // Get the numeric list if (!evalNumericExpression()) return false; // values may be expressions double nfind = EvalNumericExpressionValue; if (!isNext(',')) return false; // move to the result var if (!getNVar()) return false; val = mVal; if (isNext(',')) { // move to the start index if (!evalNumericExpression()) return false; start = EvalNumericExpressionValue.intValue(); if (--start < 0) { start = 0; } // convert to zero-based index } if (!checkEOL()) return false; for (int i = start; i < NValues.size(); ++i) { // search the list for a match if (nfind == (NValues.get(i))) { found = i; break; } } } val.val(++found); // return found as 1-based index return true; } private boolean execute_LIST_ADD() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; // move to the result value Var.Type type; type = theListsType.get(listIndex).isNS(); // ensure either numeric or sring if (type == Var.Type.NUM) { ArrayList<Double> Values = theLists.get(listIndex); // Get the numeric list if (!LoadNumericList(Values)) return false; // load numeric list } else { ArrayList<String> Values = theLists.get(listIndex); // Get the string list if (!LoadStringList(Values)) return false; // load string list } return checkEOL(); } private boolean execute_LIST_SET() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the index to get int getIndex = EvalNumericExpressionValue.intValue(); --getIndex; // Ones based for Basic user if (!isNext(',')) return false; Var.Type type; type = theListsType.get(listIndex).isNS(); // ensure either numeric or sring if (type == Var.Type.STR) { // String type list if (!evalStringExpression()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; ArrayList<String> thisList = theLists.get(listIndex); // Get the string list if (getIndex < 0 || getIndex >= thisList.size()) { return RunTimeError("Index out of bounds"); } thisList.set(getIndex, StringConstant); } else { // Numeric type list if (!evalNumericExpression()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; ArrayList<Double> thisList = theLists.get(listIndex);// Get the numeric list if (getIndex < 0 || getIndex >= thisList.size()) { return RunTimeError("Index out of bounds"); } thisList.set(getIndex, EvalNumericExpressionValue); } return true; } private boolean execute_LIST_GET() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the index to get int getIndex = EvalNumericExpressionValue.intValue(); --getIndex; // Ones based for Basic user if (!isNext(',')) return false; if (!getVar()) return false; // Get the return value variable Var.Val val = mVal; if (!checkEOL()) return false; Var.Type listType; // Get this list's type listType = theListsType.get(listIndex).isNS(); // ensure either numeric or sring boolean isListNumeric = listType.isNumeric(); if (isListNumeric != val.isNumeric()) { return RunTimeError("Type mismatch"); } if (!isListNumeric) { // String type list ArrayList<String> thisStringList = theLists.get(listIndex); // Get the string list if (getIndex < 0 || getIndex >= thisStringList.size()) { return RunTimeError("Index out of bounds"); } String thisString = thisStringList.get(getIndex); // Get the requested string val.val(thisString); } else { // Numeric type list ArrayList<Double> thisNumericList = theLists.get(listIndex);// Get the numeric list if (getIndex < 0 || getIndex >= thisNumericList.size()) { return RunTimeError("Index out of bounds"); } Double thisNumber = thisNumericList.get(getIndex); // Get the requested number val.val(thisNumber); } return true; } private boolean execute_LIST_GETTYPE() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; val.val(theListsType.get(listIndex).typeNS()); return true; } private boolean execute_LIST_CLEAR() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!checkEOL()) return false; ArrayList<?> list = theLists.get(listIndex); list.clear(); list.trimToSize(); return true; } private boolean execute_LIST_REMOVE() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the index to remove if (!checkEOL()) return false; int getIndex = EvalNumericExpressionValue.intValue(); --getIndex; // Ones based for Basic user ArrayList theList = theLists.get(listIndex); // Get the list if (getIndex < 0 || getIndex >= theList.size()) { return RunTimeError("Index out of bounds"); } theList.remove(getIndex); return true; } private boolean execute_LIST_INSERT() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Get the index insert at int getIndex = EvalNumericExpressionValue.intValue(); --getIndex; // Ones based for Basic user if (!isNext(',')) return false; Var.Type listType; // Get this list's type listType = theListsType.get(listIndex).isNS(); // ensure either numeric or sring if (listType == Var.Type.STR) { // String type list if (!getStringArg()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; ArrayList<String> thisStringList = theLists.get(listIndex); // Get the string list if (getIndex < 0 || getIndex > thisStringList.size()) { // if index == size element goes at end of list return RunTimeError("Index out of bounds"); } thisStringList.add(getIndex, StringConstant); } else { // Numeric type list if (!evalNumericExpression()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; ArrayList<Double> thisNumericList = theLists.get(listIndex);// Get the numeric list if (getIndex < 0 || getIndex > thisNumericList.size()) { // if index == size element goes at end of list return RunTimeError("Index out of bounds"); } thisNumericList.add(getIndex, EvalNumericExpressionValue); } return true; } private boolean execute_LIST_SIZE() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; // move to the return var if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; int size = theLists.get(listIndex).size(); val.val(size); return true; } private boolean execute_LIST_TOARRAY() { int listIndex = getListArg(); // Get the list pointer if (listIndex < 0) return false; if (!isNext(',')) return false; // move to the array var Var var = getArrayVarForWrite(); // get the result array variable if (var == null) return false; // must name a new array variable if (!checkEOL()) return false; // line must end with ']' Var.Type listType; // Get this list's type listType = theListsType.get(listIndex).isNS(); // ensure either numeric or sring boolean isListNumeric = listType.isNumeric(); if (isListNumeric != var.isNumeric()) { return RunTimeError("Type mismatch"); } if (isListNumeric) { ArrayList<Double> Values = theLists.get(listIndex); // Get the numeric list return ListToBasicNumericArray(var, Values, Values.size()); // Copy the list to a BASIC! array } else { ArrayList<String> Values = theLists.get(listIndex); // Get the string list return ListToBasicStringArray(var, Values, Values.size()); // Copy the list to a BASIC! array } } // ************************************** Bundle Package ************************************** private boolean executeBUNDLE() { // Get bundle command keyword if it is there return executeSubcommand(bundle_cmd, "Bundle"); // and execute the command } private boolean execute_BUNDLE_CREATE() { if (!getNVar() || !checkEOL()) return false; // get the Bundle pointer variable Var.Val val = mVal; createBundle(val); return true; } private int createBundle(Var.Val val) { // create a new bundle and put it on the list int bundleIndex = theBundles.size(); theBundles.add(new Bundle()); val.val(bundleIndex); return bundleIndex; } private int getBundleArg() { // Get the Bundle pointer // Auto-create: if the command argument is a valid bundle pointer (index), // return the bundle pointer for re-use. If it is not, and the command line argument // is a simple numeric variable, create a new bundle and return its index. // Otherwise post a RunTimeError and return -1. // NOTE: if this method auto-creates a bundle, it writes the index to the user's variable. int startLI = LineIndex; if (evalNumericExpression()) { int bundleIndex = EvalNumericExpressionValue.intValue(); if ((bundleIndex > 0) && (bundleIndex < theBundles.size())) { return bundleIndex; // expression is valid Bundle pointer } int endLI = LineIndex; LineIndex = startLI; if (getNVar() && (LineIndex == endLI)) { // if NVar is entire expression Var.Val val = mVal; return createBundle(val); // create a new Bundle } LineIndex = endLI; RunTimeError("Invalid Bundle Pointer"); } return -1; } private boolean execute_BUNDLE_PUT() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; // move to the tag if (!getStringArg()) return false; String tag = StringConstant; if (!isNext(',')) return false; // move to the value Bundle b = theBundles.get(bundleIndex); int LI = LineIndex; if (evalNumericExpression()) { if (!checkEOL()) return false; b.putDouble(tag, EvalNumericExpressionValue); } else { LineIndex = LI; if (!getStringArg()) return false; if (!checkEOL()) return false; b.putString(tag, StringConstant); } return true; } private boolean execute_BUNDLE_GET() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; // move to the tag if (!getStringArg()) return false; String tag = StringConstant; if (!isNext(',')) return false; // move to the value variable if (!getVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; boolean varIsNumeric = val.isNumeric(); Bundle b = theBundles.get(bundleIndex); if (!b.containsKey(tag)) { return RunTimeError(tag + " not in bundle"); } Object o = b.get(tag); if (o instanceof Double) { if (!varIsNumeric) { return RunTimeError(tag + " is not a string"); } val.val(((Double) o).doubleValue()); } else { if (varIsNumeric) { return RunTimeError(tag + " is not numeric"); } val.val((String) o); } return true; } private boolean execute_BUNDLE_NEXT() { return false; } private boolean execute_BUNDLE_TYPE() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; // move to the tag if (!getStringArg()) return false; String tag = StringConstant; if (!isNext(',')) return false; // move to the result var if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; Bundle b = theBundles.get(bundleIndex); if (!b.containsKey(tag)) { return RunTimeError(tag + " not in bundle"); } Var.Type type = (b.get(tag) instanceof Double) ? Var.Type.NUM : Var.Type.STR; val.val(type.typeNS()); return true; } private boolean execute_BUNDLE_KEYSET() { int bundleIndex = getBundleArg(); // get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; // move to the list var int listIndex = getListArg(Var.Type.STR); // get a reusable List pointer - may create new list if (listIndex < 0) return false; // failed to get or create a list if (!checkEOL()) return false; Bundle b = theBundles.get(bundleIndex); ArrayList<String> theStringList = new ArrayList<String>(b.keySet()); theLists.set(listIndex, theStringList); // put the new list on the list of lists return true; } private boolean execute_BUNDLE_COPY() { return false; } private boolean execute_BUNDLE_CLEAR() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!checkEOL()) return false; Bundle b = theBundles.get(bundleIndex); b.clear(); return true; } private boolean execute_BUNDLE_CONTAIN() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; // move to the tag if (!getStringArg()) return false; String tag = StringConstant; if (!isNext(',')) return false; // move to the result var if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; Bundle b = theBundles.get(bundleIndex); val.val((b.containsKey(tag)) ? 1.0 : 0.0); return true; } private boolean execute_BUNDLE_REMOVE() { int bundleIndex = getBundleArg(); // Get the Bundle pointer if (bundleIndex < 0) return false; if (!isNext(',')) return false; if (!getStringArg()) return false; // Get the key to remove if (!checkEOL()) return false; String key = StringConstant; Bundle theBundle = theBundles.get(bundleIndex); // Get the bundle theBundle.remove(key); // Remove the requested key return true; } // ************************************** Stack Package *************************************** private boolean executeSTACK() { // Get stack command keyword if it is there return executeSubcommand(stack_cmd, "Stack"); // and execute the command } private boolean execute_STACK_CREATE() { char c = ExecutingLineBuffer.text().charAt(LineIndex); // Get the type, s or n ++LineIndex; Var.Type type = Var.Type.typeOf(c); if (type == Var.Type.NOVAR) return false; // Unknown type, don't create anything if (!isNext(',')) return false; if (!getNVar()) return false; // stack pointer variable Var.Val val = mVal; if (!checkEOL()) return false; Stack theStack = new Stack(); int theIndex = theStacks.size(); theStacks.add(theStack); theStacksType.add(type); // add the type val.val(theIndex); // return the stack pointer return true; } private int getStackIndexArg() { // get the Stack pointer if (evalNumericExpression()) { int stackIndex = EvalNumericExpressionValue.intValue(); if ((stackIndex > 0) && (stackIndex < theStacks.size())) { return stackIndex; } RunTimeError("Invalid Stack Pointer"); } return -1; } private boolean execute_STACK_PUSH() { int stackIndex = getStackIndexArg(); // get the Stack pointer if (stackIndex < 0) return false; if (!isNext(',')) return false; // move to the value Stack thisStack = theStacks.get(stackIndex); // get the stack Var.Type type; type = theStacksType.get(stackIndex).isNS(); // ensure either numeric or sring if (type == Var.Type.STR) { // string stack if (!getStringArg()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; thisStack.push(StringConstant); // Add the string to the stack } else { // numeric stack if (!evalNumericExpression()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; thisStack.push(EvalNumericExpressionValue); // Add the value to the stack } return true; } private boolean execute_STACK_POP() { int stackIndex = getStackIndexArg(); // Get the Stack pointer if (stackIndex < 0) return false; if (!isNext(',')) return false; // move to the value Stack thisStack = theStacks.get(stackIndex); // Get the Stack if (thisStack.isEmpty()) { return RunTimeError("Stack is empty"); } Var.Type stackType; stackType = theStacksType.get(stackIndex).isNS(); // ensure either numeric or sring boolean isStackNumeric = stackType.isNumeric(); if (!getVar()) return false; Var.Val val = mVal; if (isStackNumeric != val.isNumeric()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; if (!isStackNumeric) { // string stack String thisString = (String) thisStack.pop(); val.val(thisString); } else { // numeric stack double thisNumber = ((Double) thisStack.pop()).doubleValue(); val.val(thisNumber); } return true; } private boolean execute_STACK_PEEK() { int stackIndex = getStackIndexArg(); // Get the Stack pointer if (stackIndex < 0) return false; if (!isNext(',')) return false; // move to the value Stack thisStack = theStacks.get(stackIndex); // Get the Stack if (thisStack.isEmpty()) { return RunTimeError("Stack is empty"); } Var.Type stackType; stackType = theStacksType.get(stackIndex).isNS(); // ensure either numeric or sring boolean isStackNumeric = stackType.isNumeric(); if (!getVar()) return false; Var.Val val = mVal; if (isStackNumeric != val.isNumeric()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; if (!isStackNumeric) { // string stack String thisString = (String) thisStack.peek(); val.val(thisString); } else { // numeric stack double thisNumber = ((Double) thisStack.peek()).doubleValue(); val.val(thisNumber); } return true; } private boolean execute_STACK_TYPE() { int stackIndex = getStackIndexArg(); // Get the Stack pointer if (stackIndex < 0) return false; if (!isNext(',')) return false; // move to the value if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; val.val(theStacksType.get(stackIndex).typeNS()); return true; } private boolean execute_STACK_ISEMPTY() { int stackIndex = getStackIndexArg(); // Get the Stack pointer if (stackIndex < 0) return false; if (!isNext(',')) return false; if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; Stack thisStack = theStacks.get(stackIndex); // Get the Stack val.val(thisStack.isEmpty() ? 1 : 0); return true; } private boolean execute_STACK_CLEAR() { int stackIndex = getStackIndexArg(); // Get the Stack pointer if (stackIndex < 0) return false; if (!checkEOL()) return false; Stack thisStack = theStacks.get(stackIndex); // Get the Stack while (!thisStack.isEmpty()) { thisStack.pop(); } return true; } // ************************************ Clipboard Commands ************************************ /* // This code does not work on devices with API level < 11 private boolean executeCLIPBOARD_GET() { if (!getSVar()) return false; // get the var to put the clip into ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); String data = ""; if (clipboard.hasPrimaryClip()) { // If clip board has text CharSequence data1 = clipboard.getText(); // Get the clip data = data1.toString(); if (data == null) data = ""; } else data =""; // If no clip, set data to null StringVarValues.set(theValueIndex, data); // Return the result to user if (!checkEOL()) return false; return true; } private boolean executeCLIPBOARD_PUT() { int v = Build.VERSION.SDK_INT); if (!getStringArg()) return false; // Get the string to put into the clipboard ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); CharSequence cs = StringConstant; ClipData clip = ClipData.newPlainText("simple text",cs); clipboard.setPrimaryClip(clip); if (!checkEOL()) return false; return true; } */ private boolean executeCLIPBOARD_GET() { if (!getSVar()) return false; // get the var to put the clip into Var.Val val = mVal; if (!checkEOL()) return false; String data; if (clipboard.hasText()) { // If clip board has text data = clipboard.getText().toString(); // Get the clip } else { data = ""; } // If no clip, set data to null val.val(data); // Return the result to user return true; } private boolean executeCLIPBOARD_PUT() { if (!getStringArg()) return false; // Get the string to put into the clipboard if (!checkEOL()) return false; clipboard.setText(StringConstant); // Put the user expression into the clipboard return true; } // *********************************** Encryption Commands ************************************ private boolean executeENCRYPT(boolean mode) { String pw = ""; // default password if (!isNext(',')) { if (!getStringArg()) return false; // get the (optional) password arg pw = StringConstant; if (!isNext(',')) return false; } if (!getStringArg()) return false; // get the (required) source string String src = StringConstant; if (!isNext(',')) return false; if (!getSVar()) return false; // get the destination string variable Var.Val val = mVal; if (!checkEOL()) return false; String dest = null; try { dest = (mode == ENCODE) ? encrypt(src, pw) : decrypt(src, pw); } catch (Exception e) { return encryptionException(mode, e); } if (dest == null) return false; val.val(dest); // Put the encrypted string into the user variable return true; } // ************************************* Socket Commands ************************************** private boolean executeSOCKET() { // Get Socket command keyword if it is there return executeSubcommand(Socket_cmd, "Socket"); } private boolean executeSocketServer() { // Get Socket Server command keyword if it is there return executeSubcommand(SocketServer_cmd, "Socket.Server"); } private boolean executeSocketClient() { // Get Socket Client command keyword if it is there return executeSubcommand(SocketClient_cmd, "Socket.Client"); } private boolean isServerSocketConnected() { return isServerSocketConnected("No current connection"); } private boolean isServerSocketConnected(String msgNullSocket) { if (theServerSocket == null) { return RunTimeError(msgNullSocket); } if (!theServerSocket.isConnected()) { return RunTimeError("Server Connection Disrupted"); } return true; } private boolean isClientSocketConnected() { if (theClientSocket == null) { return RunTimeError("Client Socket Not Opened"); } if (!theClientSocket.isConnected()) { return RunTimeError("Client Connection Disrupted"); } return true; } private boolean executeSERVER_CREATE() { if (!evalNumericExpression()) return false; // Get the List pointer if (!checkEOL()) return false; int SocketServersServerPort = EvalNumericExpressionValue.intValue(); try { newSS = new ServerSocket(SocketServersServerPort); } catch (Exception e) { return RunTimeError(e); } return true; } private boolean executeSERVER_ACCEPT() { if (newSS == null) { return RunTimeError("Server not created"); } boolean block = true; // Default if no "wait" parameter is to block. This preserves // behavior from before v01.73, when the parameter was added. if (evalNumericExpression()) { // Optional "wait" parameter block = (EvalNumericExpressionValue != 0.0); } if (!checkEOL()) return false; if ((theServerSocket != null) && theServerSocket.isConnected()) return true; serverSocketState = STATE_LISTENING; serverSocketConnectThread = new ServerSocketConnectThread(); serverSocketConnectThread.start(); if (block) { while (serverSocketState == STATE_LISTENING) { Thread.yield(); } if (serverSocketState != STATE_CONNECTED) { return RunTimeError("Server socket connection error: state " + serverSocketState); } } return true; } private boolean executeCLIENT_CONNECT() { if (!getStringArg()) return false; // get the server address String SocketClientsServerAdr = StringConstant; if (!isNext(',')) return false; // move to the port number if (!evalNumericExpression()) return false; // get the port number int SocketClientsServerPort = EvalNumericExpressionValue.intValue(); boolean block = true; // Default if no "wait" parameter is to block. if (isNext(',')) { if (evalNumericExpression()) { // Optional "wait" parameter block = (EvalNumericExpressionValue != 0.0); } } if (!checkEOL()) return false; clientSocketState = STATE_CONNECTING; clientSocketConnectThread = new ClientSocketConnectThread(SocketClientsServerAdr, SocketClientsServerPort); clientSocketConnectThread.start(); if (block) { while (clientSocketState == STATE_CONNECTING) { Thread.yield(); } if (clientSocketState != STATE_CONNECTED) { return RunTimeError("Client socket connection error: state " + clientSocketState); } } return true; } private boolean executeSERVER_STATUS() { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; double status = (newSS == null) ? STATE_NOT_ENABLED : (serverSocketState == STATE_LISTENING) ? STATE_LISTENING : (theServerSocket == null) ? STATE_NONE : theServerSocket.isConnected() ? STATE_CONNECTED : STATE_NONE; val.val(status); return true; } private boolean executeCLIENT_STATUS() { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; double status = (clientSocketState == STATE_CONNECTING) ? STATE_CONNECTING : (theClientSocket == null) ? STATE_NONE : theClientSocket.isConnected() ? STATE_CONNECTED : STATE_NONE; val.val(status); return true; } private boolean executeSERVER_CLIENT_IP() { if (!isServerSocketConnected("Server not connected to a client")) return false; return socketIP(theServerSocket); } private boolean executeCLIENT_SERVER_IP() { if (!isClientSocketConnected()) return false; return socketIP(theClientSocket); } private boolean socketIP(Socket socket) { if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; InetAddress ia = socket.getInetAddress(); val.val(ia.toString()); return true; } private boolean executeSERVER_READ_READY() { if (!isServerSocketConnected("No current client accepted")) return false; return socketReadReady(ServerBufferedReader); } private boolean executeCLIENT_READ_READY() { if (!isClientSocketConnected()) return false; return socketReadReady(ClientBufferedReader); } private boolean socketReadReady(BufferedReader reader) { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; double ready = 0; try { if (reader.ready()) { ready = 1; } } catch (IOException e) { return RunTimeError(e); } val.val(ready); return true; } private boolean executeSERVER_READ_LINE() { if (!isServerSocketConnected()) return false; return socketReadLine(ServerBufferedReader); } private boolean executeCLIENT_READ_LINE() { if (!isClientSocketConnected()) return false; return socketReadLine(ClientBufferedReader); } private boolean socketReadLine(BufferedReader reader) { if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; String line = null; try { line = reader.readLine(); } catch (Exception e) { return RunTimeError(e); } if (line == null) { line = "NULL"; } val.val(line); return true; } private boolean executeSERVER_WRITE_LINE() { if (!isServerSocketConnected()) return false; return socketWrite(theServerSocket, ServerPrintWriter, false); } private boolean executeCLIENT_WRITE_LINE() { if (!isClientSocketConnected()) return false; return socketWrite(theClientSocket, ClientPrintWriter, false); } private boolean executeSERVER_WRITE_BYTES() { if (!isServerSocketConnected()) return false; return socketWrite(theServerSocket, ServerPrintWriter, true); } private boolean executeCLIENT_WRITE_BYTES() { if (!isClientSocketConnected()) return false; return socketWrite(theClientSocket, ClientPrintWriter, true); } private boolean socketWrite(Socket socket, PrintWriter writer, boolean byteMode) { if (!getStringArg()) return false; if (!checkEOL()) return false; String err = null; if (byteMode) { OutputStream os = null; try { os = socket.getOutputStream(); for (int k = 0; k < StringConstant.length(); ++k) { byte bb = (byte) StringConstant.charAt(k); os.write(bb); } } catch (Exception e) { err = "Error: " + e; } finally { IOException ex = FileInfo.closeStream(os, FileInfo.flushStream(os, null)); if (ex != null && err != null) { err = "Error: " + ex; } } } else { writer.println(StringConstant); if (writer.checkError()) { err = "Error writing to socket"; } } if (err != null) { return RunTimeError(err); } return true; } private boolean executeSERVER_PUTFILE() { if (!isServerSocketConnected()) return false; return socketPutFile(theServerSocket); } private boolean executeCLIENT_PUTFILE() { if (!isClientSocketConnected()) return false; return socketPutFile(theClientSocket); } private boolean executeSERVER_GETFILE() { if (!isServerSocketConnected()) return false; return socketGetFile(theServerSocket); } private boolean executeCLIENT_GETFILE() { if (!isClientSocketConnected()) return false; return socketGetFile(theClientSocket); } private boolean socketPutFile(Socket socket) { if (!evalNumericExpression()) return false; // Parm is the file number variable Var.Val val = mVal; if (!checkEOL()) return false; int fileNumber = (int) val.nval(); if (!checkReadFile(fileNumber)) return false; // Check runtime errors FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkReadAttributes(fInfo, FileType.FILE_BYTE)) return false; // Check runtime errors if (fInfo.isEOF()) { return RunTimeError("Attempt to read beyond the EOF at:"); } BufferedInputStream bis = ((ByteReaderInfo) fInfo).mByteReader; int bufferSize = 1024 * 16; try { OutputStream os = socket.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); // Set buffer size to 16K bytes, timeout to 16 sec, time out if rate is slower than 1kb/sec if (!streamCopy(bis, dos, bufferSize, (long) bufferSize)) { // Copy from file to socket RunTimeError("Data transmission time out."); // Timeout doServerDisconnect(); return false; } } catch (Exception e) { return RunTimeError(e); } finally { fInfo.eof(true); // update fInfo fInfo.close(null); // file is already closed, this cleans up the fInfo } return true; // Success } private boolean socketGetFile(Socket socket) { if (!evalNumericExpression()) return false; // Parm is the file number variable if (!checkEOL()) return false; int fileNumber = EvalNumericExpressionValue.intValue(); if (!checkFile(fileNumber)) return false; // Check runtime errors FileInfo fInfo = FileTable.get(fileNumber); // Get the file info if (!checkWriteAttributes(fInfo, FileType.FILE_BYTE)) return false; // Check runtime errors DataOutputStream dos = ((ByteWriterInfo) fInfo).getDOS(); if (dos == null) { return RunTimeError("Error writing file"); } int bufferSize = 1024 * 512; try { InputStream is = socket.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); streamCopy(bis, dos, bufferSize, 0L); // Copy from socket to file and close streams } catch (Exception e) { return RunTimeError(e); } finally { fInfo.eof(true); // update fInfo fInfo.close(null); // file is already closed, this cleans up the fInfo } return true; // Success } private boolean streamCopy(BufferedInputStream bis, DataOutputStream dos, int bufferSize, long timeoutTime) // time in ms, 0 means no timeout check throws IOException { IOException ex = null; ByteArrayBuffer byteArray = new ByteArrayBuffer(bufferSize); int current = 0; boolean timeout = false; long ts = SystemClock.elapsedRealtime(); try { while (!timeout && ((current = bis.read()) != -1)) { // Read the input stream byteArray.append((byte) current); if (byteArray.isFull()) { dos.write(byteArray.toByteArray(), 0, bufferSize); // Write the output stream byteArray.clear(); if (timeoutTime != 0) { // If caller wants timeout checked long te = SystemClock.elapsedRealtime(); // If rate is too slow timeout = (te - ts > 16000); // terminate transmission ts = te; // reset the start time } } } int count = byteArray.length(); if (count > 0) { // If there is anything in the buffer dos.write(byteArray.toByteArray(), 0, count); // write it to the output stream } dos.flush(); // flush the output stream return !timeout; } catch (IOException e) { ex = e; return false; // doesn't return, but overwrites return value } finally { ex = FileInfo.closeStream(dos, ex); // close the streams ex = FileInfo.closeStream(bis, ex); if (ex != null) { throw ex; } } } private boolean executeSERVER_DISCONNECT() { return checkEOL() && doServerDisconnect(); } private boolean doServerDisconnect() { if (serverSocketConnectThread != null) { serverSocketConnectThread.interrupt(); serverSocketConnectThread = null; } if (theServerSocket == null) return true; try { theServerSocket.close(); } catch (Exception e) { return RunTimeError(e); } finally { ServerPrintWriter = null; ServerBufferedReader = null; theServerSocket = null; serverSocketState = STATE_NONE; } return true; } private boolean executeSERVER_CLOSE() { if (!checkEOL()) return false; boolean disconnect = true; if (theServerSocket != null) { disconnect = doServerDisconnect(); } try { if (newSS != null) newSS.close(); } catch (Exception e) { return RunTimeError(e); } finally { newSS = null; } return disconnect; } private boolean executeCLIENT_CLOSE() { if (!checkEOL()) return false; if (theClientSocket == null) return true; if ((clientSocketState == STATE_CONNECTING) && (clientSocketConnectThread != null)) { clientSocketConnectThread.interrupt(); clientSocketConnectThread = null; } try { theClientSocket.close(); } catch (Exception e) { return RunTimeError(e); } finally { ClientPrintWriter = null; ClientBufferedReader = null; theClientSocket = null; clientSocketState = STATE_NONE; } return true; } private boolean executeMYIP() { Var var = getVarAndType(TYPE_STRING); // string scalar or array to hold IP address(es) Var.Val val = null; // val in case var is scalar Var.Val nVal = null; // optional address count in case var is array if (var == null) return false; if (var.isArray()) { if (!validArrayVarForWrite(var, TYPE_STRING)) return false; if (isNext(',')) { if (!getNVar()) return false; nVal = mVal; } } else { mVal = getVarValue(var); // create a new variable if necessary val = mVal; // can't be null because var is not array } if (!checkEOL()) return false; ArrayList<String> IP = new ArrayList<String>(); try { for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en .hasMoreElements();) { NetworkInterface intf = en.nextElement(); for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr .hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!(inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress())) { IP.add(inetAddress.getHostAddress().toString()); } } } } catch (Exception e) { return RunTimeError(e); } int nIP = IP.size(); if (nIP == 0) { IP.add(""); } // prevent zero-length array if (var.isArray()) { // return arg is array if (!ListToBasicStringArray(var, IP, IP.size())) return false; if (nVal != null) { nVal.val(nIP); } // actual IP count, not array size } else { // return arg is scalar val.val(IP.get(0)); // return first IP address (may be "") } return true; } //****************************************** TTS ******************************************* private boolean executeTTS() { // Get TTS command keyword if it is there return executeSubcommand(tts_cmd, "TTS"); } private boolean executeTTS_INIT() { if (!checkEOL()) return false; if (theTTS != null) return true; // done if already opened theTTS = new TextToSpeechActivity(Run.this); // blocks until initialized if (theTTS == null) return false; switch (theTTS.mStatus) { case TextToSpeech.SUCCESS: break; case TextToSpeech.LANG_MISSING_DATA: return RunTimeError("Language Data Missing"); case TextToSpeech.LANG_NOT_SUPPORTED: return RunTimeError("Language Not Supported"); default: return RunTimeError("TTS Init Failed. Code = " + theTTS.mStatus); } return true; } private boolean executeTTS_SPEAK() { if (theTTS == null) { return RunTimeError("Text to speech not initialized"); } if (!evalStringExpression()) return false; String speech = StringConstant; boolean block = true; // Default if no "wait" parameter is to block. This preserves if (isNext(',')) { // behavior from before v01.76, when the parameter was added. if (!evalNumericExpression()) return false; // optional "wait" flag block = (EvalNumericExpressionValue != 0.0); // block if non-zero } if (!checkEOL()) return false; setVolumeControlStream(AudioManager.STREAM_MUSIC); HashMap<String, String> params = new HashMap<String, String>(); params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC)); theTTS.speak(speech, params, block); return true; } private boolean executeTTS_SPEAK_TOFILE() { if (theTTS == null) { return RunTimeError("Text to speech not initialized"); } if (!evalStringExpression()) return false; String speech = StringConstant; String theFileName; if (isNext(',')) { // optional file name parameter if (!getStringArg()) return false; theFileName = StringConstant; } else { theFileName = "tts.wav"; } // default file name if (!checkEOL()) return false; HashMap<String, String> params = new HashMap<String, String>(); theFileName = Basic.getDataPath(theFileName); theTTS.speakToFile(speech, params, theFileName); // always blocks return true; } private boolean executeTTS_STOP() { return checkEOL() && ttsWaitForDone() && ttsStop(); } private boolean ttsWaitForDone() { // wait for any outstanding speaking to finish if (theTTS != null) { // because cleanup() can kill theTTS while we're not looking theTTS.waitForDone(); } return (theTTS != null); } private boolean ttsStop() { if (theTTS != null) { theTTS.shutdown(); theTTS = null; } return true; } // ******************************************* FTP ******************************************** private boolean executeFTP() { // Get FTP command keyword if it is there return executeSubcommand(ftp_cmd, "FTP"); // and execute the command } private boolean executeFTP_OPEN() { if (!getStringArg()) return false; // URL String url = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // Port int port = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; if (!getStringArg()) return false; // User Name String user = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // Pass word String pw = StringConstant; if (!checkEOL()) return false; if (!ftpConnect(url, user, pw, port)) return false; FTPdir = ftpGetCurrentWorkingDirectory(); if (FTPdir == null) return false; return true; } public boolean ftpConnect(String host, String username, String password, int port) { try { mFTPClient = new FTPClient(); // connecting to the host mFTPClient.connect(host, port); // now check the reply code, if positive mean connection success if (FTPReply.isPositiveCompletion(mFTPClient.getReplyCode())) { // login using username & password boolean status = mFTPClient.login(username, password); /* Set File Transfer Mode * * To avoid corruption issue you must specified a correct * transfer mode, such as ASCII_FILE_TYPE, BINARY_FILE_TYPE, * EBCDIC_FILE_TYPE .etc. Here, I use BINARY_FILE_TYPE * for transferring text, image, and compressed files. */ mFTPClient.setFileType(FTP.BINARY_FILE_TYPE); mFTPClient.enterLocalPassiveMode(); return status; } } catch (Exception e) { RunTimeError(e); } return false; } public String ftpGetCurrentWorkingDirectory() { try { String workingDir = mFTPClient.printWorkingDirectory(); return workingDir; } catch (Exception e) { RunTimeError(e); } return null; } private boolean executeFTP_CLOSE() { if (!checkEOL()) return false; if (FTPdir == null) return true; try { mFTPClient.logout(); mFTPClient.disconnect(); FTPdir = null; return true; } catch (Exception e) { return RunTimeError(e); } } private boolean executeFTP_DIR() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getNVar()) return false; // get the list variable Var.Val val = mVal; String dirMark = "(d)"; if (isNext(',')) { // optional directory marker if (!getStringArg()) return false; dirMark = StringConstant; } if (!checkEOL()) return false; ArrayList<String> theStringList = new ArrayList<String>(); // create a new user list int theIndex = theLists.size(); theLists.add(theStringList); theListsType.add(Var.Type.STR); // add the type val.val(theIndex); // return the list pointer FTPFile[] ftpFiles; try { ftpFiles = mFTPClient.listFiles(); } // get the list of files catch (Exception e) { return RunTimeError(e); } for (FTPFile file : ftpFiles) { // write file names to list var String name = file.getName(); if (file.isDirectory()) { name += dirMark; } // mark directories theStringList.add(name); } return true; } private boolean executeFTP_CD() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // new directory name if (!checkEOL()) return false; String directory_path = "/" + StringConstant; boolean status = false; try { status = mFTPClient.changeWorkingDirectory(directory_path); } catch (Exception e) { return RunTimeError(e); } if (!status) { return RunTimeError("Directory change failed."); } FTPdir = directory_path; return true; } private boolean executeFTP_GET() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // Source file name String srcFile = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // Destination file name String destFile = StringConstant; if (!checkEOL()) return false; destFile = Basic.getDataPath(destFile); return ftpDownload(srcFile, destFile); } public boolean ftpDownload(String srcFilePath, String desFilePath) { FileOutputStream desFileStream = null; boolean status = false; try { desFileStream = new FileOutputStream(desFilePath); status = mFTPClient.retrieveFile(srcFilePath, desFileStream); desFileStream.close(); } catch (Exception e) { FileInfo.closeStream(desFileStream, null); return RunTimeError(e); } if (!status) { RunTimeError("Download error"); } return status; } private boolean executeFTP_PUT() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // Source file name String srcFile = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // Destination file name String destFile = StringConstant; if (!checkEOL()) return false; srcFile = Basic.getDataPath(srcFile); return ftpUpload(srcFile, destFile); } public boolean ftpUpload(String srcFilePath, String desFilePath) { FileInputStream srcFileStream = null; boolean status = false; try { srcFileStream = new FileInputStream(srcFilePath); status = mFTPClient.storeFile(desFilePath, srcFileStream); srcFileStream.close(); } catch (Exception e) { FileInfo.closeStream(srcFileStream, null); return RunTimeError(e); } if (!status) { RunTimeError("Upload problem"); } return status; } public boolean executeFTP_CMD() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // Command String cmd = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // String parameter String parms = StringConstant; if (!isNext(',')) return false; if (!getNVar()) return false; // Numeric parameter if (!checkEOL()) return false; String[] response = null; try { response = mFTPClient.doCommandAsStrings(cmd, parms); } catch (Exception e) { return RunTimeError(e); } PrintShow(response); return true; } private boolean executeFTP_DELETE() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // get the file name String filePath = StringConstant; if (!checkEOL()) return false; boolean status = false; try { status = mFTPClient.deleteFile(filePath); // try to delete the file } catch (Exception e) { return RunTimeError(e); } if (!status) { RunTimeError("File not deleted"); } return status; } private boolean executeFTP_RMDIR() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // get the directory name String filePath = StringConstant; if (!checkEOL()) return false; boolean status = false; try { status = mFTPClient.removeDirectory(filePath); // try to remove it } catch (Exception e) { return RunTimeError(e); } if (!status) { RunTimeError("Directory not deleted"); } return status; } private boolean executeFTP_MKDIR() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // get the directory name String filePath = StringConstant; if (!checkEOL()) return false; boolean status = false; try { status = mFTPClient.makeDirectory(filePath); } catch (Exception e) { return RunTimeError(e); } if (!status) { RunTimeError("Directory not created"); } return status; } private boolean executeFTP_RENAME() { if (FTPdir == null) { return RunTimeError("FTP not opened"); } if (!getStringArg()) return false; // old file name String oldName = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; // new file name String newName = StringConstant; if (!checkEOL()) return false; boolean status = false; try { status = mFTPClient.rename(oldName, newName); } catch (Exception e) { return RunTimeError(e); } if (!status) { RunTimeError("File not renamed"); } return false; } // **************************************** Bluetooth ***************************************** private boolean executeBT() { Command c = findSubcommand(bt_cmd, "BT"); if (c == null) return false; if ((mChatService == null) && (c.id != CID_OPEN) && (c.id != CID_STATUS)) { return RunTimeError("Bluetooth not opened"); } return c.run(); } private synchronized boolean execute_BT_status() { if (isEOL()) return true; // user asked for no data // status variable may be either numeric or string; the name and address are strings byte[] types = { 3, 2, 2 }; // type of each variable int nArgs = types.length; Var.Val[] args = new Var.Val[nArgs]; // Val object for each variable if (!getOptVars(types, args)) return false; int arg = 0; Var.Val val = args[arg]; if (val != null) { int state = (mBluetoothAdapter == null) ? STATE_NOT_ENABLED : (mChatService == null) ? STATE_NONE : bt_state; if (types[arg] == 1) { // status variable is numeric val.val(state); } else { String st = ""; // string representation of state switch (state) { case STATE_NOT_ENABLED: st = "Not enabled"; break; case STATE_NONE: st = "Idle"; break; case STATE_LISTENING: st = "Listening"; break; case STATE_CONNECTING: st = "Connecting"; break; case STATE_CONNECTED: st = "Connected"; break; case STATE_READING: st = "Reading"; break; case STATE_WRITING: st = "Writing"; break; default: break; } val.val(st); } } val = args[++arg]; if (val != null) { String name = (mBluetoothAdapter == null) ? "" : mBluetoothAdapter.getName(); val.val(name); } val = args[++arg]; if (val != null) { String address = (mBluetoothAdapter == null) ? "" : mBluetoothAdapter.getAddress(); val.val(address); } return (++arg == nArgs); // sanity-check arg count } // execute_BT_status private boolean execute_BT_open() { if (mBluetoothAdapter == null) { return RunTimeError("Bluetooth is not available"); } bt_Secure = true; // this flag will be used when starting if (evalNumericExpression()) { // the accept thread in BlueTootChatService if (EvalNumericExpressionValue == 0) { bt_Secure = false; } } if (!checkEOL()) return false; bt_enabled = mBluetoothAdapter.isEnabled() ? 1 : 0; // Is BT enabled? if (bt_enabled == 0) { bt_state = STATE_NOT_ENABLED; // Enable BT if (GRopen) { GR.doEnableBT = true; GR.drawView.postInvalidate(); // Start GR drawing. } else { Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BT); } while (bt_enabled == 0) // Wait until enabled Thread.yield(); if (bt_enabled == -1) { // Enable failed return RunTimeError("Bluetooth Not Enabled"); } } synchronized (BT_Read_Buffer) { bt_state = STATE_NONE; btConnectDevice = null; mOutStringBuffer = new StringBuffer(""); BT_Read_Buffer.clear(); mChatService = new BluetoothChatService(Run.this, mHandler); // Starts the accept thread mChatService.start(bt_Secure); } return true; } private boolean execute_BT_close() { if (mChatService != null) mChatService.stop(); return checkEOL(); } public void connectDevice(Intent data, boolean secure) { String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); btConnectDevice = null; try { btConnectDevice = mBluetoothAdapter.getRemoteDevice(address); if (btConnectDevice != null) mChatService.connect(btConnectDevice, secure); } catch (Exception e) { RunTimeError("Connect error: " + e); } } private boolean execute_BT_connect() { bt_Secure = true; if (evalNumericExpression()) { if (EvalNumericExpressionValue == 0) { bt_Secure = false; } } if (!checkEOL()) return false; if (GRopen) { GR.startConnectBT = true; GR.drawView.postInvalidate(); // Start GR drawing. } else { Intent serverIntent = null; try { serverIntent = new Intent(Run.this, DeviceListActivity.class); } catch (Exception ex) { return RunTimeError("Error selecting device"); } int requestCode = (bt_Secure) ? REQUEST_CONNECT_DEVICE_SECURE : REQUEST_CONNECT_DEVICE_INSECURE; startActivityForResult(serverIntent, requestCode); } return true; } private boolean execute_BT_disconnect() { if (!checkEOL()) return false; mChatService.disconnect(); return true; } private boolean execute_BT_reconnect() { if (!checkEOL()) return false; if (btConnectDevice == null) { return RunTimeError("Not previously connected"); } mChatService.connect(btConnectDevice, bt_Secure); return true; } private boolean execute_BT_listen() { if (!checkEOL()) return false; // mChatService.start(); return true; } private boolean execute_BT_device_name() { if (bt_state != STATE_CONNECTED) { return RunTimeError("Bluetooth not connected"); } if (!getSVar() || !checkEOL()) return false; mVal.val(mConnectedDeviceName); return true; } private boolean execute_BT_write() { if (bt_state != STATE_CONNECTED) { RunTimeError("Bluetooth not connected"); return true; // Deliberately not making error fatal } if (!buildPrintLine("", "\n")) return false; // build up the text line in StringConstant // Check that there's actually something to send if (StringConstant.length() > 0) { // Get the message bytes and tell the BluetoothChatService to write byte[] send = new byte[StringConstant.length()]; for (int k = 0; k < StringConstant.length(); ++k) { send[k] = (byte) StringConstant.charAt(k); } mChatService.write(send); } return true; } private boolean execute_BT_read_ready() { if (!getNVar() || !checkEOL()) return false; int count = 0; if (bt_state == STATE_CONNECTED) { synchronized (BT_Read_Buffer) { count = BT_Read_Buffer.size(); } } mVal.val(count); return true; } private boolean execute_BT_readReady_Resume() { return doResume("No Bluetooth Read Ready Interrupt"); } private boolean execute_BT_read_bytes() { if (bt_state != STATE_CONNECTED) { return RunTimeError("Bluetooth not connected"); } String msg = ""; if (bt_state == STATE_CONNECTED) { synchronized (BT_Read_Buffer) { int index = BT_Read_Buffer.size(); if (index > 0) { msg = BT_Read_Buffer.get(0); BT_Read_Buffer.remove(0); } } } if (!getSVar() || !checkEOL()) return false; mVal.val(msg); return true; } private boolean execute_BT_set_uuid() { if (!evalStringExpression() || !checkEOL()) return false; UUID MY_UUID_SECURE = UUID.fromString(StringConstant); UUID MY_UUID_INSECURE = UUID.fromString(StringConstant); return true; } // *********************************** Superuser and System *********************************** private boolean executeSU(boolean isSU) { // SU command (isSU true) or system comand (isSU false) Command c = findSubcommand(SU_cmd, (isSU ? "SU" : "System")); if (c == null) return false; if (SUprocess == null) { if (c.id == CID_OPEN) { mIsSU = isSU; } else return RunTimeError((isSU ? "Superuser" : "System shell") + " not opened"); } return c.run(); // run the function and report back } private boolean execute_SU_open() { if (!checkEOL()) return false; if (SUprocess != null) return true; SU_ReadBuffer = new ArrayList<String>(); // Initialize buffer File dir = null; if (!mIsSU) { // System.open: make sure AppPath exists dir = new File(Basic.getFilePath()); if (!dir.exists() && !dir.mkdirs()) { return RunTimeError("Cannot create working directory " + dir); } } try { SUprocess = (mIsSU) ? Runtime.getRuntime().exec("su") // Request Superuser : Runtime.getRuntime().exec("sh", null, dir); // Open ordinary shell SUoutputStream = new DataOutputStream(SUprocess.getOutputStream()); // Open the output stream SUinputStream = new BufferedReader( // Open the input stream new InputStreamReader(SUprocess.getInputStream())); } catch (Exception e) { return RunTimeError((mIsSU ? "SU" : "System") + " Exception: " + e); } theSUReader = new SUReader(SUinputStream, SU_ReadBuffer); theSUReader.start(); return true; } private boolean execute_SU_write() { if (!evalStringExpression()) return false; if (!checkEOL()) return false; String command = StringConstant; try { SUoutputStream.writeBytes(command + "\n"); // write the command followed by new line character SUoutputStream.flush(); } catch (Exception e) { return RunTimeError((mIsSU ? "SU" : "System") + " Exception:", e); } return true; } private boolean execute_SU_read_ready() { if (!getNVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; int bfrSize = 0; for (int pass = 0; pass < 3; ++pass) { // try more than once so tight loops in user programs work better synchronized (SU_ReadBuffer) { bfrSize = SU_ReadBuffer.size(); } if (bfrSize != 0) break; // data available Thread.yield(); // give the SUreader another chance to read } val.val(bfrSize); // return buffer size return true; } private boolean execute_SU_read_line() { if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; boolean available; String msg = ""; synchronized (SU_ReadBuffer) { available = (SU_ReadBuffer.size() > 0); if (available) { msg = SU_ReadBuffer.remove(0); // read and remove the first item in the buffer } } if (available) { Thread.yield(); } // give the SUreader a chance to read again val.val(msg); return true; } private boolean execute_SU_close() { theSUReader.stop(); SUprocess.destroy(); SUoutputStream = null; SUinputStream = null; SU_ReadBuffer = null; theSUReader = null; SUprocess = null; return true; } // *************************************** FONT Commands ************************************** private boolean executeFONT() { // Get Console command keyword if it is there return executeSubcommand(font_cmd, "Font"); } private boolean executeFONT_LOAD() { clearErrorMsg(); if (!getNVar()) return false; // font pointer variable Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // get the file path String fileName = StringConstant; // the filename as given by the user if (!checkEOL()) return false; int fontIdx; Typeface aFont = getTypeface(fileName); if (aFont == null) { writeErrorMsg("Font file not found: " + fileName); fontIdx = -1; } else { fontIdx = FontList.size(); FontList.add(aFont); } val.val(fontIdx); return true; } private int getFontArg() { // get the font number return getFontArg("Invalid Font Pointer"); // with the default error message } private int getFontArg(String errMsg) { // get and validate the font number if (!evalNumericExpression()) { return -2; } // or return -2 if argument is not numeric int fontPtr = EvalNumericExpressionValue.intValue(); if (fontPtr < 1 | fontPtr >= FontList.size()) { RunTimeError(errMsg); fontPtr = -1; } return fontPtr; } private boolean executeFONT_DELETE() { Typeface font = null; int fp; // index into FontList if (isEOL()) { // no font number arg for (fp = FontList.size() - 1; (fp > 0) && (font == null); --fp) { font = FontList.get(fp); // find last font loaded and not deleted } if (fp < 1) return true; // nothing to do } else { fp = getFontArg(); // get the font number arg if (fp < 0) return false; if (!checkEOL()) return false; font = FontList.get(fp); // get the font } if (font != null) { FontList.set(fp, null); } return true; } private boolean executeFONT_CLEAR() { // command to clear the font list if (!checkEOL()) return false; clearFontList(); return true; } private void clearFontList() { FontList.clear(); // clear the font list FontList.add(null); // add a dummy element 0 } // ************************************* CONSOLE Commands ************************************* private boolean executeCONSOLE() { // Get Console command keyword if it is there return executeSubcommand(Console_cmd, "Console"); } private boolean executeCONSOLE_TITLE() { // Set the console title string String title; if (isEOL()) { title = null; // Use default } else { if (!getStringArg() || !checkEOL()) return false; // Get new title title = StringConstant; } sendMessage(MESSAGE_CONSOLE_TITLE, title); // Signal UI to update its title return true; } private boolean executeCONSOLE_LINE_COUNT() { if (!getNVar()) return false; // variable to hold the number of lines Var.Val val = mVal; if (!checkEOL()) return false; checkpointMessage(); // allow any pending Console activity to complete while (mMessagePending) { Thread.yield(); } // wait for checkpointMessage semaphore to clear val.val(mConsole.getCount()); // number of lines written to console return true; } private boolean executeCONSOLE_LINE_TEXT() { if (!evalNumericExpression()) return false; // line number to read int lineNum = EvalNumericExpressionValue.intValue(); if (!isNext(',') || !getSVar() || !checkEOL()) return false; // variable for line content Var.Val val = mVal; if (--lineNum < 0) { // convert from 1-based user index to 0-based Java index return RunTimeError("Line number must be >= 1"); } int max = mConsole.getCount(); // number of lines written to console String lineText = (lineNum < max) ? mConsole.getItem(lineNum) : ""; val.val(lineText); return true; } private boolean executeCONSOLE_LINE_TOUCHED() { if (!getNVar()) return false; // variable for last line number touched Var.Val lineVar = mVal; Var.Val longTouchVar = null; if (isNext(',')) { if (!getNVar()) return false; // optional variable indicating short or long touch longTouchVar = mVal; } if (!checkEOL()) return false; lineVar.val(TouchedConsoleLine); if (longTouchVar != null) { longTouchVar.val(ConsoleLongTouch ? 1 : 0); } return true; } private boolean executeCONSOLETOUCH_RESUME() { return doResume("Console not touched"); } private boolean executeCONSOLE_DUMP() { if (!getStringArg() || !checkEOL()) return false; // Only parameter is the filename String theFileName = StringConstant; checkpointMessage(); // allow any pending Console activity to complete while (mMessagePending) { Thread.yield(); } // wait for checkpointMessage semaphore to clear File file = new File(Basic.getDataPath(theFileName)); try { file.createNewFile(); } catch (Exception e) { return RunTimeError(e); } if (!file.exists() || !file.canWrite()) { return RunTimeError("Problem opening " + theFileName); } FileWriter writer = null; try { writer = new FileWriter(file, false); // open the filewriter for the SD Card synchronized (mConsoleBuffer) { for (String line : mOutput) { writer.write(line + "\n"); } } writer.flush(); writer.close(); } catch (Exception e) { return RunTimeError(e); } // Log.d(LOGTAG, "executeCONSOLE_DUMP: file " + theFileName + " written"); return true; } private boolean executeCONSOLE_FRONT() { mConsoleIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(mConsoleIntent); return true; } private boolean executeCONSOLE_LINE_NEW() { PrintShow(""); return true; } private boolean executeCONSOLE_LINE_CHAR() { if (!evalStringExpression()) return false; char c = StringConstant.charAt(0); sendMessage(MESSAGE_CONSOLE_LINE_CHAR, (int) c, 0); return true; } // ************************************* Ringer Commands ************************************** private boolean executeRINGER() { // Get RINGER command keyword if it is there return executeSubcommand(ringer_cmd, "Ringer"); // and execute the command } private boolean executeRINGER_GET_MODE() { if (!getNVar()) return false; // Mode return variable Var.Val val = mVal; if (!checkEOL()) return false; AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int mode = am.getRingerMode(); switch (mode) { // convert from Android to internal values case AudioManager.RINGER_MODE_SILENT: mode = RINGER_SILENT; break; case AudioManager.RINGER_MODE_VIBRATE: mode = RINGER_VIBRATE; break; case AudioManager.RINGER_MODE_NORMAL: mode = RINGER_NORMAL; break; default: mode = RINGER_UNKNOWN; break; } val.val(mode); return true; } private boolean executeRINGER_SET_MODE() { if (!evalNumericExpression()) return false; // Mode value int mode = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; switch (mode) { // convert from internal to Android values case RINGER_SILENT: mode = AudioManager.RINGER_MODE_SILENT; break; case RINGER_VIBRATE: mode = AudioManager.RINGER_MODE_VIBRATE; break; case RINGER_NORMAL: mode = AudioManager.RINGER_MODE_NORMAL; break; default: return true; // bad value: don't change anything } AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); am.setRingerMode(mode); return true; } private boolean executeRINGER_GET_VOLUME() { if (!getNVar()) return false; // volume return variable Var.Val volVar = mVal; Var.Val maxVar = null; if (isNext(',')) { if (!getNVar()) return false; // optional max volume return variable maxVar = mVal; } if (!checkEOL()) return false; AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int max = am.getStreamMaxVolume(AudioManager.STREAM_RING); int vol = am.getStreamVolume(AudioManager.STREAM_RING); volVar.val(vol); if (maxVar != null) { maxVar.val(max); } return true; } private boolean executeRINGER_SET_VOLUME() { if (!evalNumericExpression()) return false; // volume value int vol = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int max = am.getStreamMaxVolume(AudioManager.STREAM_RING); if (vol < 0) vol = 0; else if (vol > max) vol = max; am.setStreamVolume(AudioManager.STREAM_RING, vol, 0); return true; } // **************************************** SOUND POOL **************************************** private boolean executeSOUNDPOOL() { Command c = findSubcommand(sp_cmd, "Soundpool"); if (c == null) return false; if ((theSoundPool == null) && (c.id != CID_OPEN)) { return RunTimeError("SoundPool not opened"); } return c.run(); } private boolean execute_SP_open() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int SP_max = EvalNumericExpressionValue.intValue(); if (SP_max <= 0) { return RunTimeError("Stream count must be > 0"); } // this.setVolumeControlStream(AudioManager.STREAM_MUSIC); theSoundPool = new SoundPool(SP_max, AudioManager.STREAM_MUSIC, 0); return true; } private boolean execute_SP_load() { if (!getNVar()) return false; Var.Val val = mVal; if (!isNext(',')) return false; if (!getStringArg()) return false; // Get the file path if (!checkEOL()) return false; String fileName = StringConstant; // The filename as given by the user String fn = Basic.getDataPath(fileName); int SoundID = theSoundPool.load(fn, 1); if (SoundID == 0) { // if file does not exist if (Basic.isAPK) { // and this is a user APK int resID = Basic.getRawResourceID(fileName); // try to load the file from a raw resource if (resID != 0) { SoundID = theSoundPool.load(getApplicationContext(), resID, 1); } else { // try to load the file from assets AssetFileDescriptor afd = null; try { String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, fileName); afd = getAssets().openFd(assetPath); SoundID = theSoundPool.load(afd, 1); } catch (IOException ex) { } finally { if (afd != null) { try { afd.close(); } catch (IOException e) { } } } } } } val.val(SoundID); return true; } private boolean execute_SP_play() { if (isEOL()) return true; // no parameters Var.Val streamVar = null; boolean isComma = isNext(','); if (!isComma) { if (!getNVar()) return false; // stream return variable streamVar = mVal; isComma = isNext(','); } // soundID, rightVol, leftVol, priority, loop, rate boolean[] isInt = { true, false, false, true, true, false };// true = int, false = float float[] fltVal = { 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f }; int[] intVal = { 0, 0, 0, 0, 0, 0 }; int nArgs = isInt.length; for (int arg = 0; arg < nArgs; ++arg) { if (isComma) { isComma = isNext(','); if (!isComma) { if (!evalNumericExpression()) return false; if (isInt[arg]) { int value = EvalNumericExpressionValue.intValue(); intVal[arg] = value; } else { float value = EvalNumericExpressionValue.floatValue(); fltVal[arg] = value; } isComma = isNext(','); } } } if (isComma || !checkEOL()) return false; int soundID = intVal[0]; float rightVolume = fltVal[1]; if (rightVolume < 0 || rightVolume >= 1.0) { return RunTimeError("Right volume out of range"); } float leftVolume = fltVal[2]; if (leftVolume < 0 || leftVolume >= 1.0) { return RunTimeError("Left volume out of range"); } int priority = intVal[3]; if (priority < 0) { return RunTimeError("Priority less than zero"); } int loop = intVal[4]; float rate = fltVal[5]; int streamID = theSoundPool.play(soundID, leftVolume, rightVolume, priority, loop, rate); if (streamVar != null) { streamVar.val(streamID); } return true; } private boolean execute_SP_stop() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int streamID = EvalNumericExpressionValue.intValue(); theSoundPool.stop(streamID); return true; } private boolean execute_SP_unload() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int soundID = EvalNumericExpressionValue.intValue(); theSoundPool.unload(soundID); return true; } @TargetApi(Build.VERSION_CODES.FROYO) private boolean execute_SP_pause() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (streamID == 0) theSoundPool.autoPause(); else theSoundPool.pause(streamID); return true; } @TargetApi(Build.VERSION_CODES.FROYO) private boolean execute_SP_resume() { if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (streamID == 0) theSoundPool.autoResume(); else theSoundPool.resume(streamID); return true; } private boolean execute_SP_release() { if (!checkEOL()) return false; theSoundPool.release(); theSoundPool = null; return true; } private boolean execute_SP_setvolume() { if (!evalNumericExpression()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; // Left Volume if (!evalNumericExpression()) return false; float leftVolume = EvalNumericExpressionValue.floatValue(); if (!isNext(',')) return false; // Right Volume if (!evalNumericExpression()) return false; float rightVolume = EvalNumericExpressionValue.floatValue(); if (!checkEOL()) return false; if (leftVolume < 0 || leftVolume >= 1.0) { return RunTimeError("Left volume out of range"); } if (rightVolume < 0 || rightVolume >= 1.0) { return RunTimeError("Right volume out of range"); } theSoundPool.setVolume(streamID, leftVolume, rightVolume); return true; } private boolean execute_SP_setpriority() { if (!evalNumericExpression()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; // Priority if (!evalNumericExpression()) return false; int priority = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; if (priority < 0) { return RunTimeError("Priority less than zero"); } theSoundPool.setPriority(streamID, priority); return true; } private boolean execute_SP_setloop() { if (!evalNumericExpression()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; // Loop value if (!evalNumericExpression()) return false; int loop = EvalNumericExpressionValue.intValue(); if (!checkEOL()) return false; theSoundPool.setLoop(streamID, loop); return true; } private boolean execute_SP_setrate() { if (!evalNumericExpression()) return false; int streamID = EvalNumericExpressionValue.intValue(); if (!isNext(',')) return false; // Rate if (!evalNumericExpression()) return false; float rate = EvalNumericExpressionValue.floatValue(); if (!checkEOL()) return false; theSoundPool.setRate(streamID, rate); return true; } // ***************************************** Headset ****************************************** private boolean executeHEADSET() { if (!getNVar()) return false; Var.Val stateVar = mVal; if (!isNext(',')) return false; if (!getSVar()) return false; Var.Val nameVar = mVal; if (!isNext(',')) return false; if (!getNVar()) return false; Var.Val micVar = mVal; if (!checkEOL()) return false; stateVar.val(headsetState); nameVar.val(headsetName); micVar.val(headsetMic); return true; } // ******************************************* SMS ******************************************** private boolean executeSMS() { // Get SMS command keyword if it is there return executeSubcommand(sms_cmd, "SMS"); // and execute the command } private boolean executeSMS_SEND() { if (!getStringArg()) return false; String number = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String msg = StringConstant; if (!checkEOL()) return false; SmsManager sm = android.telephony.SmsManager.getDefault(); try { sm.sendTextMessage(number, null, msg, null, null); } catch (Exception e) { return RunTimeError(e); } return true; } private boolean executeSMS_RCV_INIT() { if (!checkEOL()) return false; registerReceiver(receiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED")); smsRcvBuffer = new ArrayList<String>(); return true; } private boolean executeSMS_RCV_NEXT() { if (smsRcvBuffer == null) { return RunTimeError("SMS.RCV.INIT not executed)"); } if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; if (smsRcvBuffer.size() != 0) { val.val(smsRcvBuffer.get(0)); smsRcvBuffer.remove(0); } else { val.val("@"); } return true; } private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context arg0, Intent arg1) { Bundle bundle = arg1.getExtras(); SmsMessage[] recievedMsgs = null; String str = ""; if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); recievedMsgs = new SmsMessage[pdus.length]; for (int i = 0; i < recievedMsgs.length; ++i) { recievedMsgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); str += "SMS from " + recievedMsgs[i].getOriginatingAddress() + " :" + recievedMsgs[i].getMessageBody().toString(); if (smsRcvBuffer != null) smsRcvBuffer.add(str); } } } }; // *********************************** Phone Calls and Info *********************************** private boolean executePHONE() { // Get phone command keyword if it is there return executeSubcommand(phone_cmd, "Phone"); // and execute the command } private boolean executePHONE_DIAL(String action) { // Dial or call a phone number if (!getStringArg()) return false; if (!checkEOL()) return false; String number = "tel:" + StringConstant; String encodedHash = Uri.encode("#"); number = number.replace("#", encodedHash); Intent callIntent = new Intent(action); callIntent.setData(Uri.parse(number)); // this will make such that when user returns to your app, your app is displayed, instead of the phone app. callIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { startActivityForResult(callIntent, BASIC_GENERAL_INTENT); } catch (Exception e) { return RunTimeError(e); } return true; } private boolean executePHONE_RCV_INIT() { if (!checkEOL()) return false; if (phoneRcvInited) return true; phoneRcvInited = true; mTM = (TelephonyManager) Run.this.getSystemService(Context.TELEPHONY_SERVICE); mTM.listen(PSL, PhoneStateListener.LISTEN_CALL_STATE + PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); return true; } PhoneStateListener PSL = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { phoneState = state; if (phoneState == TelephonyManager.CALL_STATE_RINGING) { phoneNumber = incomingNumber; } } @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { mSignalStrength = signalStrength; } }; private boolean executePHONE_RCV_NEXT() { if (!phoneRcvInited) { return RunTimeError("phone.rcv.init not executed"); } if (!getNVar()) return false; Var.Val stateVar = mVal; if (!isNext(',')) return false; if (!getSVar()) return false; Var.Val numberVar = mVal; if (!checkEOL()) return false; int callState = mTM.getCallState(); if (callState == TelephonyManager.CALL_STATE_IDLE) { phoneNumber = ""; } stateVar.val(callState); numberVar.val(phoneNumber); return true; } private boolean executeMYPHONENUMBER() { if (!getSVar()) return false; Var.Val val = mVal; if (!checkEOL()) return false; String pn = getPhoneNumber(null, "Get phone number failed."); val.val(pn); return true; // Leave theValueIndex intact for executeDEVICE } private boolean executePHONE_INFO() { // This is dynamic info. Some static // phone info is available from executeDEVICE(). int bundleIndex = getBundleArg(); // get the Bundle pointer if (bundleIndex < 0) return false; if (!checkEOL()) return false; Bundle b = theBundles.get(bundleIndex); TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); String phoneType = getPhoneType(tm); String networkType = getNetworkType(tm); b.putString("PhoneType", phoneType); b.putString("NetworkType", networkType); CellLocation loc = (tm != null) ? tm.getCellLocation() : null; if (loc == null) { // no CellLocation available - not on a network? } else if (phoneType.equals("GSM")) { double cid = ((GsmCellLocation) loc).getCid(); double lac = ((GsmCellLocation) loc).getLac(); b.putDouble("CID", cid); b.putDouble("LAC", lac); String mcc_mnc = tm.getNetworkOperator(); String operator = tm.getNetworkOperatorName(); b.putString("MCC/MNC", mcc_mnc); b.putString("Operator", operator); } else if (phoneType.equals("CDMA")) { double baseID = ((CdmaCellLocation) loc).getBaseStationId(); double networkID = ((CdmaCellLocation) loc).getNetworkId(); double systemID = ((CdmaCellLocation) loc).getSystemId(); b.putDouble("BaseID", baseID); b.putDouble("NetworkID", networkID); b.putDouble("SystemID", systemID); } if (mSignalStrength != null) { // The PhoneStateListener is listening and caught a signal strength change. // Try to use reflection to find "@hide" methods available in some API levels. try { Class<?> c = Class.forName("android.telephony.SignalStrength"); java.lang.reflect.Method m = c.getMethod("getLevel"); Integer level = (Integer) m.invoke(mSignalStrength, (Object[]) null); b.putDouble("SignalLevel", level.doubleValue()); } catch (NoSuchMethodException nsm) { // fall back on less flexible methods if (phoneType.equals("GSM")) { b.putDouble("GsmSignal", (double) mSignalStrength.getGsmSignalStrength()); } else if (phoneType.equals("CDMA")) { b.putDouble("CdmaDbm", (double) mSignalStrength.getCdmaDbm()); } } catch (Exception e) { } try { Class<?> c = Class.forName("android.telephony.SignalStrength"); java.lang.reflect.Method m = c.getMethod("getAsuLevel"); Integer level = (Integer) m.invoke(mSignalStrength, (Object[]) null); b.putDouble("SignalASU", level.doubleValue()); } catch (Exception e) { } } return true; } private String getPhoneNumber(TelephonyManager tm, String failMsg) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } String pn = (tm != null) ? tm.getLine1Number() : null; return (pn != null) ? pn : failMsg; } private String getDeviceID(TelephonyManager tm, String failMsg) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } String id = (tm != null) ? tm.getDeviceId() : null; return (id != null) ? id : failMsg; } private String getPhoneType(TelephonyManager tm) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } int typecode = (tm != null) ? tm.getPhoneType() : TelephonyManager.PHONE_TYPE_NONE; String type; switch (typecode) { default: case TelephonyManager.PHONE_TYPE_NONE: type = "None"; break; case TelephonyManager.PHONE_TYPE_GSM: type = "GSM"; break; case TelephonyManager.PHONE_TYPE_CDMA: type = "CDMA"; break; case TelephonyManager.PHONE_TYPE_SIP: type = "SIP"; break; // API level >= 11 } return type; } private String getNetworkType(TelephonyManager tm) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } int typecode = (tm != null) ? tm.getNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN; String type; switch (typecode) { default: case TelephonyManager.NETWORK_TYPE_UNKNOWN: type = "Unknown"; break; case TelephonyManager.NETWORK_TYPE_GPRS: type = "GPRS"; break; case TelephonyManager.NETWORK_TYPE_EDGE: type = "EDGE"; break; case TelephonyManager.NETWORK_TYPE_UMTS: type = "UMTS"; break; case TelephonyManager.NETWORK_TYPE_CDMA: type = "CDMA"; break; case TelephonyManager.NETWORK_TYPE_EVDO_0: type = "EVDOrev0"; break; case TelephonyManager.NETWORK_TYPE_EVDO_A: type = "EVDOrevA"; break; case TelephonyManager.NETWORK_TYPE_1xRTT: type = "1xRTT"; break; case TelephonyManager.NETWORK_TYPE_HSDPA: type = "HSDPA"; break; case TelephonyManager.NETWORK_TYPE_HSUPA: type = "HSUPA"; break; case TelephonyManager.NETWORK_TYPE_HSPA: type = "HSPA"; break; case TelephonyManager.NETWORK_TYPE_IDEN: type = "iDen"; break; // API level >= 8 case TelephonyManager.NETWORK_TYPE_EVDO_B: type = "EVDOrevB"; break; // API level >= 9 case TelephonyManager.NETWORK_TYPE_LTE: type = "LTE"; break; // API level >= 11 case TelephonyManager.NETWORK_TYPE_EHRPD: type = "EHRPD"; break; // API level >= 11 case TelephonyManager.NETWORK_TYPE_HSPAP: type = "HSPAP+"; break; // API level >= 13 } return type; } private int getSimState(TelephonyManager tm) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } return (tm != null) ? tm.getSimState() : TelephonyManager.SIM_STATE_UNKNOWN; } private String getSimSN(TelephonyManager tm, String failMsg) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } String sn = failMsg; if (tm != null) { int state = getSimState(tm); if (state == TelephonyManager.SIM_STATE_ABSENT) { sn = "No SIM"; } else if (state == TelephonyManager.SIM_STATE_READY) { String realSN = tm.getSimSerialNumber(); if (realSN != null) { sn = realSN; } } } return sn; } private String getSimOperator(TelephonyManager tm, String failMsg) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } return (getSimState(tm) == TelephonyManager.SIM_STATE_READY) ? tm.getSimOperator() : failMsg; } private String getSimOpName(TelephonyManager tm, String failMsg) { if (tm == null) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); } return (getSimState(tm) == TelephonyManager.SIM_STATE_READY) ? tm.getSimOperatorName() : failMsg; } // ****************************************** EMAIL ******************************************* private boolean executeEMAIL_SEND() { if (!getStringArg()) return false; String recipiant = "mailto:" + StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String subject = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String body = StringConstant; if (!checkEOL()) return false; Intent intent = new Intent(Intent.ACTION_SENDTO); // it's not ACTION_SEND intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, body); intent.setData(Uri.parse(recipiant)); // or just "mailto:" for blank // this will make such that when user returns to your app, your app is displayed, instead of the email app. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { startActivityForResult(intent, BASIC_GENERAL_INTENT); } catch (Exception e) { return RunTimeError(e); } return true; } // ***************************************** VOLKEYS ****************************************** private boolean executeVOLKEYS() { // Get VOLKEYS command keyword if it is there return executeSubcommand(VolKeys_cmd, "VolKeys"); // and execute the command } private boolean executeVOLKEYS_TOGGLE(boolean on) { // Enable or disable passing certain keys to system: // VOL up/down/mute, MUTE (mic), HEADSET hook if (!checkEOL()) return false; mBlockVolKeys = !on; // OFF means block keys, ON means let keys through to the system. return true; } // ******************************************* HTML ******************************************* private boolean executeHTML() { if (htmlOpening) { while (Web.aWebView == null) Thread.yield(); htmlOpening = false; } Command c = findSubcommand(html_cmd, "HTML"); if (c == null) return false; if ((htmlIntent == null) || (Web.aWebView == null)) { if (c.id == CID_CLOSE) return true; // Allow close if already closed if ((c.id != CID_OPEN) && // Allow open and get.datalink if not opened (c.id != CID_DATALINK)) { return RunTimeError("html not opened"); } } return c.run(); } private boolean execute_html_open() { if (Web.aWebView != null) { return RunTimeError("HTML previously open and not closed"); } int showStatusBar = 0; // default to status bar not showing int orientation = -1; // default to orientation per sensor if (evalNumericExpression()) { showStatusBar = EvalNumericExpressionValue.intValue(); if (isNext(',')) { if (!evalNumericExpression()) return false; orientation = EvalNumericExpressionValue.intValue(); } } if (!checkEOL()) return false; htmlIntent = new Intent(Run.this, Web.class); // Intent variable used to tell if opened htmlIntent.putExtra(Web.EXTRA_SHOW_STATUSBAR, showStatusBar); htmlIntent.putExtra(Web.EXTRA_ORIENTATION, orientation); Web.aWebView = null; // Will be set in Web.java htmlData_Buffer = new ArrayList<String>(); // Initialize the datalink buffer sendMessage(MESSAGE_HTML_OPEN); // Start Web View in UI thread. htmlOpening = true; return true; } private String getURL(String path) { // build a URL for a file or directory in the file system or assets String urlPath = Basic.getDataPath(path); // if path is null, get full path of default data directory // if non-null, get full path of a file in the data directory if (!new File(urlPath).exists() && Basic.isAPK) { // if file or dir does not exist in file system, and this is an APK String assetPath = Basic.getAppFilePath(Basic.DATA_DIR, path); // then look for it in assets String type = getAssetType(assetPath); // type is null if asset not found String expType = (path == null) ? "d" : "f"; // are we looking for a directory or a file? if ((type != null) && type.equals(expType)) { // if asset exists and is the expected type, use its path urlPath = "/android_asset/" + assetPath; } } // else use the file system path return "file://" + urlPath; // make the path into a URL } private boolean execute_html_orientation() { // change the screen orientation if (!evalNumericExpression() || !checkEOL()) return false; Web.aWebView.setOrientation(EvalNumericExpressionValue.intValue()); return true; } private boolean execute_html_load_url() { // Load an internet url if (!getStringArg() || !checkEOL()) return false; String urlString = StringConstant; String protocolName = urlString.substring(0, 4); if (!protocolName.equals("http") && !protocolName.equals("java") && !protocolName.equals("file")) { urlString = getURL(urlString); // Get URL with full path to file in file system or assets. // If neither file nor asset exists, // path points to non-existent file system file. } sendMessage(MESSAGE_LOAD_URL, urlString); return true; } private boolean execute_html_load_string() { // Load an html string if (!getStringArg() || !checkEOL()) return false; String baseURL = getURL(null) + File.separatorChar; // baseURL is default data directory in file system or assets. // If directory does not exist in either file system or assets, // path points to non-existent file system directory. String[] data = { baseURL, StringConstant }; sendMessage(MESSAGE_LOAD_STRING, data); return true; } private boolean execute_html_get_datalink() { // Gets a data sring from datalink queue if (!getSVar()) return false; // The string return variable Var.Val val = mVal; if (!checkEOL()) return false; String data = ""; if (htmlData_Buffer != null) { synchronized (htmlData_Buffer) { if (htmlData_Buffer.size() > 0) { // If the buffer is not empty data = htmlData_Buffer.remove(0); // get the oldest entry and remove it from the buffer } } } val.val(data); // return the data to the user if (Web.aWebView == null) return true; // if already closed, return now // else check to see if we should close // if (data.startsWith("FOR:")) return execute_html_close(); // if Form, close the html if (data.startsWith("ERR:")) return execute_html_close(); // if error, close the html return true; } private void addDataLink(String data) { if (htmlData_Buffer != null) { synchronized (htmlData_Buffer) { htmlData_Buffer.add(data); } } } private boolean execute_html_go_back() { if (!checkEOL()) return false; // Web.aWebView.goBack(); sendMessage(MESSAGE_GO_BACK); return true; } private boolean execute_html_go_forward() { if (!checkEOL()) return false; // Web.aWebView.goForward(); sendMessage(MESSAGE_GO_FORWARD); return true; } private boolean execute_html_clear_cache() { if (!checkEOL()) return false; // Web.aWebView.clearCache(); sendMessage(MESSAGE_CLEAR_CACHE); return true; } private boolean execute_html_clear_history() { if (!checkEOL()) return false; // Web.aWebView.clearHistory(); sendMessage(MESSAGE_CLEAR_HISTORY); return true; } private boolean execute_html_close() { // Close the html if (!checkEOL()) return false; if (Web.aWebView != null) Web.aWebView.webClose(); // if it is open while (Web.aWebView != null) Thread.yield(); // wait for the close signal htmlIntent = null; // indicate not open return true; } private boolean execute_html_post() { if (!getStringArg()) return false; String url = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; if (!checkEOL()) return false; int theListIndex = EvalNumericExpressionValue.intValue(); if (theListIndex < 1 || theListIndex >= theLists.size()) { return RunTimeError("Invalid list pointer"); } if (theListsType.get(theListIndex) != Var.Type.STR) { return RunTimeError("List must be of string type."); } List<String> thisList = theLists.get(theListIndex); int r = thisList.size() % 2; if (r != 0) { return RunTimeError("List must have even number of elements"); } StringBuilder sb = new StringBuilder(); int i = 0; while (i < thisList.size()) { sb.append(thisList.get(i++)).append('='); sb.append(thisList.get(i++)).append('&'); } String[] params = { url, sb.substring(0, sb.length() - 1) }; sendMessage(MESSAGE_POST, params); return true; } // *************************************** Run Command **************************************** private boolean executeRUN() { if (!getStringArg()) return false; // get program filename String fileName = StringConstant; String data = ""; if (isNext(',')) { // optional if (!getStringArg()) return false; // parameter to pass to program data = StringConstant; } if (!checkEOL()) return false; String path = Basic.getFilePath(Basic.SOURCE_DIR, fileName); boolean exists = false; if (!Basic.isAPK) { exists = new File(path).exists(); } // standard BASIC can only RUN a file else if (Basic.getRawResourceID(fileName) != 0) { exists = true; } // APK can run resource else { // or asset String assetPath = Basic.getAppFilePath(Basic.SOURCE_DIR, fileName); if (getAssetType(assetPath) == "f") { exists = true; } // it is a valid asset file } if (!exists) { // error if the program does not exist return RunTimeError(fileName + " not found"); } // This AutoRun will be used later to load the new program // and to get an Intent to create a new Interpreter to run it. Context context = getApplicationContext(); mAutoRun = new AutoRun(context, fileName, true, data); // use file name without path Exit = true; // do not allow interrupt processing return true; } // ********************************** Empty Program Command *********************************** private boolean executeEMPTY_PROGRAM() { PrintShow("Nothing to execute."); Stop = true; return true; } // ************************************** Notify Command ************************************** private boolean executeNOTIFY() { int NOTIFICATION_ID = 1; // These two constants are without meaning in this application int REQUEST_CODE = 2; if (!getStringArg()) return false; String title = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String subtitle = StringConstant; if (!isNext(',')) return false; if (!getStringArg()) return false; String msg = StringConstant; if (!isNext(',')) return false; if (!evalNumericExpression()) return false; // logical expression: wait flag boolean wait = (EvalNumericExpressionValue != 0); if (!checkEOL()) { return false; } Notified = false; NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.icon, msg, System.currentTimeMillis()); // The PendingIntent will launch activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(Run.this, REQUEST_CODE, new Intent(Run.this, HandleNotify.class), 0); notification.setLatestEventInfo(Run.this, title, subtitle, contentIntent); notification.flags = Notification.FLAG_AUTO_CANCEL; manager.notify(NOTIFICATION_ID, notification); if (wait) { while (!Notified) Thread.yield(); } return true; } // *************************************** Swap Command *************************************** private boolean executeSWAP() { if (!getVar()) return false; Var.Val aVar = mVal; boolean aIsNumeric = aVar.isNumeric(); if (!isNext(',')) return false; if (!getVar()) return false; Var.Val bVar = mVal; if (aIsNumeric != bVar.isNumeric()) { return RunTimeError("Type mismatch"); } if (!checkEOL()) return false; if (aIsNumeric) { double aValue = aVar.nval(); double bValue = bVar.nval(); aVar.val(bValue); bVar.val(aValue); } else { String aValue = aVar.sval(); String bValue = bVar.sval(); aVar.val(bValue); bVar.val(aValue); } return true; } // ********************************* Speech-to-Text Commands ********************************** private boolean executeSTT_LISTEN() { if (isEOL()) { sttPrompt = sttDefaultPrompt; } else { if (!getStringArg()) return false; sttPrompt = StringConstant; if (!checkEOL()) return false; } PackageManager pm = getPackageManager(); List<ResolveInfo> activities = pm .queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); if (activities.size() == 0) { return RunTimeError("Recognizer not present"); } sttListening = true; sttDone = false; if (GRopen) { GR.doSTT = true; } else { Intent intent = Run.buildVoiceRecognitionIntent(); startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE); } return true; } private boolean executeSTT_RESULTS() { if (!sttListening) { return RunTimeError("STT_LISTEN not executed."); } int listIndex = getListArg(Var.Type.STR); // reuse old list or create new one if (listIndex < 0) return false; if (!checkEOL()) return false; while (!sttDone) Thread.yield(); sttListening = false; if (sttResults == null) { sttResults = new ArrayList<String>(); sttResults.add("Recognition Cancelled"); } theLists.set(listIndex, sttResults); // give the list of results to the user return true; } // ************************************** Timer Commands ************************************** private boolean executeTIMER() { // Get Timer command keyword if it is there return executeSubcommand(Timer_cmd, "Timer"); } private boolean executeTIMER_SET() { if (theTimer != null) { return RunTimeError("Previous Timer Not Cleared"); } if (!mIntA.containsKey(Interrupt.TIMER_BIT)) { return RunTimeError("No OnTimer: Label"); } if (!evalNumericExpression()) return false; long interval = EvalNumericExpressionValue.longValue(); if (interval < 1) { interval = 1; } // Disallow negative or zero if (!checkEOL()) return false; TimerTask tt = new TimerTask() { public void run() { triggerInterrupt(Interrupt.TIMER_BIT); } }; theTimer = new Timer(); theTimer.scheduleAtFixedRate(tt, interval, interval); return true; } private boolean executeTIMER_CLEAR() { if (!checkEOL()) return false; cancelTimer(); return true; } private void cancelTimer() { if (theTimer != null) { theTimer.cancel(); theTimer = null; } } private boolean executeTIMER_RESUME() { return doResume("No timer interrupt to resume"); } // *************************************** Home Command *************************************** private boolean executeHOME() { if (!checkEOL()) return false; moveTaskToBack(true); return true; } private boolean executeBACKGROUND_RESUME() { return doResume("No background state change"); } // ****************************** Android Activity Manager (AM) ******************************* private boolean executeAPP() { // Get APPlication command keyword if it is there return executeSubcommand(app_cmd, "APP"); } private Intent buildIntentForAPP() { // Six optional string expressions and two optional numeric expressions: // the action, data, package, component, type, categories, bundle pointer, flags byte[] type = { 2, 2, 2, 2, 2, 2, 1, 1 }; Double[] nVal = { null, null, null, null, null, null, null, null }; String[] sVal = { null, null, null, null, null, null, null, null }; if (!getOptExprs(type, nVal, sVal)) return null; String action = sVal[0]; String data = sVal[1]; String pkg = sVal[2]; String comp = sVal[3]; String mime = sVal[4]; String cats = sVal[5]; Double bIdx = nVal[6]; Double flags = nVal[7]; Intent intent = new Intent(); // corresponding adb "am start" parameters if (action != null) { intent.setAction(action); } // am -a if (data != null) { // am -d and -t Uri dataUri = Uri.parse(data); if (mime == null) { intent.setData(dataUri); } // data, no MIME type else { intent.setDataAndType(dataUri, mime); } // data and MIME type } else if (mime != null) { intent.setType(mime); } // MIME type, no data if (comp != null) { // component name for am -n if (pkg != null) { intent.setClassName(pkg, comp); // package name given } else { intent.setClassName(Run.this, comp); // no package given } } if (cats != null) { // am -c (multiple allowed) String[] catArray = sVal[5].split("\\s+,\\s+", 0); // comma-separated list of categories for (String cat : catArray) { intent.addCategory(cat); } } if (bIdx != null) { // am -e (limited subset) int bundleIndex = bIdx.intValue(); // index of a bundle of extras if ((bundleIndex <= 0) || (bundleIndex >= theBundles.size())) { return null; // expression is not valid Bundle pointer } intent.putExtras(theBundles.get(bundleIndex)); } if (flags != null) { intent.addFlags(flags.intValue()); } // am -f return intent; } private boolean executeAPP_BROADCAST() { // Broadcast an Intent to application(s) if (isEOL()) return true; // nothing to do Intent intent = buildIntentForAPP(); if (intent != null) { try { Run.this.sendBroadcast(intent); } catch (Exception e) { return RunTimeError(e); } return true; } return false; } private boolean executeAPP_START() { // Start an Application's Activity via Intent if (isEOL()) return true; // nothing to do Intent intent = buildIntentForAPP(); if (intent != null) { try { Run.this.startActivity(intent); } catch (Exception e) { return RunTimeError(e); } return true; } return false; } // ************************************** Debug Commands ************************************** private boolean executeDEBUG() { // Get debug command keyword if it is there return executeSubcommand(debug_cmd, "Debug"); // and execute the command } private boolean executeECHO() { // Legacy: can use DEBUG.ECHO or just ECHO return executeSubcommand(echo_cmd, "Echo"); } private boolean executeDEBUG_TOGGLE(boolean on) { // parameter is ON or OFF if (!checkEOL()) return false; Debug = on; if (!Debug) { Echo = OFF; } return true; } private boolean executeDEBUG_PRINT() { if (Debug) executePRINT(); return true; } private boolean executeECHO_TOGGLE(boolean on) { // parameter is ON or OFF if (!checkEOL()) return false; Echo = Debug & on; return true; } private boolean executeDEBUG_COMMANDS() { if (!Debug) return true; if (isEOL()) return true; // user asked for no data int listIndex = -1; ArrayList<String> list = null; if (!isNext(',')) { listIndex = getListArg(Var.Type.STR); // get a reusable List pointer - may create new list if (listIndex < 0) return false; // failed to get or create a list isNext(','); // consume comma, if there is one } // Three optional numeric variables for counts of math functions, string functions, and command keywords. byte[] types = { 1, 1, 1 }; // type of each variable int nArgs = types.length; Var.Val[] args = new Var.Val[nArgs]; // Val object for each variable if (!getOptVars(types, args)) return false; Var.Val countVar = args[nArgs - 1]; // keyword count var is last in array int kwCount = 0; if (listIndex >= 0) { list = new ArrayList<String>(); // the list of commands theLists.set(listIndex, list); // put new list in theLists } if ((listIndex >= 0) || (countVar != null)) { // if need list or command keyword count HashMap<String, String[]> groups = getKeywordLists(); // command groups if (list != null) { for (String kw : MathFunctions) { list.add(kw + ')'); } for (String kw : StringFunctions) { list.add(kw + ')'); } } for (String kw : BasicKeyWords) { // build list and/or count keywords if (!kw.endsWith(".")) { if (list != null) { list.add(kw); } ++kwCount; } else { String[] group = groups.get(kw); for (String sub : group) { if (list != null) { list.add(kw + sub); } ++kwCount; } } } } int[] vals = { MathFunctions.length, StringFunctions.length, kwCount }; for (int arg = 0; arg < nArgs; ++arg) { Var.Val argval = args[arg]; if (argval != null) { argval.val(vals[arg]); } } return true; } // executeDEBUG_COMMANDS private String plusBase(int val) { return (val == 0) ? "0" : "1 + " + (val - 1); } private String varStats(Var.Table symbols) { ArrayList<Var> vars = symbols.mVars; int scalars = 0; int arrays = 0; for (Var v : vars) { if (v instanceof Var.ScalarVar) { ++scalars; } else if (v.isArray()) { ++arrays; } } return "\n# variables: " + vars.size() + "\n scalars : " + scalars + "\n arrays : " + arrays; } private boolean executeDEBUG_STATS() { if (Debug) { ActivityManager actvityManager = (ActivityManager) Run.this.getSystemService(ACTIVITY_SERVICE); PrintShow("Mem class : " + actvityManager.getMemoryClass(), "Local scope" + varStats(mSymbolTable), "Global scope" + varStats(mGlobalSymbolTable), "# labels : " + Labels.size(), " lists : " + plusBase(theLists.size()), // item 0 always present but not accessible " stacks : " + plusBase(theStacks.size()), // item 0 always present but not accessible " bundles : " + plusBase(theBundles.size()), // item 0 always present but not accessible " functions: " + mFunctionTable.size(), " nested : " + FunctionStack.size(), " fonts : " + plusBase(FontList.size()), // item 0 always present but not accessible " paints : " + plusBase(PaintList.size()), // item 0 present after GR.Open but not accessible " bitmaps : " + plusBase(BitmapList.size()), // item 0 present after GR.Open but not accessible "DL size : " + plusBase(DisplayList.size()), // item 0 present after GR.Open but not accessible "RealDL size: " + plusBase(RealDisplayList.size()) // item 0 present after GR.Open but not accessible ); int c; c = InChar.size(); if (c != 0) { PrintShow("InKey count: " + c); } c = IfElseStack.size(); if (c != 0) { PrintShow("IF stack : " + c); } c = ForNextStack.size(); if (c != 0) { PrintShow("FOR stack : " + c); } c = WhileStack.size(); if (c != 0) { PrintShow("WHILE stack: " + c); } c = DoStack.size(); if (c != 0) { PrintShow("DO stack : " + c); } c = GosubStack.size(); if (c != 0) { PrintShow("GOSUB stack: " + c); } c = FunctionStack.size(); if (c != 0) { PrintShow("Call stack : " + c); } } return true; } private boolean executeDUMP_SCALARS() { if (!Debug) return true; if (!checkEOL()) return false; ArrayList<String> lines = dbDoScalars(""); for (String line : lines) { if (line != null) { PrintShow(line); } } PrintShow("...."); return true; } private boolean executeDUMP_ARRAY() { if (!Debug) return true; Var var = getVarAndType(); if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (var.isNew()) { return RunTimeError(EXPECT_ARRAY_EXISTS); } // No checkEOL: ignore anything after the '[' WatchedArray = var; ArrayList<String> lines = dbDoArray(""); for (String line : lines) { if (line != null) { PrintShow(line); } } PrintShow("...."); return true; } private boolean executeDUMP_LIST() { if (!Debug) return true; int listIndex = getListArg(); // get the list pointer if (listIndex < 0) return false; if (!checkEOL()) return false; WatchedList = listIndex; ArrayList<String> lines = dbDoList(""); for (String line : lines) { if (line != null) { PrintShow(line); } } PrintShow("...."); return true; } private boolean executeDUMP_STACK() { if (!Debug) return true; int stackIndex = getStackIndexArg(); // get the stack pointer if (stackIndex < 0) return false; if (!checkEOL()) return false; WatchedStack = stackIndex; ArrayList<String> lines = dbDoStack(""); for (String line : lines) { if (line != null) { PrintShow(line); } } PrintShow("...."); return true; } private boolean executeDUMP_BUNDLE() { if (!Debug) return true; int bundleIndex = getBundleArg(); // get the Bundle pointer if (bundleIndex < 0) return false; if (!checkEOL()) return false; WatchedBundle = bundleIndex; ArrayList<String> lines = dbDoBundle(""); for (String line : lines) { if (line != null) { PrintShow(line); } } PrintShow("...."); return true; } //=====================DEBUGGER DIALOG STUFF======================== private boolean executeDEBUG_WATCH_CLEAR() { if (!Debug) return true; if (!checkEOL()) return false; WatchedVars.clear(); return (WatchedVars.isEmpty()); } private boolean executeDEBUG_WATCH() { // separate the names and store them if (!Debug) return true; String text = ExecutingLineBuffer.text(); int max = text.length() - 1; int ni = LineIndex; // start of name string do { int i = text.indexOf(',', ni); if (i < 0) { i = max; } Var var = getVarAndType(); boolean add = (var != null); for (int j = 0; j < WatchedVars.size(); ++j) { if (WatchedVars.get(j) == var) { add = false; } } if (add) { WatchedVars.add(var); } LineIndex = ni = i + 1; } while (ni < max); return true; } private boolean executeDEBUG_SHOW_SCALARS() { DialogSelector(1); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_ARRAY() { if (!Debug) return true; Var var = getVarAndType(); if ((var == null) || !var.isArray()) { return RunTimeError(EXPECT_ARRAY_VAR); } if (var.isNew()) { return RunTimeError(EXPECT_ARRAY_EXISTS); } WatchedArray = var; DialogSelector(2); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_LIST() { if (!Debug) return true; int listIndex = getListArg(); // get the list pointer if (listIndex < 0) return false; WatchedList = listIndex; DialogSelector(3); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_STACK() { if (!Debug) return true; int stackIndex = getStackIndexArg(); // get the stack pointer if (stackIndex < 0) return false; WatchedStack = stackIndex; DialogSelector(4); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_BUNDLE() { if (!Debug) return true; int bundleIndex = getBundleArg(); // get the Bundle pointer if (bundleIndex < 0) return false; WatchedBundle = bundleIndex; DialogSelector(5); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_WATCH() { if (!Debug) return true; DialogSelector(6); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_CONSOLE() { if (!Debug) return true; DialogSelector(7); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW_PROGRAM() { if (!Debug) return true; DialogSelector(8); executeDEBUG_SHOW(); return true; } private boolean executeDEBUG_SHOW() { // trigger do debug dialog if (!Debug) return true; WaitForDebugResume = true; // make RunLoop() check debug flags DebuggerStep = true; // make RunLoop() start the debug dialog after this command return true; } private ArrayList<String> dbDoWatch(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Watching:"); if (!WatchedVars.isEmpty()) { int watchcount = WatchedVars.size(); for (int j = 0; j < watchcount; ++j) { Var wv = WatchedVars.get(j); String line = dbDoOneScalar(wv, prefix); if (line != null) { msg.add(line); } } } else { msg.add("\n" + "Undefined."); } return msg; } private ArrayList<String> dbDoFunc() { ArrayList<String> msg = new ArrayList<String>(); String msgs = ""; if (!FunctionStack.isEmpty()) { Stack<CallStackFrame> tempStack = (Stack<CallStackFrame>) FunctionStack.clone(); do { msgs = tempStack.pop().name() + msgs; } while (!tempStack.isEmpty()); } else { msgs += "MainProgram"; } msg.add("In Function: " + msgs); return msg; } private ArrayList<String> dbDoScalars(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Scalar Dump"); msg.add(" Local"); for (Var var : mSymbolTable.mVars) { String line = dbDoOneScalar(var, prefix); if (line != null) { msg.add(line); } } msg.add(" Global"); for (Var var : mGlobalSymbolTable.mVars) { String line = dbDoOneScalar(var, prefix); if (line != null) { msg.add(line); } } return msg; } private String dbDoOneScalar(Var var, String prefix) { if (var == null) { return (prefix + "Warning: null variable"); } String name = var.name(); if (name.length() == 0) { return (prefix + "Warning: zero-length variable name"); } boolean isScalar = !var.isArray() && !var.isFunction(); if (isScalar) { Var.Val val = var.val(); boolean isString = var.isString(); String line = prefix + name; line += " = " + (isString ? quote(val.sval()) : val.nval()); return line; } return null; } private ArrayList<String> dbDoArray(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Dumping Array " + WatchedArray.name() + "]"); Var.ArrayDef array = WatchedArray.arrayDef(); // get the array if (array == null) { msg.add(prefix + "Warning: null array table entry"); } else { int length = array.length(); // get the array length // ArrayList<Integer> dims = array.dimList(); // ArrayList<Integer> sizes = array.arraySizes(); // msg.add("dims: " + dims.toString()); // msg.add("sizes: " + sizes.toString()); boolean isString = WatchedArray.isString(); for (int i = 0; i < length; ++i) { msg.add(prefix + (isString ? quote(array.sval(i)) : array.nval(i))); } } return msg; } private ArrayList<String> dbDoList(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Dumping List " + WatchedList); if ((WatchedList < 0) || (WatchedList >= theLists.size())) { msg.add(prefix + "List has not been created."); return msg; } ArrayList list = theLists.get(WatchedList); // get the list if (list == null) { msg.add(prefix + "Warning: null list variable"); return msg; } int length = list.size(); if (length == 0) { msg.add(prefix + "Empty List"); } else { boolean isString = (theListsType.get(WatchedList) == Var.Type.STR); for (Object item : list) { // get each item String line; if (item == null) { line = "Warning: null list item"; } else { line = item.toString(); if (isString) { line = quote(line); } } msg.add(prefix + line); } } return msg; } private ArrayList<String> dbDoStack(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Dumping stack " + WatchedStack); if ((WatchedStack < 0) || (WatchedStack >= theStacks.size())) { msg.add(prefix + "Stack has not been created."); return msg; } Stack stack = theStacks.get(WatchedStack); // get the stack if (stack == null) { msg.add(prefix + "Warning: null list variable"); } else if (stack.isEmpty()) { msg.add(prefix + "Empty Stack"); } else { Stack tempStack = (Stack) stack.clone(); boolean isString = (theStacksType.get(WatchedStack) == Var.Type.STR); do { String line; Object item = tempStack.pop(); // get each item if (item == null) { line = "Warning: null stack item"; } else { line = item.toString(); if (isString) { line = quote(line); } } msg.add(prefix + line); } while (!tempStack.isEmpty()); } return msg; } private ArrayList<String> dbDoBundle(String prefix) { ArrayList<String> msg = new ArrayList<String>(); msg.add("Dumping Bundle " + WatchedBundle); if ((WatchedBundle < 0) || (WatchedBundle >= theBundles.size())) { msg.add(prefix + "Bundle has not been created."); return msg; } Bundle b = theBundles.get(WatchedBundle); // get the bundle if (b == null) { msg.add(prefix + "Warning: null bundle variable"); return msg; } Set<String> set = b.keySet(); if (set.size() == 0) { msg.add(prefix + "Empty Bundle"); return msg; } for (String s : set) { Object o = b.get(s); boolean isNumeric = o instanceof Double; msg.add(prefix + s + ": " + (isNumeric ? (Double) o : quote((String) o))); } return msg; } } // End of Interpreter } // End of Run