Module: JsonCompleter::CompletionEngine

Included in:
JsonCompleter
Defined in:
lib/json_completer/completion_engine.rb

Instance Method Summary collapse

Instance Method Details

#complete(partial_json) ⇒ Object



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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/json_completer/completion_engine.rb', line 5

def complete(partial_json)
  input = partial_json
  # Same byte-oriented trick as parse: compare ASCII JSON syntax as integers and avoid
  # allocating transient 1-character strings in the streaming loop.
  input_length = input.bytesize

  if @state.nil? || @state.input_length > input_length
    @state = ParsingState.new
  end

  return input if input.empty?
  return input if valid_json_primitive_or_document?(input)

  if @state.input_length == input_length && !@state.output_tokens.empty?
    return finalize_completion(@state.output_tokens.dup, @state.context_stack.dup, @state.incomplete_string_token)
  end

  output_tokens = @state.output_tokens.dup
  context_stack = @state.context_stack.dup
  index = @state.last_index
  incomplete_string_token = @state.incomplete_string_token

  if incomplete_string_token && output_tokens.last&.start_with?('"') && output_tokens.last.end_with?('"')
    output_tokens.pop
  end

  while index < input_length
    if incomplete_string_token && index == @state.last_index
      index, status = Scanners.scan_string(input, index, incomplete_string_token)

      break unless %i[terminated invalid_unicode].include?(status)

      output_tokens << incomplete_string_token.buffer.string
      incomplete_string_token = nil

      next
    end

    byte = input.getbyte(index)
    last_significant_char_in_output = get_last_significant_char(output_tokens)

    # ASCII byte values: 9/10/13/32 = whitespace, 34 = ", 44 = ,, 45 = -, 58 = :,
    # 91/93 = [] , 102/110/116 = f/n/t, 123/125 = {}.
    case byte
    when 9, 10, 13, 32
      output_tokens << input.byteslice(index, 1)
      index += 1
    when 34
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)

      string_token = Scanners::CompletionStringToken.new
      index, status = Scanners.scan_string(input, index + 1, string_token)

      if %i[terminated invalid_unicode].include?(status)
        output_tokens << string_token.buffer.string
      else
        incomplete_string_token = string_token
      end
    when 44
      remove_trailing_comma(output_tokens)
      output_tokens << ','
      index += 1
    when 45, 48..57
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)

      num_str, consumed = Scanners.scan_number_literal(input, index)
      output_tokens << num_str
      index += consumed
    when 58
      remove_trailing_comma(output_tokens) if last_significant_char_in_output == ','
      output_tokens << ':'
      index += 1
    when 91
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)
      output_tokens << '['
      context_stack << '['
      index += 1
    when 93
      output_tokens << ']'
      context_stack.pop if !context_stack.empty? && context_stack.last == '['
      index += 1
    when 102
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)

      keyword_val, consumed = Scanners.scan_keyword_literal(input, index, KEYWORD_MAP['f'])
      output_tokens << keyword_val
      index += consumed
    when 110
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)

      keyword_val, consumed = Scanners.scan_keyword_literal(input, index, KEYWORD_MAP['n'])
      output_tokens << keyword_val
      index += consumed
    when 116
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)

      keyword_val, consumed = Scanners.scan_keyword_literal(input, index, KEYWORD_MAP['t'])
      output_tokens << keyword_val
      index += consumed
    when 123
      ensure_comma_before_new_item(output_tokens, context_stack, last_significant_char_in_output)
      ensure_colon_if_value_expected(output_tokens, context_stack, last_significant_char_in_output)
      output_tokens << '{'
      context_stack << '{'
      index += 1
    when 125
      remove_trailing_comma(output_tokens)
      output_tokens << '}'
      context_stack.pop if !context_stack.empty? && context_stack.last == '{'
      index += 1
    else
      index += 1
    end
  end

  @state = ParsingState.new(
    output_tokens: output_tokens,
    context_stack: context_stack,
    last_index: index,
    input_length: input_length,
    incomplete_string_token: incomplete_string_token
  )

  finalize_completion(output_tokens.dup, context_stack.dup, incomplete_string_token)
end