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 "phsint.hpp"
33#include "PhasorString.hpp"
34
36namespace Phasor
37{
38
52
58class Value
59{
60 public:
62 {
64 std::unordered_map<PhsString, Value> fields;
65 };
66 using ArrayInstance = std::vector<Value>;
67
68 private:
69 using DataType = std::variant<std::monostate, bool, i64, f64, PhsString,
70 std::shared_ptr<StructInstance>,
71 std::shared_ptr<ArrayInstance>>;
72
74
75 public:
77 Value() : data(std::monostate{})
78 {
79 }
80
81 Value(bool b) : data(b)
82 {
83 }
84
85 Value(i64 i) : data(i)
86 {
87 }
88
89 Value(int i) : data(static_cast<i64>(i))
90 {
91 }
92
93 Value(f64 d) : data(d)
94 {
95 }
96
97 Value(const std::string &s) : data(PhsString(s))
98 {
99 }
100
101 Value(const PhsString &s) : data(s)
102 {
103 }
104
105 Value(const char *s) : data(PhsString(s))
106 {
107 }
108
109 Value(std::shared_ptr<StructInstance> s) : data(std::move(s))
110 {
111 }
112
113 Value(std::shared_ptr<ArrayInstance> a) : data(std::move(a))
114 {
115 }
116
118 [[nodiscard]] ValueType getType() const noexcept {
119 return static_cast<ValueType>(data.index());
120 }
121
122 static Value typeToString(const ValueType &type)
123 {
124 switch (type)
125 {
126 case ValueType::Null:
127 return {"null"};
128 case ValueType::Bool:
129 return {"bool"};
130 case ValueType::Int:
131 return {"int"};
132 case ValueType::Float:
133 return {"float"};
135 return {"string"};
137 return {"struct"};
138 case ValueType::Array:
139 return {"array"};
140 default:
141 return {"unknown"};
142 }
143 }
144
146 [[nodiscard]] bool isNull() const noexcept { return data.index() == 0; }
147 [[nodiscard]] bool isBool() const noexcept { return data.index() == 1; }
148 [[nodiscard]] bool isInt() const noexcept { return data.index() == 2; }
149 [[nodiscard]] bool isFloat() const noexcept { return data.index() == 3; }
150 [[nodiscard]] bool isString() const noexcept { return data.index() == 4; }
151 [[nodiscard]] bool isNumber() const noexcept { return data.index() == 2 || data.index() == 3; }
153 [[nodiscard]] bool isArray() const noexcept
154 {
155 return std::holds_alternative<std::shared_ptr<ArrayInstance>>(data);
156 }
157
159 [[nodiscard]] bool asBool() const noexcept
160 {
161 return std::get<bool>(data);
162 }
163
164 [[nodiscard]] i64 asInt() const noexcept
165 {
166 if (isInt())
167 {
168 return std::get<i64>(data);
169 }
170 if (isFloat())
171 {
172 return static_cast<i64>(std::get<f64>(data));
173 }
174 return 0;
175 }
176
177 [[nodiscard]] f64 asFloat() const noexcept
178 {
179 if (isFloat())
180 {
181 return std::get<f64>(data);
182 }
183 if (isInt())
184 {
185 return static_cast<f64>(std::get<i64>(data));
186 }
187 return 0.0;
188 }
189
190 [[nodiscard]] std::string string() const noexcept
191 {
192 if (isString())
193 {
194 return std::get<PhsString>(data).str();
195 }
196 return toString();
197 }
198
199 [[nodiscard]] PhsString asString() const noexcept
200 {
201 if (isString())
202 {
203 return std::get<PhsString>(data);
204 }
205 return PhsString(toString());
206 }
207
208 std::shared_ptr<ArrayInstance> asArray()
209 {
210 return std::get<std::shared_ptr<ArrayInstance>>(data);
211 }
212
214 [[nodiscard]] std::shared_ptr<const ArrayInstance> asArray() const noexcept
215 {
216 return std::get<std::shared_ptr<ArrayInstance>>(data);
217 }
218
220 Value operator+(const Value &other) const
221 {
222 if (isInt() && other.isInt())
223 {
224 return {asInt() + other.asInt()};
225 }
226 if (isNumber() && other.isNumber())
227 {
228 return {asFloat() + other.asFloat()};
229 }
230 if (isString() && other.isString())
231 {
232 return Value(asString() + other.asString());
233 }
234 throw std::runtime_error("Cannot add these value types");
235 }
236
238 Value operator-(const Value &other) const
239 {
240 if (isInt() && other.isInt())
241 {
242 return {asInt() - other.asInt()};
243 }
244 if (isNumber() && other.isNumber())
245 {
246 return {asFloat() - other.asFloat()};
247 }
248 throw std::runtime_error("Cannot subtract these value types");
249 }
250
252 {
253 if (isInt())
254 {
255 data = asInt() - 1;
256 return *this;
257 }
258 if (isFloat())
259 {
260 data = asFloat() - 1.0;
261 return *this;
262 }
263 throw std::runtime_error("Cannot decrement this value type");
264 }
265
267 {
268 if (isInt())
269 {
270 data = asInt() + 1;
271 return *this;
272 }
273 if (isFloat())
274 {
275 data = asFloat() + 1.0;
276 return *this;
277 }
278 throw std::runtime_error("Cannot increment this value type");
279 }
280
282 Value operator*(const Value &other) const
283 {
284 if (isInt() && other.isInt())
285 {
286 return {asInt() * other.asInt()};
287 }
288 if (isNumber() && other.isNumber())
289 {
290 return {asFloat() * other.asFloat()};
291 }
292 throw std::runtime_error("Cannot multiply these value types");
293 }
294
296 Value operator/(const Value &other) const
297 {
298 if (isInt() && other.isInt())
299 {
300 if (other.asInt() == 0)
301 {
302 throw std::runtime_error("Division by zero");
303 }
304 return {asInt() / other.asInt()};
305 }
306 if (isNumber() && other.isNumber())
307 {
308 if (other.asFloat() == 0.0)
309 {
310 throw std::runtime_error("Division by zero");
311 }
312 return {asFloat() / other.asFloat()};
313 }
314 throw std::runtime_error("Cannot divide these value types");
315 }
316
318 Value operator%(const Value &other) const
319 {
320 if (isInt() && other.isInt())
321 {
322 if (other.asInt() == 0)
323 {
324 throw std::runtime_error("Modulo by zero");
325 }
326 return {asInt() % other.asInt()};
327 }
328 throw std::runtime_error("Modulo requires integer operands");
329 }
330
332 Value operator!() const noexcept
333 {
334 return {!isTruthy()};
335 }
336
338 [[nodiscard]] Value logicalAnd(const Value &other) const noexcept
339 {
340 return {isTruthy() && other.isTruthy()};
341 }
342
344 [[nodiscard]] Value logicalOr(const Value &other) const noexcept
345 {
346 return {isTruthy() || other.isTruthy()};
347 }
348
350 [[nodiscard]] bool isTruthy() const noexcept
351 {
352 if (isNull())
353 {
354 return false;
355 }
356 if (isBool())
357 {
358 return asBool();
359 }
360 if (isInt())
361 {
362 return asInt() != 0;
363 }
364 if (isFloat())
365 {
366 return asFloat() != 0.0;
367 }
368 if (isString())
369 {
370 return !asString().empty();
371 }
372 return false;
373 }
374
376 bool operator==(const Value &other) const noexcept
377 {
378 if (getType() != other.getType())
379 {
380 return false;
381 }
382 if (isNull())
383 {
384 return true;
385 }
386 if (isBool())
387 {
388 return asBool() == other.asBool();
389 }
390 if (isInt())
391 {
392 return asInt() == other.asInt();
393 }
394 if (isFloat())
395 {
396 return asFloat() == other.asFloat();
397 }
398 if (isString())
399 {
400 return asString() == other.asString();
401 }
402 if (isArray())
403 {
404 if (!other.isArray())
405 {
406 return false;
407 }
408 const auto &self_arr = *asArray();
409 const auto &other_arr = *other.asArray();
410 return self_arr == other_arr;
411 }
412 return false;
413 }
414
416 bool operator!=(const Value &other) const noexcept
417 {
418 return !(*this == other);
419 }
420
422 bool operator<(const Value &other) const
423 {
424 if (isInt() && other.isInt())
425 {
426 return asInt() < other.asInt();
427 }
428 if (isNumber() && other.isNumber())
429 {
430 return asFloat() < other.asFloat();
431 }
432 if (isString() && other.isString())
433 {
434 return asString() < other.asString();
435 }
436 throw std::runtime_error("Cannot compare these value types ");
437 }
438
440 bool operator>(const Value &other) const
441 {
442 if (isInt() && other.isInt())
443 {
444 return asInt() > other.asInt();
445 }
446 if (isNumber() && other.isNumber())
447 {
448 return asFloat() > other.asFloat();
449 }
450 if (isString() && other.isString())
451 {
452 return asString() > other.asString();
453 }
454 throw std::runtime_error("Cannot compare these value types ");
455 }
456
458 bool operator<=(const Value &other) const noexcept
459 {
460 return !(*this > other);
461 }
462
463 bool operator>=(const Value &other) const noexcept
464 {
465 return !(*this < other);
466 }
467
468 [[nodiscard]] std::string toRepr() const noexcept
469 {
470 if (isString())
471 {
472 return "\"" + string() + "\"";
473 }
474 return toString();
475 }
476
478 [[nodiscard]] std::string toString() const noexcept
479 {
480 if (isNull())
481 {
482 return "null";
483 }
484 if (isBool())
485 {
486 return asBool() ? "true" : "false";
487 }
488 if (isInt())
489 {
490 return std::to_string(asInt());
491 }
492 if (isFloat())
493 {
494 return std::to_string(asFloat());
495 }
496 if (isString())
497 {
498 [[likely]] return string();
499 }
500 if (isArray())
501 {
502 std::string result = "[";
503 const auto &arr = *asArray();
504 for (size_t i = 0; i < arr.size(); ++i)
505 {
506 result += arr[i].toRepr();
507 if (i < arr.size() - 1)
508 {
509 result += ", ";
510 }
511 }
512 result += "]";
513 return result;
514 }
515 if (isStruct())
516 {
517 std::string result = "{";
518 const auto &s = *asStruct();
519 bool first = true;
520 for (const auto &[k, v] : s.fields)
521 {
522 if (!first)
523 {
524 result += ", ";
525 }
526 result += "\"" + k.str() + "\": " + v.toRepr();
527 first = false;
528 }
529 result += "}";
530 return result;
531 }
532 return "unknown";
533 }
534
536 [[nodiscard]] const char *c_str() const
537 {
538 if (!isString())
539 {
540 [[unlikely]] throw std::runtime_error("c_str() can only be called on string values");
541 }
542 return std::get<PhsString>(data).c_str();
543 }
544
546 friend std::ostream &operator<<(std::ostream &os, const Value &v)
547 {
548 os << v.toString();
549 return os;
550 }
551
552 [[nodiscard]] bool isStruct() const
553 {
554 return std::holds_alternative<std::shared_ptr<StructInstance>>(data);
555 }
556
557 std::shared_ptr<StructInstance> asStruct()
558 {
559 return std::get<std::shared_ptr<StructInstance>>(data);
560 }
561
562 [[nodiscard]] std::shared_ptr<const StructInstance> asStruct() const noexcept
563 {
564 return std::get<std::shared_ptr<StructInstance>>(data);
565 }
566
567 static Value createStruct(const PhsString &name)
568 {
569 return Value(std::make_shared<StructInstance>(StructInstance{.structName = name, .fields = {}}));
570 }
571
572 static Value createArray(std::vector<Value> elements = {})
573 {
574 return {std::make_shared<ArrayInstance>(std::move(elements))};
575 }
576
577 [[nodiscard]] Value getField(const PhsString &name) const
578 {
579 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
580 {
581 [[unlikely]] throw std::runtime_error("getField() called on non-struct value");
582 }
583 auto s = std::get<std::shared_ptr<StructInstance>>(data);
584 auto it = s->fields.find(name);
585 if (it == s->fields.end())
586 {
587 return {};
588 }
589 return it->second;
590 }
591
592 void setField(const PhsString &name, Value value)
593 {
594 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
595 {
596 [[unlikely]] throw std::runtime_error("setField() called on non-struct value");
597 }
598 auto s = std::get<std::shared_ptr<StructInstance>>(data);
599 s->fields[name] = std::move(value);
600 }
601
602 [[nodiscard]] bool hasField(const PhsString &name) const noexcept
603 {
604 if (!std::holds_alternative<std::shared_ptr<StructInstance>>(data))
605 {
606 return false;
607 }
608 auto s = std::get<std::shared_ptr<StructInstance>>(data);
609 return s->fields.contains(name);
610 }
611};
612} // namespace Phasor
613
614template <> struct std::formatter<Phasor::Value>
615{
616 enum class Style
617 {
618 Value,
619 TypeOnly,
620 TypeValue,
621 Debug,
622 Quoted
623 };
625 std::string_view passthrough;
626
627 constexpr auto parse(std::format_parse_context &ctx)
628 {
629 auto it = ctx.begin();
630 auto end = ctx.end();
631
632 auto close = it;
633 while (close != end && *close != '}')
634 {
635 ++close;
636 }
637
638 std::string_view full(&*it, static_cast<size_t>(close - it));
639 std::string_view inner = full;
640
641 if (!full.empty())
642 {
643 switch (full.back())
644 {
645 case 't':
647 inner = full.substr(0, full.size() - 1);
648 break;
649 case 'T':
651 inner = full.substr(0, full.size() - 1);
652 break;
653 case '?':
655 inner = full.substr(0, full.size() - 1);
656 break;
657 case 'q':
659 inner = full.substr(0, full.size() - 1);
660 break;
661 default:
662 break;
663 }
664 }
665
666 passthrough = inner;
667 return close;
668 }
669
670 template <typename FormatContext> auto format(const Phasor::Value &v, FormatContext &ctx) const
671 {
672 std::string fmtstr;
673 fmtstr.reserve(passthrough.size() + 3);
674 fmtstr += "{:";
675 fmtstr += passthrough;
676 fmtstr += '}';
677
678 auto fwd = [&]<typename T>(const T &val) {
679 return std::vformat_to(ctx.out(), fmtstr, std::make_format_args(val));
680 };
681
682 using namespace Phasor;
683
684 switch (style)
685 {
686 case Style::TypeOnly:
687 return fwd(Value::typeToString(v.getType()).asString().str());
688
689 case Style::TypeValue:
690 return fwd(Value::typeToString(v.getType()).asString().str() + "(" + escapeString(v.toString()) + ")");
691
692 case Style::Debug:
693 return fwd(debug_repr(v));
694
695 case Style::Quoted:
696 if (v.isString())
697 {
698 return fwd("\"" + escapeString(v.asString()) + "\"");
699 }
700 [[fallthrough]];
701
702 case Style::Value:
703 default:
704 switch (v.getType())
705 {
706 case ValueType::Null:
707 return std::format_to(ctx.out(), "null");
708 case ValueType::Bool:
709 return fwd(v.asBool());
710 case ValueType::Int:
711 return fwd(v.asInt());
712 case ValueType::Float:
713 return fwd(v.asFloat());
715 return fwd(debug_repr(escapeString(v.asString())));
716 case ValueType::Array:
717 return fwd(v.toString());
719 return fwd(v.toString());
720 }
721 }
722 return ctx.out();
723 }
724
725 private:
726 static std::string escapeString(std::string_view input)
727 {
728 std::string output;
729 output.reserve(input.size());
730 for (char c : input)
731 {
732 switch (c)
733 {
734 case '\n':
735 output += "\\n";
736 break;
737 case '\t':
738 output += "\\t";
739 break;
740 case '\r':
741 output += "\\r";
742 break;
743 case '\0':
744 output += "\\0";
745 break;
746 case '\\':
747 output += "\\\\";
748 break;
749 case '\"':
750 output += "\\\"";
751 break;
752 case '\'':
753 output += "\\'";
754 break;
755 case '\a':
756 output += "\\a";
757 break;
758 case '\b':
759 output += "\\b";
760 break;
761 case '\f':
762 output += "\\f";
763 break;
764 case '\v':
765 output += "\\v";
766 break;
767 default:
768 if (c < 0x20 || c == 0x7F)
769 {
770 char buf[5];
771 snprintf(buf, sizeof(buf), "\\x%02X", (unsigned char)c);
772 output += buf;
773 }
774 else
775 {
776 output += c;
777 }
778 break;
779 }
780 }
781 return output;
782 }
783
784 static std::string debug_repr(const Phasor::Value &v)
785 {
786 using Phasor::ValueType;
787 switch (v.getType())
788 {
789 case ValueType::Null:
790 return "null";
792 return "\"" + escapeString(v.asString()) + "\"";
793 case ValueType::Array: {
794 const auto &arr = *v.asArray();
795 std::string out = "[";
796 for (std::size_t i = 0; i < arr.size(); ++i)
797 {
798 out += debug_repr(arr[i]);
799 if (i + 1 < arr.size())
800 {
801 out += ", ";
802 }
803 }
804 return out + "]";
805 }
806 case ValueType::Struct: {
807 const auto &s = *v.asStruct();
808 std::string out = s.structName.str() + " { ";
809 bool first = true;
810 for (const auto &[k, val] : s.fields)
811 {
812 if (!first)
813 {
814 out += ", ";
815 }
816 out += k.str() + ": " + debug_repr(val);
817 first = false;
818 }
819 return out + " }";
820 }
821 default:
822 return v.toString();
823 }
824 }
825};
bool empty() const noexcept
A value in the Phasor VM.
Definition Value.hpp:59
PhsString asString() const noexcept
Get the value as a Small String.
Definition Value.hpp:199
std::string string() const noexcept
Get the value as a string.
Definition Value.hpp:190
const char * c_str() const
Convert to C Style String.
Definition Value.hpp:536
Value & operator++()
Definition Value.hpp:266
std::shared_ptr< StructInstance > asStruct()
Definition Value.hpp:557
Value(std::shared_ptr< StructInstance > s)
Struct constructor.
Definition Value.hpp:109
static Value typeToString(const ValueType &type)
Definition Value.hpp:122
Value(int i)
Integer constructor.
Definition Value.hpp:89
bool isStruct() const
Definition Value.hpp:552
bool isNull() const noexcept
Check if the value is null.
Definition Value.hpp:146
bool isTruthy() const noexcept
Helper to determine truthiness.
Definition Value.hpp:350
friend std::ostream & operator<<(std::ostream &os, const Value &v)
Print to output stream.
Definition Value.hpp:546
std::shared_ptr< ArrayInstance > asArray()
Get the value as an array.
Definition Value.hpp:208
bool isArray() const noexcept
Check if the value is an array.
Definition Value.hpp:153
Value operator/(const Value &other) const
Divide two values.
Definition Value.hpp:296
bool operator==(const Value &other) const noexcept
Comparison operations.
Definition Value.hpp:376
Value(f64 d)
Double constructor.
Definition Value.hpp:93
Value operator-(const Value &other) const
Subtract two values.
Definition Value.hpp:238
bool operator<=(const Value &other) const noexcept
Less than or equal to comparison.
Definition Value.hpp:458
std::vector< Value > ArrayInstance
Definition Value.hpp:66
bool isInt() const noexcept
Definition Value.hpp:148
ValueType getType() const noexcept
Get the type of the value.
Definition Value.hpp:118
static Value createStruct(const PhsString &name)
Definition Value.hpp:567
bool operator>=(const Value &other) const noexcept
Greater than or equal to comparison.
Definition Value.hpp:463
bool hasField(const PhsString &name) const noexcept
Definition Value.hpp:602
bool isFloat() const noexcept
Definition Value.hpp:149
void setField(const PhsString &name, Value value)
Definition Value.hpp:592
Value getField(const PhsString &name) const
Definition Value.hpp:577
f64 asFloat() const noexcept
Get the value as a f64.
Definition Value.hpp:177
Value(bool b)
Boolean constructor.
Definition Value.hpp:81
DataType data
Definition Value.hpp:73
std::shared_ptr< const StructInstance > asStruct() const noexcept
Definition Value.hpp:562
i64 asInt() const noexcept
Get the value as an integer.
Definition Value.hpp:164
bool asBool() const noexcept
Get the value as a boolean.
Definition Value.hpp:159
std::shared_ptr< const ArrayInstance > asArray() const noexcept
Get the value as an array (const).
Definition Value.hpp:214
bool isNumber() const noexcept
Definition Value.hpp:151
Value(const std::string &s)
String constructor.
Definition Value.hpp:97
bool operator!=(const Value &other) const noexcept
Inequality comparison.
Definition Value.hpp:416
bool operator<(const Value &other) const
Less than comparison.
Definition Value.hpp:422
Value operator*(const Value &other) const
Multiply two values.
Definition Value.hpp:282
bool operator>(const Value &other) const
Greater than comparison.
Definition Value.hpp:440
std::string toString() const noexcept
Convert to string for printing.
Definition Value.hpp:478
Value & operator--()
Definition Value.hpp:251
bool isString() const noexcept
Definition Value.hpp:150
Value operator%(const Value &other) const
Modulo two values.
Definition Value.hpp:318
Value operator!() const noexcept
Logical negation.
Definition Value.hpp:332
static Value createArray(std::vector< Value > elements={})
Definition Value.hpp:572
Value(std::shared_ptr< ArrayInstance > a)
Array constructor.
Definition Value.hpp:113
Value(i64 i)
Integer constructor.
Definition Value.hpp:85
Value logicalOr(const Value &other) const noexcept
Logical OR.
Definition Value.hpp:344
Value operator+(const Value &other) const
Add two values.
Definition Value.hpp:220
Value(const PhsString &s)
Small Strring constructor.
Definition Value.hpp:101
Value logicalAnd(const Value &other) const noexcept
Logical AND.
Definition Value.hpp:338
bool isBool() const noexcept
Definition Value.hpp:147
Value()
Default constructor.
Definition Value.hpp:77
Value(const char *s)
String constructor.
Definition Value.hpp:105
std::string toRepr() const noexcept
Definition Value.hpp:468
std::variant< std::monostate, bool, i64, f64, PhsString, std::shared_ptr< StructInstance >, std::shared_ptr< ArrayInstance > > DataType
Definition Value.hpp:69
static Phasor::u64 s[2]
Definition random.cpp:7
The Phasor Programming Language and Runtime.
Definition AST.hpp:13
int64_t i64
Definition phsint.hpp:16
ValueType
Runtime value types for the VM.
Definition Value.hpp:43
uint8_t u8
Definition phsint.hpp:9
double f64
Definition phsint.hpp:7
std::unordered_map< PhsString, Value > fields
Definition Value.hpp:64
static std::string debug_repr(const Phasor::Value &v)
Definition Value.hpp:784
constexpr auto parse(std::format_parse_context &ctx)
Definition Value.hpp:627
static std::string escapeString(std::string_view input)
Definition Value.hpp:726
auto format(const Phasor::Value &v, FormatContext &ctx) const
Definition Value.hpp:670
std::string_view passthrough
Definition Value.hpp:625