diff --git a/src/inc/cmdsys.plh b/src/inc/cmdsys.plh
index d48c91b..235ae86 100644
--- a/src/inc/cmdsys.plh
+++ b/src/inc/cmdsys.plh
@@ -36,7 +36,7 @@ import cmdsys
     //
     // CMD exported functions
     //
-    predef putc(c)#0, putln()#0, puts(s)#0, puti(i)#0, getc()#1, gets(p)#1
+    predef putc(c)#0, putln()#0, puts(s)#0, puti(i)#0, getc()#1, gets(p)#1, toupper(c)#1
     predef call(addr,areg,xreg,yreg,status)#1, syscall(cmd,params)#1
     predef heapmark()#1, heapallocalign(size, pow2, freeaddr), heapalloc(size)#1, heaprelease(newheap)#1, heapavail()#1
     predef memset(addr,value,size)#0, memcpy(dst,src,size)#0
diff --git a/src/libsrc/sane.pla b/src/libsrc/sane.pla
index 7a4166c..bcb04a0 100644
--- a/src/libsrc/sane.pla
+++ b/src/libsrc/sane.pla
@@ -753,19 +753,19 @@ end
 //
 def elemsLoad1(op, dst)#1
     if loadElems
-        return sane[3](op, dst)
+        return sane[6](op, dst)
     fin
     return -1
 end
 def elemsLoad2(op, dst, src)#1
     if loadElems
-        return sane[4](op, dst, src)
+        return sane[7](op, dst, src)
     fin
     return -1
 end
 def elemsLoad3(op, dst, src, src2)#1
     if loadElems
-        return sane[4](op, dst, src, src2)
+        return sane[8](op, dst, src, src2)
     fin
     return -1
 end
diff --git a/src/samplesrc/sanity.pla b/src/samplesrc/sanity.pla
index ba3a28e..dca0621 100644
--- a/src/samplesrc/sanity.pla
+++ b/src/samplesrc/sanity.pla
@@ -3,14 +3,173 @@
 //
 include "inc/cmdsys.plh"
 include "inc/sane.plh"
+struc t_decrecord
+    word sgn
+    word exp
+    byte sig[29]
+end
+struc t_decformat
+    word style
+    word digits
+end
 //
 // Test values
 //
 word iA, iB, iC, zero, fpEnv
 byte xT[t_extended]
-word decrec = 0, 0
-byte strA[28] = "10"
-word decform = 0, 6
+byte strNum = "-100.25"
+byte strA[16]
+//
+// Parse string into decrecord
+//
+def str2ext(str, ext)
+    byte i, s, d
+    byte decrec[t_decrecord]
+    word sgnadj, expadj
+
+    decrec:sgn  = 0
+    decrec:exp  = 0
+    decrec.sig  = 0
+    s           = 1
+    i           = 1
+    //
+    // Skip whitespace
+    //
+    while ^(str+i) <= ' ' and i <= ^str; i++; loop
+    //
+    // Check for sign
+    //
+    if ^(str+i) == '-'
+        decrec:sgn = 1
+        i++
+    elsif ^(str+i) == '+'
+        i++
+    fin
+    //
+    // Skip leading zeros
+    //
+    while i <= ^str and ^(str+i) == '0'
+        i++
+    loop
+    //
+    // Parse number
+    //
+    while i <= ^str
+        d = toupper(^(str+i))
+        if d >= '0' and d <= '9'
+            //
+            // Parse digit
+            //
+            decrec.sig[s] = ^(str+i)
+            decrec:sig++
+            s++
+        elsif d == '.'
+            //
+            // Parse decimal point
+            //
+            i++
+            while i <= ^str
+                d = toupper(^(str+i))
+                if d >= '0' and d <= '9'
+                    decrec.sig[s] = ^(str+i)
+                    decrec.sig++
+                    decrec:exp--
+                    s++
+                elsif d == 'E'
+                    i--
+                    break
+                else
+                    i = ^str
+                fin
+                i++
+            loop
+        elsif d == 'E'
+            //
+            // Parse exponent
+            //
+            i++
+            expadj = 0
+            sgnadj = 1
+            if ^(str+i) == '-'
+                sgnadj = -1
+                i++
+            elsif ^(str+i) == '+'
+                i++
+            fin
+            while i <= ^str
+                d = ^(str+i)
+                if d >= '0' and d <= '9'
+                    expadj = expadj * 10 + (d - '0')
+                else
+                    i = ^str
+                fin
+            loop
+            decrec:exp = decrec:exp + (sgnadj * expadj)
+        else
+            i = ^str
+        fin
+        i++
+    loop
+    //
+    // Strip leading zeros from sig
+    //
+    while decrec.sig > 1 and decrec.sig.1 == '0'
+        decrec.sig--
+        if decrec:exp < 0
+            decrec:exp--
+        fin
+        memcpy(@decrec.sig.1, @decrec.sig.2, decrec.sig)
+    loop
+    //
+    // Check for zero
+    //
+    if !decrec.sig
+        decrec.sig = 1
+        decrec.sig.1 = '0'
+    fin
+    //putc(decrec.sgn ?? '-' :: '+'); puts(@decrec.sig); putc('e'); puti(decrec:exp); putln
+    return sane:fpOp2(FFEXT|FOD2B, ext, @decrec)
+end
+def ext2str(ext, str)
+    byte d, i, dp
+    byte decform[t_decformat]
+    byte decrec[t_decrecord]
+
+    decform:style  = 1
+    decform:digits = 4
+    sane:fpOp3(FFEXT|FOB2D, @decrec, ext, @decform)
+    //putc(decrec.sgn ?? '-' :: '+'); puts(@decrec.sig); putc('e'); puti(decrec:exp); putln
+    i = 0
+    if decrec.sgn
+        ^(str+1) = '-'
+        i = 1
+    fin
+    dp = decrec.sig + decrec:exp
+    for d = 1 to decrec.sig
+        i++
+        ^(str+i) = decrec.sig[d]
+        if d == dp
+            i++
+            ^(str+i) = '.'
+        fin
+    next
+    ^str = i
+    return ^str
+end
+def divstri(strNum, denom)#0
+    byte strResult[16]
+    byte xResult[t_extended]
+
+    //
+    // Convert string to and from SANE
+    //
+    sane:zpSave()
+    str2ext(strNum, @xResult)
+    sane:fpOp2(FFINT|FODIV, @xResult, @denom) // Div int denom into ext Result
+    ext2str(@xResult, @strResult)
+    sane:zpRestore()
+    puts(strNum); putc('/'); puti(denom); putc('='); puts(@strResult); putln
+end
 //
 // My custom SANE exception handler
 //
@@ -27,6 +186,7 @@ def myException(pstatus)
     return pstatus=>4
 end
 
+
 iA = 3
 iB = 4
 iC = -1
@@ -73,14 +233,8 @@ sane:fpOp2(FFINT|FOX2Z, @iC, @xT) // Convert ext T to int C
 sane:zpRestore()
 puti(iA); putc('/'); puti(zero); putc('='); puti(iC); putc('\n')
 //
-// Convert string to and from SANE
+// String conversion tests
 //
-sane:zpSave()
-sane:fpOp2(FFEXT|FOD2B, @xT, @decrec)
-//sane:fpOp2(FFINT|FOZ2X, @xT, @iA) // Convert int A to ext T
-sane:fpOp2(FFINT|FODIV, @xT, @iB) // Div int B into ext T
-sane:fpOp2(FFINT|FOX2Z, @iC, @xT) // Convert ext T to int C
-sane:fpOp3(FFEXT|FOB2D, @decrec, @xT, @decform)
-sane:zpRestore()
-puts("10 / 4 = "); puts(@strA); putc('e'); puti(decrec:2); putc('('); puti(iC); putc(')'); putln
+divstri("-100.5", 4)
+divstri("00.5", 2)
 done
diff --git a/src/samplesrc/test.pla b/src/samplesrc/test.pla
index 580349f..55a2da0 100755
--- a/src/samplesrc/test.pla
+++ b/src/samplesrc/test.pla
@@ -127,6 +127,10 @@ def dummy(zz)#0
   return 0
 end
 
+def value1
+    return 1
+end
+
 puti(array[0]);putc(' ')
 puti(array[1]);putc(' ')
 puti(array[2]);putc(' ')
@@ -161,4 +165,7 @@ putln
 puts(@constr); puti(constval); putln
 puts("Signed byte constant:"); puti(-3); putln
 puts("Hello from in-line string!\$7F\n")
+puts(1 ?? "This is TRUE\n" :: "This is FALSE\n")
+puts(0 ?? "This is TRUE\n" :: "This is FALSE\n")
+ptr = 0
 done
diff --git a/src/toolsrc/codegen.c b/src/toolsrc/codegen.c
index bc53903..a2e3888 100755
--- a/src/toolsrc/codegen.c
+++ b/src/toolsrc/codegen.c
@@ -1665,6 +1665,11 @@ int emit_pending_seq()
             case BRTRUE_CODE:
                 emit_brtru(op->tag);
                 break;
+            case CODETAG_CODE:
+                printf("_B%03d%c\n", op->tag, LBL);
+                break;
+            case NOP_CODE:
+                break;
             default:
                 return (0);
         }
diff --git a/src/toolsrc/codegen.h b/src/toolsrc/codegen.h
index e7c1247..49cc303 100755
--- a/src/toolsrc/codegen.h
+++ b/src/toolsrc/codegen.h
@@ -64,6 +64,8 @@ typedef struct _opseq {
 #define BRNCH_CODE  0x031C
 #define BRFALSE_CODE 0x031D
 #define BRTRUE_CODE 0x031E
+#define CODETAG_CODE 0x031F
+#define NOP_CODE    0x0320
 
 #define gen_uop(seq,op)     gen_seq(seq,UNARY_CODE(op),0,0,0,0)
 #define gen_op(seq,op)      gen_seq(seq,BINARY_CODE(op),0,0,0,0)
@@ -83,6 +85,9 @@ typedef struct _opseq {
 #define gen_drop(seq)       gen_seq(seq,DROP_CODE,0,0,0,0)
 #define gen_brfls(seq,tag)  gen_seq(seq,BRFALSE_CODE,0,tag,0,0)
 #define gen_brtru(seq,tag)  gen_seq(seq,BRTRUE_CODE,0,tag,0,0)
+#define gen_brnch(seq,tag)  gen_seq(seq,BRNCH_CODE,0,tag,0,0)
+#define gen_codetag(seq,tag) gen_seq(seq, CODETAG_CODE,0,tag,0,0)
+#define gen_nop(seq)        gen_seq(seq,NOP_CODE,0,0,0,0)
 
 void emit_flags(int flags);
 void emit_header(void);
diff --git a/src/toolsrc/lex.c b/src/toolsrc/lex.c
index 0c7fe3f..985f454 100755
--- a/src/toolsrc/lex.c
+++ b/src/toolsrc/lex.c
@@ -388,6 +388,30 @@ t_token scan(void)
                     scanpos++;
                 }
                 break;
+            case ':':
+                if (scanpos[1] == ':')
+                {
+                    scantoken = TRIELSE_TOKEN;
+                    scanpos += 2;
+                }
+                else
+                {
+                    scantoken = COLON_TOKEN;
+                    scanpos++;
+                }
+                break;
+            case '?':
+                if (scanpos[1] == '?')
+                {
+                    scantoken = TERNARY_TOKEN;
+                    scanpos += 2;
+                }
+                else
+                {
+                    scantoken = TERNARY_TOKEN;
+                    scanpos++;
+                }
+                break;
             default:
                 /*
                  * Simple single character tokens.
diff --git a/src/toolsrc/parse.c b/src/toolsrc/parse.c
index 94b1402..4f439b9 100755
--- a/src/toolsrc/parse.c
+++ b/src/toolsrc/parse.c
@@ -746,6 +746,32 @@ t_opseq *parse_expr(t_opseq *codeseq, int *stackdepth)
         if (stackdepth)
             (*stackdepth)--;
     }
+    /*
+     * Look for ternary operator
+     */
+    if (scantoken == TERNARY_TOKEN)
+    {
+        int tag_else, tag_endtri;
+        int stackdepth1;
+
+        if (*stackdepth != 1)
+            parse_error("Ternary op must evaluate to single value");
+        tag_else   = tag_new(BRANCH_TYPE);
+        tag_endtri = tag_new(BRANCH_TYPE);
+        codeseq    = gen_brfls(codeseq, tag_else);
+        codeseq    = parse_expr(codeseq, &stackdepth1);
+        if (scantoken != TRIELSE_TOKEN)
+        {
+            parse_error("Missing '::' in ternary op");
+            return (NULL);
+        }
+        codeseq = gen_brnch(codeseq, tag_endtri);
+        codeseq = gen_codetag(codeseq, tag_else);
+        codeseq = parse_expr(codeseq, stackdepth);
+        if (stackdepth1 != *stackdepth)
+            parse_error("Inconsistent value counts in ternary op");
+        codeseq = gen_codetag(codeseq, tag_endtri);
+    }
     return (codeseq);
 }
 t_opseq *parse_set(t_opseq *codeseq)
diff --git a/src/toolsrc/tokens.h b/src/toolsrc/tokens.h
index dd629b7..dfe1421 100755
--- a/src/toolsrc/tokens.h
+++ b/src/toolsrc/tokens.h
@@ -44,6 +44,11 @@
 #define STRUC_TOKEN         TOKEN(31)
 #define CONTINUE_TOKEN      TOKEN(32)
 //#define EVAL_TOKEN          TOKEN(32)
+/*
+ * Ternary operand operators.
+ */
+#define TERNARY_TOKEN       TOKEN('?')
+#define TRIELSE_TOKEN       TOKEN('_')
 /*
  * Double operand operators.
  */
diff --git a/src/vmsrc/a1cmd.pla b/src/vmsrc/a1cmd.pla
index ec21df9..354ad9e 100755
--- a/src/vmsrc/a1cmd.pla
+++ b/src/vmsrc/a1cmd.pla
@@ -31,7 +31,7 @@ const CFFAEntryPtr = $0B
 // Pedefined functions.
 //
 predef syscall(cmd)#1, call(addr,areg,xreg,yreg,status)#1
-predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1
+predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1, toupper(c)#1
 predef markheap()#1, allocheap(size)#1, allocalignheap(size, pow2, freeaddr), releaseheap(newheap)#1, availheap()#1
 predef memset(addr,value,size)#0, memcpy(dst,src,size)#0
 predef uword_isgt(a,b)#1, uword_isge(a,b)#1, uword_islt(a,b)#1, uword_isle(a,b)#1
@@ -74,6 +74,7 @@ byte putsstr[]    = "PUTS"
 byte putistr[]    = "PUTI"
 byte getcstr[]    = "GETC"
 byte getsstr[]    = "GETS"
+byte toupstr[]    = "TOUPPER"
 byte sysstr[]     = "SYSCALL"
 byte callstr[]    = "CALL"
 byte hpmarkstr[]  = "HEAPMARK"
@@ -99,6 +100,7 @@ word              = @putsstr,   @prstr
 word              = @putistr,   @print
 word              = @getcstr,   @cin
 word              = @getsstr,   @rdstr
+word              = @toupstr,   @toupper
 word              = @hpmarkstr, @markheap
 word              = @hpallocstr,@allocheap
 word              = @hpalignstr,@allocalignheap
diff --git a/src/vmsrc/cmd.pla b/src/vmsrc/cmd.pla
index b8f1995..555ac3d 100755
--- a/src/vmsrc/cmd.pla
+++ b/src/vmsrc/cmd.pla
@@ -25,7 +25,7 @@ const modinitkeep = $4000
 // Pedefined functions.
 //
 predef syscall(cmd,params)#1, call(addr,areg,xreg,yreg,status)#1
-predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1
+predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1, toupper(c)#1
 predef markheap()#1, allocheap(size)#1, allocalignheap(size, pow2, freeaddr)#1, releaseheap(newheap)#1, availheap()#1
 predef memset(addr,value,size)#0, memcpy(dst,src,size)#0
 predef uword_isgt(a,b)#1, uword_isge(a,b)#1, uword_islt(a,b)#1, uword_isle(a,b)#1
@@ -53,6 +53,7 @@ byte putsstr    = "PUTS"
 byte putistr    = "PUTI"
 byte getcstr    = "GETC"
 byte getsstr    = "GETS"
+byte toupstr    = "TOUPPER"
 byte hpmarkstr  = "HEAPMARK"
 byte hpalignstr = "HEAPALLOCALIGN"
 byte hpallocstr = "HEAPALLOC"
@@ -78,6 +79,7 @@ word            = @putsstr,   @prstr
 word            = @putistr,   @print
 word            = @getcstr,   @cin
 word            = @getsstr,   @rdstr
+word            = @toupstr,   @toupper
 word            = @hpmarkstr, @markheap
 word            = @hpallocstr,@allocheap
 word            = @hpalignstr,@allocalignheap
diff --git a/src/vmsrc/soscmd.pla b/src/vmsrc/soscmd.pla
index 79fb66c..87802a0 100755
--- a/src/vmsrc/soscmd.pla
+++ b/src/vmsrc/soscmd.pla
@@ -26,7 +26,7 @@ const O_READ_WRITE = 3
 // Pedefined functions.
 //
 predef syscall(cmd,params)#1, call(addr,areg,xreg,yreg,status)#1
-predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1
+predef crout()#0, cout(c)#0, prstr(s)#0, print(i)#0, cin()#1, rdstr(p)#1, toupper(c)#1
 predef markheap()#1, allocheap(size)#1, allocalignheap(size, pow2, freeaddr), releaseheap(newheap)#1, availheap()#1
 predef memset(addr,value,size)#0, memcpy(dst,src,size)#0
 predef uword_isgt(a,b)#1, uword_isge(a,b)#1, uword_islt(a,b)#1, uword_isle(a,b)#1
@@ -71,6 +71,7 @@ byte putsstr[]    = "PUTS"
 byte putistr[]    = "PUTI"
 byte getcstr[]    = "GETC"
 byte getsstr[]    = "GETS"
+byte toupstr[]    = "TOUPPER"
 byte hpmarkstr[]  = "HEAPMARK"
 byte hpalignstr[] = "HEAPALLOCALIGN"
 byte hpallocstr[] = "HEAPALLOC"
@@ -95,6 +96,7 @@ word              = @putsstr,   @prstr
 word              = @putistr,   @print
 word              = @getcstr,   @cin
 word              = @getsstr,   @rdstr
+word              = @toupstr,   @toupper
 word              = @hpmarkstr, @markheap
 word              = @hpallocstr,@allocheap
 word              = @hpalignstr,@allocalignheap