Phasor 3.3.0
Stack VM based Programming Language
Loading...
Searching...
No Matches
Value.hpp
Go to the documentation of this file.
1// Copyright 2026 Daniel McGuire
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5// http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software
7// distributed under the License is distributed on an "AS IS" BASIS,
8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9// See the License for the specific language governing permissions and
10// limitations under the License.
11
12// README
13//
14// Provides types for the Phasor (and Pulsar) Programming Language.
15// Wraps a std::variant over null, bool, int64_t, double, string, struct, and array,
16// with structs, arrays, and strings heap-allocated via std::shared_ptr. Provides arithmetic,
17// comparison, and logical operators, and isTruthy() and toString().
18//
19// Also includes a std::formatter<Phasor::Value> implementation for use with std::format (or std::print).
20// Supports four format specifiers: default (value as-is), t (type name only),
21// T (type and value), ? (debug repr with quoted strings and recursive expansion), and
22// q (quoted strings, default otherwise).
23
24#pragma once
25#include <iostream>
26#include <string>
27#include <variant>
28#include <unordered_map>
29#include <memory>
30#include <vector>
31#include <format>
32#include <string>
33
35namespace Phasor
36{
37
51
57class Value
58{
59 public:
61 {
62 std::string structName;
63 std::unordered_map<std::string, Value> fields;
64 };
65 using ArrayInstance = std::vector<Value>;
66
67 private:
68 using DataType = std::variant<std::monostate, bool, int64_t, double, std::shared_ptr<std::string>,
69 std::shared_ptr<StructInstance>,
70 std::shared_ptr<ArrayInstance>>;
71
73
74 public:
76 Value() : data(std::monostate{})
77 {
78 }
79
80 Value(bool b) : data(b)
81 {
82 }
83
84 Value(int64_t i) : data(i)
85 {
86 }
87
88 Value(int i) : data(static_cast<int64_t>(i))
89 {
90 }
91
92 Value(double d) : data(d)
93 {
94 }
95
96 Value(const std::string &s) : data(std::make_shared<std::string>(s))
97 {
98 }
99
100 Value(const char *s) : data(std::make_shared<std::string>(s))
101 {
102 }
103
104 Value(std::shared_ptr<StructInstance> s) : data(std::move(s))
105 {
106 }
107
108 Value(std::shared_ptr<ArrayInstance> a) : data(std::move(a))
109 {
110 }
111
113 [[nodiscard]] ValueType getType() const noexcept {
114 return static_cast<ValueType>(data.index());
115 }
116
117 static Value typeToString(const ValueType &type)
118 {
119 switch (type)
120 {
121 case ValueType::Null:
122 return {"null"};
123 case ValueType::Bool:
124 return {"bool"};
125 case ValueType::Int:
126 return {"int"};
127 case ValueType::Float:
128 return {"float"};
130 return {"string"};
132 return {"struct"};
133 case ValueType::Array:
134 return {"array"};
135 default:
136 return {"unknown"};
137 }
138 }
139
141 [[nodiscard]] bool isNull() const noexcept { return data.index() == 0; }
142 [[nodiscard]] bool isBool() const noexcept { return data.index() == 1; }
143 [[nodiscard]] bool isInt() const noexcept { return data.index() == 2; }
144 [[nodiscard]] bool isFloat() const noexcept { return data.index() == 3; }
145 [[nodiscard]] bool isString() const noexcept { return data.index() == 4; }
146 [[nodiscard]] bool isNumber() const noexcept { return data.index() == 2 || data.index() == 3; }
148 [[nodiscard]] bool isArray() const noexcept
149 {
150 return std::holds_alternative<std::shared_ptr<ArrayInstance>>(data);
151 }
152
154 [[nodiscard]] bool asBool() const noexcept
155 {
156 return std::get<bool>(data);
157 }
158
159 [[nodiscard]] int64_t asInt() const noexcept
160 {
161 if (isInt())
162 {
163 return std::get<int64_t>(data);
164 }
165 if (isFloat())
166 {
167 return static_cast<int64_t>(std::get<double>(data));
168 }
169 return 0;
170 }
171
172 [[nodiscard]] double asFloat() const noexcept
173 {
174 if (isFloat())
175 {
176 return std::get<double>(data);
177 }
178 if (isInt())
179 {
180 return static_cast<double>(std::get<int64_t>(data));
181 }
182 return 0.0;
183 }
184
185 [[nodiscard]] std::string asString() const noexcept
186 {
187 if (isString())
188 {
189 return *std::get<std::shared_ptr<std::string>>(data);
190 }
191 return toString();
192 }
193
194 std::shared_ptr<ArrayInstance> asArray()
195 {
196 return std::get<std::shared_ptr<ArrayInstance>>(data);
197 }
198
200 [[nodiscard]] std::shared_ptr<const ArrayInstance> asArray() const noexcept
201 {
202 return std::get<std::shared_ptr<ArrayInstance>>(data);
203 }
204
206 Value operator+(const Value &other) const
207 {
208 if (isInt() && other.isInt())
209 {
210 return {asInt() + other.asInt()};
211 }
212 if (isNumber() && other.isNumber())
213 {
214 return {asFloat() + other.asFloat()};
215 }
216 if (isString() && other.isString())
217 {
218 return Value(asString() + other.asString());
219 }
220 throw std::runtime_error("Cannot add these value types");
221 }
222
224 Value operator-(const Value &other) const
225 {
226 if (isInt() && other.isInt())
227 {
228 return {asInt() - other.asInt()};
229 }
230 if (isNumber() && other.isNumber())
231 {
232 return {asFloat() - other.asFloat()};
233 }
234 throw std::runtime_error("Cannot subtract these value types");
235 }
236
238 {
239 if (isInt())
240 {
241 data = asInt() - 1;
242 return *this;
243 }
244 if (isFloat())
245 {
246 data = asFloat() - 1.0;
247 return *this;
248 }
249 throw std::runtime_error("Cannot decrement this value type");
250 }
251
253 {
254 if (isInt())
255 {
256 data = asInt() + 1;
257 return *this;
258 }
259 if (isFloat())
260 {
261 data = asFloat() + 1.0;
262 return *this;
263 }
264 throw std::runtime_error("Cannot increment this value type");
265 }
266
268 Value operator*(const Value &other) const
269 {
270 if (isInt() && other.isInt())
271 {
272 return {asInt() * other.asInt()};
273 }
274 if (isNumber() && other.isNumber())
275 {
276 return {asFloat() * other.asFloat()};
277 }
278 throw std::runtime_error("Cannot multiply these value types");
279 }
280
282 Value operator/(const Value &other) const
283 {
284 if (isInt() && other.isInt())
285 {
286 if (other.asInt() == 0)
287 {
288 throw std::runtime_error("Division by zero");
289 }
290 return {asInt() / other.asInt()};
291 }
292 if (isNumber() && other.isNumber())
293 {
294 if (other.asFloat() == 0.0)
295 {
296 throw std::runtime_error("Division by zero");
297 }
298 return {asFloat() / other.asFloat()};
299 }
300 throw std::runtime_error("Cannot divide these value types");
301 }
302
304 Value operator%(const Value &other) const
305 {
306 if (isInt() && other.isInt())
307 {
308 if (other.asInt() == 0)
309 {
310 throw std::runtime_error("Modulo by zero");
311 }
312 return {asInt() % other.asInt()};
313 }
314 throw std::runtime_error("Modulo requires integer operands");
315 }
316
318 Value operator!() const noexcept
319 {
320 return {!isTruthy()};
321 }
322
324 [[nodiscard]] Value logicalAnd(const Value &other) const noexcept
325 {
326 return {isTruthy() && other.isTruthy()};
327 }
328
330 [[nodiscard]] Value logicalOr(const Value &other) const noexcept
331 {
332 return {isTruthy() || other.isTruthy()};
333 }
334
336 [[nodiscard]] bool isTruthy() const noexcept
337 {
338 if (isNull())
339 {
340 return false;
341 }
342 if (isBool())
343 {
344 return asBool();
345 }
346 if (isInt())
347 {
348 return asInt() != 0;
349 }
350 if (isFloat())
351 {
352 return asFloat() != 0.0;
353 }
354 if (isString())
355 {
356 return !asString().empty();
357 }
358 return false;
359 }
360
362 bool operator==(const Value &other) const noexcept
363 {
364 if (getType() != other.getType())
365 {
366 return false;
367 }
368 if (isNull())
369 {
370 return true;
371 }
372 if (isBool())
373 {
374 return asBool() == other.asBool();
375 }
376 if (isInt())
377 {
378 return asInt() == other.asInt();
379 }
380 if (isFloat())
381 {
382 return asFloat() == other.asFloat();
383 }
384 if (isString())
385 {
386 return asString() == other.asString();
387 }
388 if (isArray())
389 {
390 if (!other.isArray())
391 {
392 return false;
393 }
394 const auto &self_arr = *asArray();
395 const auto &other_arr = *other.asArray();
396 return self_arr == other_arr;
397 }
398 return false;
399 }
400
402 bool operator!=(const Value &other) const noexcept
403 {
404 return !(*this == other);
405 }
406
408 bool operator<(const Value &other) const
409 {
410 if (isInt() && other.isInt())
411 {
412 return asInt() < other.asInt();
413 }
414 if (isNumber() && other.isNumber())
415 {
416 return asFloat() < other.asFloat();
417 }
418 if (isString() && other.isString())
419 {
420 return asString() < other.asString();
421 }
422 throw std::runtime_error("Cannot compare these value types ");
423 }
424
426 bool operator>(const Value &other) const
427 {
428 if (isInt() && other.isInt())
429 {
430 return asInt() > other.asInt();
431 }
432 if (isNumber() && other.isNumber())
433 {
434 return asFloat() > other.asFloat();
435 }
436 if (isString() && other.isString())
437 {
438 return asString() > other.asString();
439 }
440 throw std::runtime_error("Cannot compare these value types ");
441 }
442
444 bool operator<=(const Value &other) const noexcept
445 {
446 return !(*this > other);
447 }
448
449 bool operator>=(const Value &other) const noexcept
450 {
451 return !(*this < other);
452 }
453
455 [[nodiscard]] std::string toString() const noexcept
456 {
457 if (isNull())
458 {
459 return "null";
460 }
461 if (isBool())
462 {
463 return asBool() ? "true" : "false";
464 }
465 if (isInt())
466 {
467 return std::to_string(asInt());
468 }
469 if (isFloat())
470 {
471 return std::to_string(asFloat());
472 }
473 if (isString())
474 {
475 [[likely]] return asString();
476 }
477 if (isArray())
478 {
479 std::string result = "[";
480 const auto &arr = *asArray();
481 for (size_t i = 0; i < arr.size(); ++i)
482 {
483 result += arr[i].toString();
484 if (i < arr.size() - 1)
485 {
486 result += ", ";
487 }
488 }
489 result += "]";
490 return result;
491 }
492 return "unknown";
493 }
494
496 [[nodiscard]] const char *c_str() const
497 {
498 if (!isString())
499 {
500 [[unlikely]] throw std::runtime_error("c_str() can only be called on string values");
501 }
502 return std::get<std::shared_ptr<std::string>>(data)->c_str();
503 }
504
506 friend std::ostream &operator<<(std::ostream &os, const Value &v)
507 {
508 os << v.toString();
509 return os;
510 }
511
512 [[nodiscard]] bool isStruct() const
513 {
514 return std::holds_alternative<std::shared_ptr<StructInstance>>(data);
515 }
516
517 std::shared_ptr<StructInstance> asStruct()
518 {
519 return std::get<std::shared_ptr<StructInstance>>(data);
520 }
521
522 [[nodiscard]] std::shared_ptr<const StructInstance> asStruct() const noexcept
523 {
524 return std::get<std::shared_ptr<StructInstance>>(data);
525 }
526
527 static Value createStruct(const std::string &name)
528 {
529 return Value(std::make_shared<StructInstance>(StructInstance{.structName = name, .fields = {}}));
530 }
531
532 static Value createArray(std::vector<Value> elements = {})
533 {
534 return {std::make_shared<ArrayInstance>(std::move(elements))};
535 }
536
537 [[nodiscard]] Value getField(const std::string &name) const
538 {
539 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
540 {
541 [[unlikely]] throw std::runtime_error("getField() called on non-struct value");
542 }
543 auto s = std::get<std::shared_ptr<StructInstance>>(data);
544 auto it = s->fields.find(name);
545 if (it == s->fields.end())
546 {
547 return {};
548 }
549 return it->second;
550 }
551
552 void setField(const std::string &name, Value value)
553 {
554 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
555 {
556 [[unlikely]] throw std::runtime_error("setField() called on non-struct value");
557 }
558 auto s = std::get<std::shared_ptr<StructInstance>>(data);
559 s->fields[name] = std::move(value);
560 }
561
562 [[nodiscard]] bool hasField(const std::string &name) const noexcept
563 {
564 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
565 {
566 return false;
567 }
568 auto s = std::get<std::shared_ptr<StructInstance>>(data);
569 return s->fields.contains(name);
570 }
571};
572} // namespace Phasor
573
574template <> struct std::formatter<Phasor::Value>
575{
576 enum class Style
577 {
578 Value,
579 TypeOnly,
580 TypeValue,
581 Debug,
582 Quoted
583 };
585 std::string_view passthrough;
586
587 constexpr auto parse(std::format_parse_context &ctx)
588 {
589 auto it = ctx.begin();
590 auto end = ctx.end();
591
592 auto close = it;
593 while (close != end && *close != '}')
594 {
595 ++close;
596 }
597
598 std::string_view full(&*it, static_cast<size_t>(close - it));
599 std::string_view inner = full;
600
601 if (!full.empty())
602 {
603 switch (full.back())
604 {
605 case 't':
607 inner = full.substr(0, full.size() - 1);
608 break;
609 case 'T':
611 inner = full.substr(0, full.size() - 1);
612 break;
613 case '?':
615 inner = full.substr(0, full.size() - 1);
616 break;
617 case 'q':
619 inner = full.substr(0, full.size() - 1);
620 break;
621 default:
622 break;
623 }
624 }
625
626 passthrough = inner;
627 return close;
628 }
629
630 template <typename FormatContext> auto format(const Phasor::Value &v, FormatContext &ctx) const
631 {
632 std::string fmtstr;
633 fmtstr.reserve(passthrough.size() + 3);
634 fmtstr += "{:";
635 fmtstr += passthrough;
636 fmtstr += '}';
637
638 auto fwd = [&]<typename T>(const T &val) {
639 return std::vformat_to(ctx.out(), fmtstr, std::make_format_args(val));
640 };
641
642 using namespace Phasor;
643
644 switch (style)
645 {
646 case Style::TypeOnly:
647 return fwd(Value::typeToString(v.getType()).asString());
648
649 case Style::TypeValue:
650 return fwd(Value::typeToString(v.getType()).asString() + "(" + escapeString(v.toString()) + ")");
651
652 case Style::Debug:
653 return fwd(debug_repr(v));
654
655 case Style::Quoted:
656 if (v.isString())
657 {
658 return fwd("\"" + escapeString(v.asString()) + "\"");
659 }
660 [[fallthrough]];
661
662 case Style::Value:
663 default:
664 switch (v.getType())
665 {
666 case ValueType::Null:
667 return std::format_to(ctx.out(), "null");
668 case ValueType::Bool:
669 return fwd(v.asBool());
670 case ValueType::Int:
671 return fwd(v.asInt());
672 case ValueType::Float:
673 return fwd(v.asFloat());
675 return fwd(debug_repr(escapeString(v.asString())));
676 case ValueType::Array:
677 return fwd(v.toString());
679 return fwd(v.toString());
680 }
681 }
682 return ctx.out();
683 }
684
685 private:
686 static std::string escapeString(const std::string &input)
687 {
688 std::string output;
689 output.reserve(input.size());
690 for (char c : input)
691 {
692 switch (c)
693 {
694 case '\n':
695 output += "\\n";
696 break;
697 case '\t':
698 output += "\\t";
699 break;
700 case '\r':
701 output += "\\r";
702 break;
703 case '\0':
704 output += "\\0";
705 break;
706 case '\\':
707 output += "\\\\";
708 break;
709 case '\"':
710 output += "\\\"";
711 break;
712 case '\'':
713 output += "\\'";
714 break;
715 case '\a':
716 output += "\\a";
717 break;
718 case '\b':
719 output += "\\b";
720 break;
721 case '\f':
722 output += "\\f";
723 break;
724 case '\v':
725 output += "\\v";
726 break;
727 default:
728 if (c < 0x20 || c == 0x7F)
729 {
730 char buf[5];
731 snprintf(buf, sizeof(buf), "\\x%02X", (unsigned char)c);
732 output += buf;
733 }
734 else
735 {
736 output += c;
737 }
738 break;
739 }
740 }
741 return output;
742 }
743
744 static std::string debug_repr(const Phasor::Value &v)
745 {
746 using Phasor::ValueType;
747 switch (v.getType())
748 {
749 case ValueType::Null:
750 return "null";
752 return "\"" + escapeString(v.asString()) + "\"";
753 case ValueType::Array: {
754 const auto &arr = *v.asArray();
755 std::string out = "[";
756 for (std::size_t i = 0; i < arr.size(); ++i)
757 {
758 out += debug_repr(arr[i]);
759 if (i + 1 < arr.size())
760 {
761 out += ", ";
762 }
763 }
764 return out + "]";
765 }
766 case ValueType::Struct: {
767 const auto &s = *v.asStruct();
768 std::string out = s.structName + " { ";
769 bool first = true;
770 for (const auto &[k, val] : s.fields)
771 {
772 if (!first)
773 {
774 out += ", ";
775 }
776 out += k + ": " + debug_repr(val);
777 first = false;
778 }
779 return out + " }";
780 }
781 default:
782 return v.toString();
783 }
784 }
785};
A value in the Phasor VM.
Definition Value.hpp:58
const char * c_str() const
Convert to C Style String.
Definition Value.hpp:496
Value & operator++()
Definition Value.hpp:252
std::shared_ptr< StructInstance > asStruct()
Definition Value.hpp:517
Value(std::shared_ptr< StructInstance > s)
Struct constructor.
Definition Value.hpp:104
static Value typeToString(const ValueType &type)
Definition Value.hpp:117
Value(int i)
Integer constructor.
Definition Value.hpp:88
bool isStruct() const
Definition Value.hpp:512
bool isNull() const noexcept
Check if the value is null.
Definition Value.hpp:141
bool isTruthy() const noexcept
Helper to determine truthiness.
Definition Value.hpp:336
friend std::ostream & operator<<(std::ostream &os, const Value &v)
Print to output stream.
Definition Value.hpp:506
std::shared_ptr< ArrayInstance > asArray()
Get the value as an array.
Definition Value.hpp:194
bool isArray() const noexcept
Check if the value is an array.
Definition Value.hpp:148
Value operator/(const Value &other) const
Divide two values.
Definition Value.hpp:282
bool operator==(const Value &other) const noexcept
Comparison operations.
Definition Value.hpp:362
Value operator-(const Value &other) const
Subtract two values.
Definition Value.hpp:224
bool operator<=(const Value &other) const noexcept
Less than or equal to comparison.
Definition Value.hpp:444
std::vector< Value > ArrayInstance
Definition Value.hpp:65
bool isInt() const noexcept
Definition Value.hpp:143
ValueType getType() const noexcept
Get the type of the value.
Definition Value.hpp:113
bool operator>=(const Value &other) const noexcept
Greater than or equal to comparison.
Definition Value.hpp:449
int64_t asInt() const noexcept
Get the value as an integer.
Definition Value.hpp:159
Value(int64_t i)
Integer constructor.
Definition Value.hpp:84
void setField(const std::string &name, Value value)
Definition Value.hpp:552
bool isFloat() const noexcept
Definition Value.hpp:144
Value(bool b)
Boolean constructor.
Definition Value.hpp:80
DataType data
Definition Value.hpp:72
static Value createStruct(const std::string &name)
Definition Value.hpp:527
double asFloat() const noexcept
Get the value as a double.
Definition Value.hpp:172
std::shared_ptr< const StructInstance > asStruct() const noexcept
Definition Value.hpp:522
bool asBool() const noexcept
Get the value as a boolean.
Definition Value.hpp:154
std::shared_ptr< const ArrayInstance > asArray() const noexcept
Get the value as an array (const).
Definition Value.hpp:200
bool isNumber() const noexcept
Definition Value.hpp:146
Value(const std::string &s)
String constructor.
Definition Value.hpp:96
bool operator!=(const Value &other) const noexcept
Inequality comparison.
Definition Value.hpp:402
bool operator<(const Value &other) const
Less than comparison.
Definition Value.hpp:408
Value operator*(const Value &other) const
Multiply two values.
Definition Value.hpp:268
bool operator>(const Value &other) const
Greater than comparison.
Definition Value.hpp:426
std::string toString() const noexcept
Convert to string for printing.
Definition Value.hpp:455
Value(double d)
Double constructor.
Definition Value.hpp:92
Value & operator--()
Definition Value.hpp:237
bool isString() const noexcept
Definition Value.hpp:145
Value operator%(const Value &other) const
Modulo two values.
Definition Value.hpp:304
std::string asString() const noexcept
Get the value as a string.
Definition Value.hpp:185
Value operator!() const noexcept
Logical negation.
Definition Value.hpp:318
static Value createArray(std::vector< Value > elements={})
Definition Value.hpp:532
Value(std::shared_ptr< ArrayInstance > a)
Array constructor.
Definition Value.hpp:108
Value logicalOr(const Value &other) const noexcept
Logical OR.
Definition Value.hpp:330
Value operator+(const Value &other) const
Add two values.
Definition Value.hpp:206
Value logicalAnd(const Value &other) const noexcept
Logical AND.
Definition Value.hpp:324
bool isBool() const noexcept
Definition Value.hpp:142
std::variant< std::monostate, bool, int64_t, double, std::shared_ptr< std::string >, std::shared_ptr< StructInstance >, std::shared_ptr< ArrayInstance > > DataType
Definition Value.hpp:68
Value()
Default constructor.
Definition Value.hpp:76
Value getField(const std::string &name) const
Definition Value.hpp:537
Value(const char *s)
String constructor.
Definition Value.hpp:100
bool hasField(const std::string &name) const noexcept
Definition Value.hpp:562
static uint64_t s[2]
Definition random.cpp:6
The Phasor Programming Language and Runtime.
Definition AST.hpp:12
ValueType
Runtime value types for the VM.
Definition Value.hpp:42
std::unordered_map< std::string, Value > fields
Definition Value.hpp:63
static std::string debug_repr(const Phasor::Value &v)
Definition Value.hpp:744
constexpr auto parse(std::format_parse_context &ctx)
Definition Value.hpp:587
static std::string escapeString(const std::string &input)
Definition Value.hpp:686
auto format(const Phasor::Value &v, FormatContext &ctx) const
Definition Value.hpp:630
std::string_view passthrough
Definition Value.hpp:585