90 absl::string_view source) {
91 if (source.size() > kMaxSourceBytes) {
92 return absl::ResourceExhaustedError(
93 "Dungeon pattern source exceeds 64 KiB");
97 std::optional<uint32_t> cursor;
99 for (absl::string_view line : absl::StrSplit(source,
'\n')) {
101 line = absl::StripAsciiWhitespace(StripComment(line));
102 if (line.empty() || line ==
"lorom" || IsLabel(line))
104 const auto fail = [&](absl::string_view reason) {
105 return absl::InvalidArgumentError(
106 absl::StrFormat(
"Dungeon pattern line %d: %s", line_number, reason));
108 const size_t split = line.find_first_of(
" \t");
109 const auto command = line.substr(0, split);
110 const auto operand = split == absl::string_view::npos
111 ? absl::string_view{}
112 : absl::StripAsciiWhitespace(line.substr(split));
113 if (command ==
"org") {
114 const auto address = HexLiteral(operand, 6);
115 if (!address || *address < 0x009B52 || *address > 0x00FFFE ||
116 (*address & 1) != 0) {
118 "org must name an aligned bank-00 object-data address "
119 "($009B52-$00FFFE)");
121 cursor = *address - 0x8000;
122 }
else if (command ==
"dw") {
124 return fail(
"dw requires an org first");
125 for (absl::string_view token : absl::StrSplit(operand,
',')) {
126 const auto word = HexLiteral(token, 4);
128 return fail(
"dw requires literal $0000-$FFFF words");
129 if (*cursor < kFirstPatternPc || *cursor + 2 > kPatternBankEnd) {
130 return fail(
"tile words cross the bank-00 object-data boundary");
132 if (!result.
words.emplace(*cursor,
static_cast<uint16_t
>(*word))
134 return fail(
"tile words overlap an earlier declaration");
136 token = absl::StripAsciiWhitespace(token);
139 static_cast<size_t>(token.data() - source.data()),
143 }
else if (command ==
"assert") {
145 return fail(
"assert pc() requires an org first");
146 const auto address = ProgramCounterAssertion(operand);
149 "assert requires pc() == $00xxxx with an optional quoted "
150 "message; expressions are not supported");
152 const uint32_t actual = *cursor + 0x8000;
153 if (*address != actual) {
154 return fail(absl::StrFormat(
155 "assert pc() failed: expected $%06X, current address $%06X",
160 "unsupported statement; only labels, lorom, org, literal dw "
161 "and assert pc() == $00xxxx are supported");
164 if (result.
words.empty()) {
165 return absl::InvalidArgumentError(
166 "Dungeon pattern source contains no tile words");
172 const std::string& path) {
173 std::error_code error;
174 const auto size = std::filesystem::file_size(path, error);
176 return absl::NotFoundError(
"Cannot read dungeon pattern source: " + path);
177 if (size > kMaxSourceBytes) {
178 return absl::ResourceExhaustedError(
179 "Dungeon pattern source exceeds 64 KiB: " + path);
181 std::ifstream file(path, std::ios::binary);
183 return absl::NotFoundError(
"Cannot open dungeon pattern source: " + path);
185 std::string text(kMaxSourceBytes + 1,
'\0');
186 file.read(text.data(),
static_cast<std::streamsize
>(text.size()));
187 text.resize(
static_cast<size_t>(file.gcount()));
189 return absl::DataLossError(
"Cannot read dungeon pattern source: " + path);
192 return absl::Status(result.status().code(),
193 path +
": " + std::string(result.status().message()));
195 const auto canonical_path = std::filesystem::canonical(path, error);
197 return absl::NotFoundError(
"Cannot resolve dungeon pattern source: " +
199 result->source_path = canonical_path.string();
205 const std::map<uint32_t, uint16_t>& replacements) {
207 return absl::FailedPreconditionError(
"Pattern source has no file path");
211 {
"dungeon tile patterns",
"dungeon pattern source"});
213 return lock.status();
216 return current.status();
217 if (current->source_text != expected.
source_text) {
218 return absl::AbortedError(
219 "Wall pattern source changed after opening; reopen before applying");
221 std::map<size_t, std::pair<size_t, uint16_t>, std::greater<size_t>> edits;
222 for (
const auto& [pc, word] : replacements) {
223 const auto token = current->tokens.find(pc);
224 if (token == current->tokens.end()) {
225 return absl::FailedPreconditionError(
226 "Tile is not owned by this pattern source");
228 edits.emplace(token->second.offset,
229 std::make_pair(token->second.length, word));
231 std::string after = current->source_text;
232 for (
const auto& [offset, edit] : edits) {
233 after.replace(offset, edit.first, absl::StrFormat(
"$%04X", edit.second));
237 return parsed.status();
238 auto expected_words = current->words;
239 for (
const auto& [pc, word] : replacements)
240 expected_words.at(pc) = word;
241 if (parsed->words != expected_words) {
242 return absl::DataLossError(
"Pattern edit changed unrelated tile words");