-- text editor, particularly text drawing, horizontal wrap, vertical scrolling Text = {} require 'search' require 'select' require 'undo' require 'text_tests' -- draw a line starting from startpos to screen at y between State.left and State.right -- return the final y, and position of start of final screen line drawn function Text.draw(State, line_index, y, startpos) --? print('text.draw', line_index) App.color(Text_color) local line = State.lines[line_index] local line_cache = State.text_line_cache[line_index] line_cache.starty = y line_cache.startpos = startpos -- wrap long lines local x = State.left local pos = 1 local screen_line_starting_pos = 1 if line_cache.fragments == nil then Text.compute_fragments(State, line_index) end Text.populate_screen_line_starting_pos(State, line_index) --? print('--') for _, f in ipairs(line_cache.fragments) do local frag, frag_text = f.data, f.text -- render fragment local frag_width = App.width(frag_text) local frag_len = utf8.len(frag) --? local s=tostring --? print('('..s(x)..','..s(y)..') '..frag..'('..s(frag_width)..' vs '..s(right)..') '..s(line_index)..' vs '..s(State.screen_top1.line)..'; '..s(pos)..' vs '..s(State.screen_top1.pos)..'; bottom: '..s(State.screen_bottom1.line)..'/'..s(State.screen_bottom1.pos)) if x + frag_width > State.right then assert(x > State.left) -- no overfull lines -- update y only after drawing the first screen line of screen top if Text.lt1(State.screen_top1, {line=line_index, pos=pos}) then y = y + State.line_height if y + State.line_height > App.screen.height then --? print('b', y, App.screen.height, '=>', screen_line_starting_pos) return y, screen_line_starting_pos end screen_line_starting_pos = pos --? print('text: new screen line', y, App.screen.height, screen_line_starting_pos) end x = State.left end --? print('checking to draw', pos, State.screen_top1.pos) -- don't draw text above screen top if Text.le1(State.screen_top1, {line=line_index, pos=pos}) then if State.selection1.line then local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len) Text.draw_highlight(State, line, x,y, pos, lo,hi) end --? print('drawing '..frag) App.screen.draw(frag_text, x,y) end -- render cursor if necessary if line_index == State.cursor1.line then if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then if State.search_term then if State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then local lo_px = Text.draw_highlight(line, x,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)) App.color(Text_color) love.graphics.print(State.search_term, x+lo_px,y) end else Text.draw_cursor(State, x+Text.x(frag, State.cursor1.pos-pos+1), y) end end end x = x + frag_width pos = pos + frag_len end if State.search_term == nil then if line_index == State.cursor1.line and State.cursor1.pos == pos then Text.draw_cursor(State, x, y) end end return y, screen_line_starting_pos end -- manual tests: -- draw with small screen width of 100 function Text.draw_cursor(State, x, y) -- blink every 0.5s if math.floor(Cursor_time*2)%2 == 0 then App.color(Cursor_color) love.graphics.rectangle('fill', x,y, 3,State.line_height) App.color(Text_color) end State.cursor_x = x State.cursor_y = y+State.line_height end function Text.compute_fragments(State, line_index) --? print('compute_fragments', State.right) local line = State.lines[line_index] local line_cache = State.text_line_cache[line_index] line_cache.fragments = {} local x = State.left -- try to wrap at word boundaries for frag in line.data:gmatch('%S*%s*') do local frag_text = App.newText(love.graphics.getFont(), frag) local frag_width = App.width(frag_text) --? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go') --? print('frag: ^'..frag..'$ is '..tostring(frag_width)..'px wide') if x + frag_width > State.right then while x + frag_width > State.right do --? print(x, frag, frag_width, State.right) if x < 0.8*State.right then --? print(frag, x, frag_width, State.right) -- long word; chop it at some letter -- We're not going to reimplement TeX here.
//: Support literal non-integers.
:(scenarios load)
:(scenario noninteger_literal)
def main [
1:number <- copy 3.14159
]
+parse: ingredient: {3.14159: "literal-fractional-number"}
:(after "Parsing reagent(string s)")
if (is_noninteger(s)) {
name = s;
type = new type_tree("literal-fractional-number", 0);
set_value(to_double(s));
return;
}
:(code)
bool is_noninteger(const string& s) {
return s.find_first_not_of("0123456789-.") == string::npos // no other characters
&& s.find_first_of("0123456789") != string::npos // at least one digit
&& s.find('-', 1) == string::npos // '-' only at first position
&& std::count(s.begin(), s.end(), '.') == 1; // exactly one decimal point
}
double to_double(string n) {
char* end = NULL;
// safe because string.c_str() is guaranteed to be null-terminated
double result = strtod(n.c_str(), &end);
assert(*end == '\0');
return result;
}
void test_is_noninteger() {
CHECK(!is_noninteger("1234"));
CHECK(!is_noninteger("1a2"));
CHECK(is_noninteger("234.0"));
CHECK(!is_noninteger("..."));
CHECK(!is_noninteger("."));
CHECK(is_noninteger("2."));
CHECK(is_noninteger(".2"));
CHECK(is_noninteger("-.2"));
CHECK(is_noninteger("-2."));
CHECK(!is_noninteger("--.2"));
CHECK(!is_noninteger(".-2"));
CHECK(!is_noninteger("..2"));
}