Skip to content

Commit

Permalink
Implement Tag type
Browse files Browse the repository at this point in the history
The tag type is used to add a label to any arbitrary object.
It is parsed as `:Label:object`, and renders the same, except on the
stack where it renders as `Label:object`.

Note that the HP48 renders a label with an extra space. The space is
apparently gone on the HP50.

Fixes: #21

Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
  • Loading branch information
c3d committed Sep 13, 2023
1 parent 3a4577d commit 5645c4f
Show file tree
Hide file tree
Showing 16 changed files with 411 additions and 11 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ CXX_SOURCES += \
src/loops.cc \
src/conditionals.cc \
src/font.cc \
src/tag.cc \
src/graphics.cc \
src/plot.cc \
fonts/HelpFont.cc \
Expand Down
27 changes: 22 additions & 5 deletions doc/commands/tags.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
# Tag objects
# Tagged objects

## MKTAG
Apply a tag to an object
Tagged objects are a way to indicate what a value represents, using a *tag*
between colons and preceding the object. For example, `:X:3` is a tagged
integer, where the tag is `X` and the object is `3`.

When displayed on the stack, tags are shown without the leading colon for
readability. For example, the object above shows as `X:3` on the stack.

## DTAG
Remove a tag from an object
## →Tag (ToTag)

Apply a tag to an object. The tag is in level 1 as text or name. The object to
be tagged is in level 2. For example, `"Hello" 1 →Tag` results in `:Hello:1`.
Like on the HP calculators, it is possible to next tags.

## Tag→ (FromTag)

Expand a tagged object in level 1 into its object and tag. The object will be in
level 2, the tag will be in level 1 as a text object.

For example, `:Hello:1 Tag→` results in `"Hello"` in level 1 and `1` in level 2.

## DeleteTag (DTAG)

Remove a tag from an object. For example, `:Hello:1 DeleteTag` results in `1`.
If there is no tag, the object is returned as is.
2 changes: 1 addition & 1 deletion fonts/EditorFont.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extern const unsigned char EditorFont_sparse_font_data[];
const unsigned char EditorFont_sparse_font_data[73676] FONT_QSPI =
{

0xB0, 0x02, 0xC7, 0xBF, 0x04, 0x36, 0x00, 0x01, 0x00, 0x2C, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01,
0xB3, 0x02, 0xC7, 0xBF, 0x04, 0x36, 0x00, 0x01, 0x00, 0x2C, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01,
0x00, 0x2C, 0x01, 0x01, 0x08, 0x00, 0x20, 0x5F, 0x00, 0x2C, 0x01, 0x01, 0x08, 0x00, 0x02, 0x0B,
0x05, 0x21, 0x09, 0xEF, 0xBD, 0xF7, 0xDE, 0x7B, 0xEF, 0xBD, 0xF7, 0xDE, 0x7B, 0xEF, 0xBD, 0xF7,
0x1E, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x0E, 0x03, 0x0B, 0x0B, 0x0F, 0x11, 0x8F, 0x7F, 0xFC,
Expand Down
2 changes: 1 addition & 1 deletion fonts/HelpFont.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extern const unsigned char HelpFont_sparse_font_data[];
const unsigned char HelpFont_sparse_font_data[15010] FONT_QSPI =
{

0xB0, 0x02, 0x9E, 0x75, 0x14, 0x00, 0x01, 0x00, 0x11, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0xB3, 0x02, 0x9E, 0x75, 0x14, 0x00, 0x01, 0x00, 0x11, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01, 0x00,
0x11, 0x01, 0x01, 0x03, 0x00, 0x20, 0x5F, 0x00, 0x11, 0x01, 0x01, 0x03, 0x00, 0x01, 0x04, 0x02,
0x0D, 0x04, 0xFF, 0xFF, 0xF3, 0x03, 0x01, 0x04, 0x04, 0x05, 0x06, 0xFF, 0xFF, 0x0F, 0x01, 0x04,
0x08, 0x0D, 0x0A, 0x12, 0x12, 0x14, 0x7F, 0x7F, 0x24, 0x24, 0x24, 0xFE, 0xFE, 0x28, 0x48, 0x48,
Expand Down
2 changes: 1 addition & 1 deletion fonts/StackFont.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extern const unsigned char StackFont_sparse_font_data[];
const unsigned char StackFont_sparse_font_data[36409] FONT_QSPI =
{

0xB0, 0x02, 0xB4, 0x9C, 0x02, 0x24, 0x00, 0x01, 0x00, 0x1C, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01,
0xB3, 0x02, 0xB4, 0x9C, 0x02, 0x24, 0x00, 0x01, 0x00, 0x1C, 0x01, 0x01, 0x00, 0x00, 0x0D, 0x01,
0x00, 0x1C, 0x01, 0x01, 0x06, 0x00, 0x20, 0x5F, 0x00, 0x1C, 0x01, 0x01, 0x06, 0x00, 0x02, 0x05,
0x03, 0x17, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xC0, 0xFF, 0x1F, 0x02, 0x05, 0x08, 0x0A,
0x0C, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0x01, 0x05, 0x0E, 0x17, 0x10,
Expand Down
1 change: 1 addition & 0 deletions sim/simulator.pro
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ SOURCES += \
../fonts/HelpFont.cc \
../fonts/StackFont.cc \
../src/font.cc \
../src/tag.cc \
../src/graphics.cc \
../src/plot.cc \
../src/tests.cc
Expand Down
13 changes: 11 additions & 2 deletions src/arithmetic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "list.h"
#include "runtime.h"
#include "settings.h"
#include "tag.h"
#include "text.h"

#include <bit>
Expand Down Expand Up @@ -1169,13 +1170,21 @@ object::result arithmetic::evaluate(id op, ops_t ops)
// ----------------------------------------------------------------------------
{
// Fetch arguments from the stack
algebraic_g y = (algebraic_p) rt.stack(1);
// Possibly wrong type, i.e. it migth not be an algebraic on the stack,
// but since we tend to do extensive type checking later, don't overdo it
algebraic_g y = algebraic_p(rt.stack(1));
if (!y)
return ERROR;
algebraic_g x = (algebraic_p) rt.stack(0);
algebraic_g x = algebraic_p(rt.stack(0));
if (!x)
return ERROR;

// Strip tags
while (tag_p xtag = x->as<tag>())
x = algebraic_p(xtag->tagged_object());
while (tag_p ytag = y->as<tag>())
y = algebraic_p(ytag->tagged_object());

// Evaluate the operation
algebraic_g r = evaluate(op, y, x, ops);

Expand Down
2 changes: 2 additions & 0 deletions src/command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ COMMAND_BODY(Help)
case ID_equation: topic = utf8("Equations"); break;
case ID_list: topic = utf8("Lists"); break;
case ID_array: topic = utf8("Vectors and matrices"); break;
case ID_tag: topic = utf8("Tagged objects"); break;
default: topic = fancy(top->type()); break;
}
}
Expand Down Expand Up @@ -601,6 +602,7 @@ COMMAND_BODY(ToolsMenu)
case ID_equation: menu = ID_SymbolicMenu; break;
case ID_list: menu = ID_ListMenu; break;
case ID_array: menu = ID_MatrixMenu; break;
case ID_tag: menu = ID_ObjectMenu; break;
default: break;
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/functions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "fraction.h"
#include "integer.h"
#include "list.h"
#include "tag.h"


bool function::should_be_symbolic(id type)
Expand Down Expand Up @@ -319,6 +320,11 @@ object::result function::evaluate(algebraic_fn op, bool mat)
if (object_p top = rt.top())
{
id topty = top->type();
while(topty == ID_tag)
{
top = tag_p(top)->tagged_object();
topty = top->type();
}
if (topty == ID_list || (topty == ID_array && !mat))
{
top = list_p(top)->map(op);
Expand Down
5 changes: 5 additions & 0 deletions src/ids.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ NAMED(ClLCD, "ClearLCD")
NAMED(Draw, "DrawPlot")
NAMED(Drax, "DrawAxes")

NAMED(ToTag, "→Tag")
NAMED(FromTag, "Tag→")
NAMED(dtag, "DeleteTag")


// ============================================================================
//
Expand Down Expand Up @@ -540,6 +544,7 @@ ID(dense_font)
ID(sparse_font)
ID(dmcp_font)

ID(tag)


#undef ID
Expand Down
4 changes: 4 additions & 0 deletions src/menu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,10 @@ MENU(ObjectMenu,
"→Array", ID_Unimplemented,
"→Num", ID_ToDecimal,
"→Frac", ID_ToFraction,

"→Tag", ID_ToTag,
"Tag→", ID_FromTag,
"DTag", ID_dtag,
"→Graph", ID_Unimplemented);


Expand Down
1 change: 1 addition & 0 deletions src/object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "settings.h"
#include "stack-cmds.h"
#include "symbol.h"
#include "tag.h"
#include "text.h"
#include "user_interface.h"
#include "variables.h"
Expand Down
189 changes: 189 additions & 0 deletions src/tag.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// ****************************************************************************
// tag.cc DB48X project
// ****************************************************************************
//
// File Description:
//
// Tag type
//
// The tag type is used to tag objects
// It otherwise evaluates and behaves like the tagged object
//
//
//
//
//
// ****************************************************************************
// (C) 2023 Christophe de Dinechin <christophe@dinechin.org>
// This software is licensed under the terms outlined in LICENSE.txt
// ****************************************************************************
// This file is part of DB48X.
//
// DB48X is free software: you can redistribute it and/or modify
// it under the terms outlined in the LICENSE.txt file
//
// DB48X 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.
// ****************************************************************************

#include "tag.h"
#include "parser.h"
#include "renderer.h"


SIZE_BODY(tag)
// ----------------------------------------------------------------------------
// Compute the size of a tag object
// ----------------------------------------------------------------------------
{
byte_p p = o->payload();
size_t sz = leb128<size_t>(p);
p += sz;
sz = object_p(p)->size();
p += sz;
return ptrdiff(p, o);
}


PARSE_BODY(tag)
// ----------------------------------------------------------------------------
// Try to parse this as a tag, i.e. :LABEL:Object
// ----------------------------------------------------------------------------
{
utf8 source = p.source;
utf8 s = source;
if (*s++ != ':')
return SKIP;

utf8 end = source + p.length;
while (s < end && *s != ':')
s++;

if (*s != ':')
{
rt.unterminated_error().source(p.source);
return ERROR;
}
s++;

size_t parsed = s - source;
size_t llen = parsed - 2;
gcutf8 lbl = source + 1;

size_t remaining = p.length - parsed;
object_g obj = object::parse(s, remaining);
if (!obj)
{
rt.unterminated_error();
return ERROR;
}

p.end = parsed + remaining;
p.out = rt.make<tag>(ID_tag, lbl, llen, obj);

return p.out ? OK : ERROR;
}


RENDER_BODY(tag)
// ----------------------------------------------------------------------------
// Render the tag into the given text buffer
// ----------------------------------------------------------------------------
// When rendering on the stack, we render as "LABEL:object"
// Otherwise, we render as ":LABEL:object"
{
size_t llen = 0;
utf8 ltxt = o->label_value(&llen);
if (!r.stack())
r.put(':');
r.put(ltxt, llen);
r.put(':');

object_p obj = o->tagged_object();
obj->render(r);

return r.size();
}


EXEC_BODY(tag)
// ----------------------------------------------------------------------------
// Execution of a tagged object executes the associated object
// ----------------------------------------------------------------------------
{
object_p obj = o->tagged_object();
return obj->execute();
}


COMMAND_BODY(dtag)
// ----------------------------------------------------------------------------
// Remove the tag from an object
// ----------------------------------------------------------------------------
{
if (object_p obj = rt.top())
{
if (tag_p tobj = obj->as<tag>())
{
do
{
obj = tobj->tagged_object();
} while ((tobj = obj->as<tag>()));
if (!rt.top(obj))
return ERROR;
}
return OK;
}
return ERROR;
}


COMMAND_BODY(ToTag)
// ----------------------------------------------------------------------------
// Build a tag object from a name and object
// ----------------------------------------------------------------------------
{
if (object_g x = rt.stack(0))
{
if (object_g y = rt.stack(1))
{
while (tag_p tagged = x->as<tag>())
x = tagged->tagged_object();

if (text_g label = x->as_text())
{
size_t lsz = 0;
utf8 ltxt = label->value(&lsz);
tag_g tagged = tag::make(ltxt, lsz, y);
if (tagged && rt.drop() && rt.top(tagged))
return OK;
}
}
}
return ERROR;
}


COMMAND_BODY(FromTag)
// ----------------------------------------------------------------------------
// Expand a tagged object into its value and tag
// ----------------------------------------------------------------------------
{
if (object_p x = rt.top())
{
if (tag_g tagged = x->as<tag>())
{
if (rt.top(tagged->tagged_object()))
{
size_t lsz = 0;
utf8 ltxt = tagged->label_value(&lsz);
text_p label = text::make(ltxt, lsz);
if (label && rt.push(label))
return OK;
}
}
}

return ERROR;
}
Loading

0 comments on commit 5645c4f

Please sign in to comment.