1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#pragma once
/// @file wasm-codec.hh -- codec wire-format encoder for the Nix AST.
///
/// Encodes a post-finalize Expr tree into the lix codec wire format so the Go
/// evaluator can decode it without JSON parsing.
///
/// Wire format recap (see gonix/codec/codec.go):
///   message  = total_len ":" value
///   value    = record | list | variant | scalar
///   record   = "{" *( klen ":" kbytes "," vlen ":" vbytes ";" ) "}"
///   list     = "[" *( len ":" item ";" ) "]"
///   variant  = "<" taglen ":" tagbytes value
///   scalar   = len ":" rawbytes
///
/// Each Expr node encodes as a variant whose tag is the node type name and
/// whose payload is a record of its fields.

#include "lix/libexpr/nixexpr.hh"
#include "lix/libexpr/pos-table.hh"
#include "lix/libexpr/symbol-table.hh"

#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>

namespace nix::wasm_codec {

// ---- low-level encoding primitives ----

/// len ":" rawbytes
inline std::string encScalar(std::string_view s)
{
    return std::to_string(s.size()) + ":" + std::string(s);
}

/// 4 bytes little-endian uint32
inline std::string encUint32(uint32_t v)
{
    char buf[4];
    memcpy(buf, &v, 4);
    return std::string("4:") + std::string(buf, 4);
}

/// 8 bytes little-endian int64
inline std::string encInt64(int64_t v)
{
    char buf[8];
    memcpy(buf, &v, 8);
    return std::string("8:") + std::string(buf, 8);
}

/// 8 bytes little-endian float64
inline std::string encFloat64(double v)
{
    char buf[8];
    memcpy(buf, &v, 8);
    return std::string("8:") + std::string(buf, 8);
}

/// 1 byte boolean
inline std::string encBool(bool b)
{
    // Must use the (ptr, len) constructor — "1:\x00" via C-string stops at NUL.
    if (b) {
        return std::string("1:\x01", 3);
    } else {
        return std::string("1:\x00", 3);
    }
}

/// record field: klen ":" kbytes "," vlen ":" vbytes ";"
inline std::string field(std::string_view key, std::string_view encodedValue)
{
    return std::to_string(key.size()) + ":" + std::string(key) + "," + std::to_string(encodedValue.size())
        + ":" + std::string(encodedValue) + ";";
}

/// "{" fields "}"
inline std::string record(std::string_view fields)
{
    return "{" + std::string(fields) + "}";
}

/// "<" taglen ":" tag payload
inline std::string variant(std::string_view tag, std::string_view encodedPayload)
{
    return "<" + std::to_string(tag.size()) + ":" + std::string(tag) + std::string(encodedPayload);
}

/// "[" *( len ":" item ";" ) "]"
inline std::string list(std::string_view encodedItems)
{
    return "[" + std::string(encodedItems) + "]";
}

/// single list item: len ":" item ";"
inline std::string listItem(std::string_view encodedItem)
{
    return std::to_string(encodedItem.size()) + ":" + std::string(encodedItem) + ";";
}

/// total_len ":" inner
inline std::string message(std::string_view inner)
{
    return std::to_string(inner.size()) + ":" + std::string(inner);
}

// ---- AST encoding ----

/// Encode the given expression tree into the codec wire format.
/// Returns the raw variant-encoded value (no top-level length prefix).
/// Wrap with message() before writing to a response buffer.
/// positions is used to resolve PosIdx values to {file, line, column}.
/// filePath, if non-empty, is used as the file name for positions whose
/// origin is Pos::String (i.e. parsed from a string, not a file on disk).
std::string encodeExpr(
    const Expr & e, const SymbolTable & symbols, const PosTable & positions, std::string_view filePath = {}
);

} // namespace nix::wasm_codec