195 if (
static_cast<int>(op) >=
static_cast<int>(
OpCode::MOV))
203 std::stringstream ss;
204 for (
unsigned char c : str)
208 case '\a': ss <<
"\\a";
break;
209 case '\b': ss <<
"\\b";
break;
210 case '\f': ss <<
"\\f";
break;
211 case '\n': ss <<
"\\n";
break;
212 case '\r': ss <<
"\\r";
break;
213 case '\t': ss <<
"\\t";
break;
214 case '\v': ss <<
"\\v";
break;
215 case '\x1b': ss <<
"\\e";
break;
216 case '\\': ss <<
"\\\\";
break;
217 case '"': ss <<
"\\\"";
break;
219 if (c < 0x20 || c == 0x7F)
222 <<
"0123456789abcdef"[c >> 4]
223 <<
"0123456789abcdef"[c & 0xF];
227 ss << static_cast<char>(c);
238 size_t len = str.length();
240 auto hexVal = [](
char c) ->
int {
241 if (c >=
'0' && c <=
'9')
return c -
'0';
242 if (c >=
'a' && c <=
'f')
return c -
'a' + 10;
243 if (c >=
'A' && c <=
'F')
return c -
'A' + 10;
247 for (
size_t i = 0; i < len; ++i)
249 if (str[i] !=
'\\' || i + 1 >= len)
258 case 'a': result +=
'\a';
break;
259 case 'b': result +=
'\b';
break;
260 case 'f': result +=
'\f';
break;
261 case 'n': result +=
'\n';
break;
262 case 'r': result +=
'\r';
break;
263 case 't': result +=
'\t';
break;
264 case 'v': result +=
'\v';
break;
265 case '\\': result +=
'\\';
break;
266 case '\'': result +=
'\'';
break;
267 case '"': result +=
'"';
break;
269 case 'E': result +=
'\x1b';
break;
271 case '0':
case '1':
case '2':
case '3':
272 case '4':
case '5':
case '6':
case '7':
274 u32 val =
static_cast<u32>(esc -
'0');
275 for (
int k = 1; k < 3 && i + 1 < len; ++k)
278 if (d <
'0' || d >
'7')
break;
279 val = val * 8 +
static_cast<u32>(d -
'0');
282 result +=
static_cast<char>(val & 0xFF);
288 if (i + 1 >= len || hexVal(str[i + 1]) < 0) { result += esc;
break; }
289 int val = hexVal(str[++i]);
290 if (i + 1 < len && hexVal(str[i + 1]) >= 0)
291 val = (val << 4) | hexVal(str[++i]);
292 result +=
static_cast<char>(val);
299 int ndigits = (esc ==
'u') ? 4 : 8;
302 for (
int k = 0; k < ndigits; ++k)
304 if (i + 1 >= len || hexVal(str[i + 1]) < 0) { ok =
false;
break; }
305 cp = (cp << 4) | static_cast<u32>(hexVal(str[++i]));
307 if (!ok || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF))
309 result += esc;
break;
311 if (cp <= 0x7F) { result +=
static_cast<char>(cp); }
312 else if (cp <= 0x7FF) { result +=
static_cast<char>(0xC0 | (cp >> 6));
313 result +=
static_cast<char>(0x80 | (cp & 0x3F)); }
314 else if (cp <= 0xFFFF) { result +=
static_cast<char>(0xE0 | (cp >> 12));
315 result +=
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
316 result +=
static_cast<char>(0x80 | (cp & 0x3F)); }
317 else { result +=
static_cast<char>(0xF0 | (cp >> 18));
318 result +=
static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
319 result +=
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
320 result +=
static_cast<char>(0x80 | (cp & 0x3F)); }
337 const std::function<std::string(
const std::string &)> &escape)
345 ss <<
"BOOL " << (val.
asBool() ?
"true" :
"false") <<
"\n";
348 ss <<
"INT " << val.
asInt() <<
"\n";
351 ss <<
"FLOAT " << val.
asFloat() <<
"\n";
354 ss <<
"STRING \"" << escape(val.
asString()) <<
"\"\n";
361 throw std::runtime_error(
"PhasorIR::serialize: null struct pointer");
362 ss <<
"STRUCT \"" << escape(structPtr->structName) <<
"\" " << structPtr->fields.size() <<
"\n";
363 for (
const auto &[fieldName, fieldVal] : structPtr->fields)
365 ss <<
" FIELD " << fieldName <<
" ";
375 throw std::runtime_error(
"PhasorIR::serialize: null array pointer");
376 ss <<
"ARRAY " << arrayPtr->size() <<
"\n";
377 for (
const auto &elem : *arrayPtr)
386 throw std::runtime_error(
"PhasorIR::serialize: unknown ValueType");
394 const std::function<std::string(
const std::string &)> &unescape)
400 else if (type ==
"BOOL")
404 return Value{b ==
"true"};
406 else if (type ==
"INT")
412 else if (type ==
"FLOAT")
418 else if (type ==
"STRING")
422 while (ss.get(c) && c !=
'"') {}
423 std::getline(ss,
s,
'"');
424 return Value{unescape(
s)};
426 else if (type ==
"STRUCT")
428 std::string typeName;
432 while (ss.get(qc) && qc !=
'"') {}
433 std::getline(ss, typeName,
'"');
434 typeName = unescape(typeName);
437 auto structInstance = std::make_shared<Value::StructInstance>();
438 structInstance->structName =
PhsString(typeName);
440 for (
int f = 0; f < fieldCount; ++f)
442 std::string keyword, fieldName, fieldType;
446 structInstance->fields[fieldName] =
readIRValue(fieldType, ss, unescape);
449 return Value{std::move(structInstance)};
451 else if (type ==
"ARRAY")
453 int elementCount = 0;
456 auto arrayInstance = std::make_shared<Value::ArrayInstance>();
457 arrayInstance->reserve(
static_cast<size_t>(elementCount));
459 for (
int e = 0; e < elementCount; ++e)
461 std::string keyword, elemType;
464 arrayInstance->push_back(
readIRValue(elemType, ss, unescape));
467 return Value{std::move(arrayInstance)};
471 throw std::runtime_error(
"PhasorIR: unknown value type tag: " + type);
480 std::stringstream ss;
483 ss <<
".PHIR 3.0.0\n";
486 std::map<int, std::string> indexToVarName;
487 for (
const auto &[name, index] : bytecode.
variables)
488 indexToVarName[index] = name;
490 std::map<int, std::string> addressToFuncName;
492 addressToFuncName[address] = name;
495 ss <<
".CONSTANTS " << bytecode.
constants.size() <<
"\n";
496 for (
const auto &val : bytecode.
constants)
501 for (
const auto &[name, index] : bytecode.
variables)
502 ss << name <<
" " << index <<
"\n";
507 ss << name <<
" " << address <<
"\n";
510 ss <<
".STRUCTS " << bytecode.
structs.size() <<
"\n";
511 for (
const auto &info : bytecode.
structs)
513 ss << info.name <<
" " << info.firstConstIndex <<
" " << info.fieldCount;
514 for (
const auto &fieldName : info.fieldNames)
515 ss <<
" " << fieldName;
520 ss <<
".INSTRUCTIONS " << bytecode.
instructions.size() <<
"\n";
523 std::stringstream instrLine;
527 i32 operands[3] = {instr.operand1, instr.operand2, instr.operand3};
530 for (
int i = 0; i < operandCount; ++i)
534 if (i > 0) instrLine <<
",";
540 instrLine <<
"r" << operands[i];
543 instrLine << operands[i];
544 if (operands[i] >= 0 && std::cmp_less(operands[i], bytecode.
constants.size()))
550 if (str.length() > 20) str = str.substr(0, 20) +
"...";
551 comment =
"const[" + std::to_string(operands[i]) +
"]=\"" +
escapeString(str) +
"\"";
554 comment =
"const[" + std::to_string(operands[i]) +
"]=" + std::to_string(v.
asInt());
556 comment =
"const[" + std::to_string(operands[i]) +
"]=" + std::to_string(v.
asFloat());
560 instrLine << operands[i];
561 if (indexToVarName.contains(operands[i]))
562 comment =
"var=" + indexToVarName[operands[i]];
565 instrLine << operands[i];
566 if (addressToFuncName.contains(operands[i]))
567 comment =
"func=" + addressToFuncName[operands[i]];
570 instrLine << operands[i];
575 std::string lineStr = instrLine.str();
576 if (!comment.empty())
578 const size_t commentColumn = 40;
579 if (lineStr.length() < commentColumn)
580 lineStr.append(commentColumn - lineStr.length(),
' ');
583 lineStr +=
"; " + comment;
585 ss << lineStr <<
"\n";
588 std::string textData = ss.str();
589 std::vector<u8> buffer;
590 buffer.insert(buffer.end(), textData.begin(), textData.end());
600 throw std::runtime_error(
"Invalid Phasor IR file: too small");
602 std::string textData(data.begin() + 8, data.end());
603 std::stringstream ss(textData);
607 while (ss >> section)
609 if (section ==
".PHIR")
613 if (version <
"3.0.0")
614 throw std::runtime_error(
"Incompatible Phasor IR version");
616 else if (section ==
".CONSTANTS")
621 for (
int i = 0; i < count; ++i)
628 else if (section ==
".VARIABLES")
632 for (
int i = 0; i < count; ++i)
640 else if (section ==
".FUNCTIONS")
644 for (
int i = 0; i < count; ++i)
648 ss >> name >> address;
652 else if (section ==
".STRUCTS")
656 for (
int i = 0; i < count; ++i)
663 std::string fieldName;
667 int index =
static_cast<int>(bytecode.
structs.size());
668 bytecode.
structs.push_back(std::move(info));
672 else if (section ==
".INSTRUCTIONS")
677 for (
int i = 0; i < count; ++i)
684 i32 operands[3] = {0, 0, 0};
686 for (
int j = 0; j < operandCount; ++j)
691 if (token.empty() || token[0] ==
';')
693 std::getline(ss, token);
697 if (!token.empty() && token.back() ==
',')
700 if (!token.empty() && token[0] ==
'r')
701 operands[j] = std::stoi(token.substr(1));
703 operands[j] = std::stoi(token);
708 while (ss.get(c) && c !=
'\n') {}
710 bytecode.
instructions.emplace_back(op, operands[0], operands[1], operands[2]);
725 std::vector<u8> data =
serialize(bytecode);
726 std::ofstream file(filename, std::ios::binary);
729 file.write(
reinterpret_cast<const char *
>(data.data()), (std::streamsize)data.size());
740 std::ifstream file(filename, std::ios::binary | std::ios::ate);
742 throw std::runtime_error(
"Cannot open file");
744 std::streamsize size = file.tellg();
745 file.seekg(0, std::ios::beg);
746 std::vector<u8> buffer(size);
747 if (!file.read(
reinterpret_cast<char *
>(buffer.data()), size))
748 throw std::runtime_error(
"Cannot read file");
static std::string unescapeString(const std::string &str)
Helper to unescape strings from text format.
OperandType
Operand types for instructions.
@ VARIABLE_IDX
Index into variable mapping.
@ CONSTANT_IDX
Index into constant pool.
@ FUNCTION_IDX
Index into function entries.
@ REGISTER
Register operand.
static std::vector< u8 > serialize(const Bytecode &bytecode)
Serialize bytecode to Phasor IR format.
static OperandType getOperandType(OpCode op, int operandIndex)
static Bytecode loadFromFile(const std::filesystem::path &filename)
Load bytecode from .phir file.
static int getOperandCount(OpCode op)
static std::string escapeString(const std::string &str)
Helper to escape strings for text format.
static Bytecode deserialize(const std::vector< u8 > &data)
Deserialize Phasor IR format to bytecode.
static bool saveToFile(const Bytecode &bytecode, const std::filesystem::path &filename)
Save bytecode to .phir file.
A value in the Phasor VM.
PhsString asString() const noexcept
Get the value as a Small String.
std::shared_ptr< StructInstance > asStruct()
std::shared_ptr< ArrayInstance > asArray()
Get the value as an array.
ValueType getType() const noexcept
Get the type of the value.
f64 asFloat() const noexcept
Get the value as a f64.
i64 asInt() const noexcept
Get the value as an integer.
bool asBool() const noexcept
Get the value as a boolean.
The Phasor Programming Language and Runtime.
static Value readIRValue(const std::string &type, std::stringstream &ss, const std::function< std::string(const std::string &)> &unescape)
OpCode stringToOpCode(const std::string &str)
std::string opCodeToString(OpCode op)
@ IGREATER_THAN
Pop b, pop a, push a > b.
@ IEQUAL
Pop b, pop a, push a == b.
@ SYSTEM_OUT
Call system function and push stdout.
@ LOG_R
R[rA] = log(R[rB]).
@ FLMOD_R
R[rA] = R[rB] % R[rC].
@ SUBSTR
Pop len, pop start, pop s, push s.substr(start, len).
@ IAND
Pop b, pop a, push a && b.
@ SET_FIELD_STATIC
Pop value and struct instance, set field by static offset.
@ MOV
Copy register to register: R[rA] = R[rB].
@ PRINTERROR_R
Print register to stderr: printerror(R[rA]).
@ IAND_R
R[rA] = R[rB] && R[rC].
@ FLMUL_R
R[rA] = R[rB] * R[rC].
@ IADD
Pop b, pop a, push a + b.
@ PUSH2_R
Push 2 registers to stack: push2(R[rA], R[rB]).
@ FLGE_R
R[rA] = R[rB] >= R[rC].
@ SYSTEM_R
Run an operating system shell command: system(R[rA]).
@ PUSH_CONST
Push constant from constant pool.
@ JUMP_IF_TRUE
Jump if top of stack is true (pops value).
@ FLGT_R
R[rA] = R[rB] > R[rC].
@ PUSH_R
Push register to stack: push(R[rA]).
@ POP_R
Pop stack to register: R[rA] = pop().
@ FLEQUAL
Pop b, pop a, push a == b.
@ GET_FIELD_STATIC
Pop struct instance, push field by static offset (structIndex, fieldOffset).
@ FLNOT_EQUAL
Pop b, pop a, push a != b.
@ FLADD_R
R[rA] = R[rB] + R[rC].
@ POP2_R
Pop 2 values from stack to registers: pop2(R[rA], R[rB]).
@ LOAD_CONST_R
Load constant to register: R[rA] = constants[immediate].
@ FLADD
Pop b, pop a, push a + b.
@ JUMP
Unconditional jump to offset.
@ FLLT_R
R[rA] = R[rB] < R[rC].
@ SET_FIELD
Pop struct, pop field name, pop value, set field value.
@ IMULTIPLY
Pop b, pop a, push a * b.
@ READLINE_R
Read line into register: readline(R[rA]).
@ CHAR_AT
Pop index, pop s, push s[index].
@ IMUL_R
R[rA] = R[rB] * R[rC].
@ FLGREATER_EQUAL
Pop b, pop a, push a >= b.
@ FLLE_R
R[rA] = R[rB] <= R[rC].
@ IGREATER_EQUAL
Pop b, pop a, push a >= b.
@ SYSTEM_ERR
Call system function and push stderr.
@ SQRT_R
R[rA] = sqrt(R[rB]).
@ ISUB_R
R[rA] = R[rB] - R[rC].
@ FLDIV_R
R[rA] = R[rB] / R[rC].
@ COS_R
R[rA] = cos(R[rB]).
@ CALL_NATIVE
Call a native function: operand is index of function name in constants.
@ ISUBTRACT
Pop b, pop a, push a - b.
@ FLMODULO
Pop b, pop a, push a % b.
@ NEW_STRUCT_INSTANCE_STATIC
Create new struct instance using struct section metadata (structIndex).
@ IDIVIDE
Pop b, pop a, push a / b.
@ STORE_VAR
Pop top of stack, store in variable slot.
@ ILE_R
R[rA] = R[rB] <= R[rC].
@ FLDIVIDE
Pop b, pop a, push a / b.
@ POW_R
R[rA] = pow(R[rB], R[rC]).
@ IMODULO
Pop b, pop a, push a % b.
@ FLAND_R
R[rA] = R[rB] && R[rC].
@ JUMP_IF_FALSE
Jump if top of stack is false (pops value).
@ INOT_EQUAL
Pop b, pop a, push a != b.
@ FLEQ_R
R[rA] = R[rB] == R[rC].
@ LOAD_VAR
Push variable value onto stack.
@ IOR
Pop b, pop a, push a || b.
@ EXP_R
R[rA] = exp(R[rB]).
@ RETURN
Return from function.
@ STORE_VAR_R
Store register to varible: variables[immediate] = R[rA].
@ READLINE
Read line from input and push onto stack.
@ IGT_R
R[rA] = R[rB] > R[rC].
@ FLSUBTRACT
Pop b, pop a, push a - b.
@ IADD_R
R[rA] = R[rB] + R[rC].
@ IDIV_R
R[rA] = R[rB] / R[rC].
@ FLLESS_EQUAL
Pop b, pop a, push a <= b.
@ SYSTEM_ERR_R
Run shell command and get output: system_out(R[rA], R[rB]).
@ FLMULTIPLY
Pop b, pop a, push a * b.
@ ILESS_EQUAL
Pop b, pop a, push a <= b.
@ PRINT_R
Print register: print(R[rA]).
@ PRINTERROR
Pop top of stack and print to stderr.
@ TAN_R
R[rA] = tan(R[rB]).
@ FLSUB_R
R[rA] = R[rB] - R[rC].
@ JUMP_BACK
Jump backwards (for loops).
@ FLOR_R
R[rA] = R[rB] || R[rC].
@ CALL
Call a user function: operand is index of function name in constants.
@ INE_R
R[rA] = R[rB] != R[rC].
@ FLNE_R
R[rA] = R[rB] != R[rC].
@ FLGREATER_THAN
Pop b, pop a, push a > b.
@ ILESS_THAN
Pop b, pop a, push a < b.
@ GET_FIELD
Pop struct, pop field name, push field value.
@ SIN_R
R[rA] = sin(R[rB]).
@ IMPORT
Import a module: operand is index of module path in constants.
@ LOAD_VAR_R
Load variable to register: R[rA] = variables[immediate].
@ FLLESS_THAN
Pop b, pop a, push a < b.
@ NEW_STRUCT
Create new struct: operand is index of struct name in constants.
@ IOR_R
R[rA] = R[rB] || R[rC].
@ ILT_R
R[rA] = R[rB] < R[rC].
@ IMOD_R
R[rA] = R[rB] % R[rC].
@ PRINT
Pop top of stack and print.
@ SYSTEM
Call a system function: operand is index of function name in constants.
@ IEQ_R
R[rA] = R[rB] == R[rC].
@ IGE_R
R[rA] = R[rB] >= R[rC].
static void writeIRValue(std::stringstream &ss, const Value &val, const std::function< std::string(const std::string &)> &escape)
Complete bytecode structure.
std::unordered_map< std::string, int > variables
Variable name -> index mapping.
std::vector< Instruction > instructions
List of instructions.
std::unordered_map< std::string, int > functionEntries
Function name -> instruction index mapping.
int nextVarIndex
Next available variable index.
std::vector< Value > constants
Constant pool.
std::vector< StructInfo > structs
List of struct descriptors.
std::unordered_map< std::string, int > structEntries
Struct name -> index in structs.
Struct metadata stored alongside bytecode (struct section).
int firstConstIndex
Index into constants for the first default value.
std::vector< std::string > fieldNames
Field names in declaration order.
int fieldCount
Number of fields in this struct.
std::string name
Struct name.