# Mu - subx/038---literal_strings.cc

      1 //: Allow instructions to mention literals directly.
      2 //:
      3 //: This layer will transparently move them to the global segment (assumed to
      4 //: always be the second segment).
      5
      6 :(scenario transform_literal_string)
      7 == code
      8 b8/copy  "test"/imm32
      9 == data  # need to manually create this for now
     10 +transform: -- move literal strings to data segment
     11 +transform: adding global variable '__subx_global_1' containing "test"
     12 +transform: instruction after transform: 'b8 __subx_global_1'
     13
     14 //: We don't rely on any transforms running in previous layers, but this layer
     15 //: knows about labels and global variables and will emit them for previous
     16 //: layers to transform.
     17 :(after "Begin Transforms")
     18 // Begin Level-3 Transforms
     19 Transform.push_back(transform_literal_strings);
     20 // End Level-3 Transforms
     21
     22 :(before "End Globals")
     23 int Next_auto_global = 1;
     24 :(code)
     25 void transform_literal_strings(program& p) {
     26   trace(99, "transform") << "-- move literal strings to data segment" << end();
     27   if (p.segments.empty()) return;
     28   segment& code = p.segments.at(0);
     29   segment data;
     30   for (int i = 0;  i < SIZE(code.lines);  ++i) {
     31     line& inst = code.lines.at(i);
     32     for (int j = 0;  j < SIZE(inst.words);  ++j) {
     33       word& curr = inst.words.at(j);
     34       if (curr.data.at(0) != '"') continue;
     35       ostringstream global_name;
     36       global_name << "__subx_global_" << Next_auto_global;
     37       ++Next_auto_global;
     38       add_global_to_data_segment(global_name.str(), curr, data);
     39       curr.data = global_name.str();
     40     }
     41     trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
     42   }
     43   if (data.lines.empty()) return;
     44   if (SIZE(p.segments) < 2) {
     45     p.segments.resize(2);
     46     p.segments.at(1).lines.swap(data.lines);
     47   }
     48   vector<line>& existing_data = p.segments.at(1).lines;
     49   existing_data.insert(existing_data.end(), data.lines.begin(), data.lines.end());
     50 }
     51
     52 void add_global_to_data_segment(const string& name, const word& value, segment& data) {
     53   trace(99, "transform") << "adding global variable '" << name << "' containing " << value.data << end();
     54   // emit label
     55   data.lines.push_back(label(name));
     56   // emit size for size-prefixed array
     57   data.lines.push_back(line());
     58   emit_hex_bytes(data.lines.back(), SIZE(value.data)-/*skip quotes*/2, 4/*bytes*/);
     59   // emit data byte by byte
     60   data.lines.push_back(line());
     61   line& curr = data.lines.back();
     62   for (int i = /*skip start quote*/1;  i < SIZE(value.data)-/*skip end quote*/1;  ++i) {
     63     char c = value.data.at(i);
     64     curr.words.push_back(word());
     65     curr.words.back().data = hex_byte_to_string(c);
     66     curr.words.back().metadata.push_back(string(1, c));
     67   }
     68 }
     69
     70 line label(string s) {
     71   line result;
     72   result.words.push_back(word());
     73   result.words.back().data = (s+":");
     74   return result;
     75 }
     76
     77 //: Within strings, whitespace is significant. So we need to redo our instruction
     78 //: parsing.
     79
     80 :(scenarios parse_instruction_character_by_character)
     81 :(scenario instruction_with_string_literal)
     82 a "abc  def" z  # two spaces inside string
     83 +parse2: word: a
     84 +parse2: word: "abc  def"
     85 +parse2: word: z
     86 # no other words
     87 $parse2: 3
     88
     89 :(before "End Line Parsing Special-cases(line_data -> l)")
     90 if (line_data.find('"') != string::npos) {  // can cause false-positives, but we can handle them
     91   parse_instruction_character_by_character(line_data, l);
     92   continue;
     93 }
     94
     95 :(code)
     96 void parse_instruction_character_by_character(const string& line_data, vector<line>& out) {
     97   // parse literals
     98   istringstream in(line_data);
     99   in >> std::noskipws;
    100   line result;
    101   // add tokens (words or strings) one by one
    102   while (has_data(in)) {
    103     skip_whitespace(in);
    104     if (!has_data(in)) break;
    105     char c = in.get();
    106     if (c == '#') break;  // comment; drop rest of line
    107     if (c == ':') break;  // line metadata; skip for now
    108     if (c == '.') {
    109       if (!has_data(in)) break;  // comment token at end of line
    110       if (isspace(in.peek()))
    111         continue;  // '.' followed by space is comment token; skip
    112     }
    113     ostringstream w;
    114     w << c;
    115     if (c == '"') {
    116       // slurp until '"'
    117       while (has_data(in)) {
    118         in >> c;
    119         w << c;
    120         if (c == '"') break;
    121       }
    122     }
    123     // slurp any remaining characters until whitespace
    124     while (!isspace(in.peek()) && has_data(in)) {  // peek can sometimes trigger eof(), so do it first
    125       in >> c;
    126       w << c;
    127     }
    128     result.words.push_back(word());
    129     parse_word(w.str(), result.words.back());
    130     trace(99, "parse2") << "word: " << to_string(result.words.back()) << end();
    131   }
    132   if (!result.words.empty())
    133     out.push_back(result);
    134 }
    135
    136 void skip_whitespace(istream& in) {
    137   while (true) {
    138     if (has_data(in) && isspace(in.peek())) in.get();
    139     else break;
    140   }
    141 }
    142
    143 void skip_comment(istream& in) {
    144   if (has_data(in) && in.peek() == '#') {
    145     in.get();
    146     while (has_data(in) && in.peek() != '\n') in.get();
    147   }
    148 }
    149
    150 // helper for tests
    151 void parse_instruction_character_by_character(const string& line_data) {
    152   vector<line> out;
    153   parse_instruction_character_by_character(line_data, out);
    154 }
    155
    156 :(scenario parse2_comment_token_in_middle)
    157 a . z
    158 +parse2: word: a
    159 +parse2: word: z
    160 -parse2: word: .
    161 # no other words
    162 $parse2: 2
    163
    164 :(scenario parse2_word_starting_with_dot)
    165 a .b c
    166 +parse2: word: a
    167 +parse2: word: .b
    168 +parse2: word: c
    169
    170 :(scenario parse2_comment_token_at_start)
    171 . a b
    172 +parse2: word: a
    173 +parse2: word: b
    174 -parse2: word: .
    175
    176 :(scenario parse2_comment_token_at_end)
    177 a b .
    178 +parse2: word: a
    179 +parse2: word: b
    180 -parse2: word: .
    181
    182 :(scenario parse2_word_starting_with_dot_at_start)
    183 .a b c
    184 +parse2: word: .a
    185 +parse2: word: b
    186 +parse2: word: c
    187
    188 :(scenario parse2_metadata)
    189 .a b/c d
    190 +parse2: word: .a
    191 +parse2: word: b /c
    192 +parse2: word: d
    193
    194 :(scenario parse2_string_with_metadata)
    195 a "bc  def"/disp32 g
    196 +parse2: word: a
    197 +parse2: word: "bc  def" /disp32
    198 +parse2: word: g
    199
    200 :(scenario parse2_string_with_metadata_at_end)
    201 a "bc  def"/disp32
    202 +parse2: word: a
    203 +parse2: word: "bc  def" /disp32
    204
    205 :(code)
    206 void test_parse2_string_with_metadata_at_end_of_line_without_newline() {
    207   parse_instruction_character_by_character(
    208       "68/push \"test\"/f"  // no newline, which is how calls from parse() will look
    209   );
    210   CHECK_TRACE_CONTENTS(
    211       "parse2: word: 68 /push^D"
    212       "parse2: word: \"test\" /f^D"
    213   );
    214 }