ic_agent 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ require 'cbor'
2
+ require 'ctf_party'
3
+
4
+ module IcAgent
5
+ class Request
6
+ def self.sign_request(req, iden)
7
+ req_id = IcAgent::Utils.to_request_id(req)
8
+ msg = IcAgent::IC_REQUEST_DOMAIN_SEPARATOR + req_id
9
+ sig = iden.sign(msg)
10
+ envelop = {
11
+ 'content': req,
12
+ 'sender_pubkey': sig[0],
13
+ 'sender_sig': sig[1]
14
+ }
15
+
16
+ if iden.is_a?(DelegateIdentity)
17
+ envelop.update({
18
+ 'sender_pubkey': iden.der_pubkey,
19
+ 'sender_delegation': iden.delegations
20
+ })
21
+ end
22
+ [req_id, CBOR.encode(envelop)]
23
+ end
24
+ end
25
+
26
+ class Agent
27
+ attr_accessor :identity, :client, :ingress_expiry, :root_key, :nonce_factory
28
+
29
+ def initialize(identity, client, nonce_factory = nil, ingress_expiry = 300, root_key = IcAgent::IC_ROOT_KEY)
30
+ @identity = identity
31
+ @client = client
32
+ @ingress_expiry = ingress_expiry
33
+ @root_key = root_key
34
+ @nonce_factory = nonce_factory
35
+ end
36
+
37
+ def get_principal
38
+ @identity.sender
39
+ end
40
+
41
+ def get_expiry_date
42
+ ((Time.now.to_i + @ingress_expiry) * 10**9).to_i
43
+ end
44
+
45
+ def query_endpoint(canister_id, data)
46
+ ret = @client.query(canister_id, data)
47
+ decode_ret = nil
48
+ begin
49
+ decode_ret = CBOR.decode(ret)
50
+ rescue CBOR::MalformedFormatError
51
+ decode_ret = ret
52
+ # print logger
53
+ end
54
+ decode_ret
55
+ end
56
+
57
+ def call_endpoint(canister_id, request_id, data)
58
+ @client.call(canister_id, request_id, data)
59
+ request_id
60
+ end
61
+
62
+ def read_state_endpoint(canister_id, data)
63
+ result = @client.read_state(canister_id, data)
64
+ result
65
+ end
66
+
67
+ def query_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil)
68
+ req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
69
+ req = {
70
+ 'request_type' => 'query',
71
+ 'sender' => @identity.sender.bytes,
72
+ 'canister_id' => req_canister_id,
73
+ 'method_name' => method_name,
74
+ 'arg' => arg.hex2str,
75
+ 'ingress_expiry' => get_expiry_date
76
+ }
77
+
78
+ _, data = Request.sign_request(req, @identity)
79
+ query_canister_id = effective_canister_id.nil? ? canister_id : effective_canister_id
80
+ result = query_endpoint(query_canister_id, data)
81
+ raise Exception, "Malformed result: #{result}" unless result.is_a?(CBOR::Tagged) && result.value.key?('status')
82
+
83
+ if result.value['status'] == 'replied'
84
+ arg = result.value['reply']['arg']
85
+ if arg[0..3] == 'DIDL'
86
+ IcAgent::Candid.decode(arg.to_hex, return_type)
87
+ else
88
+ arg
89
+ end
90
+ elsif result.value['status'] == 'rejected'
91
+ raise Exception, "Canister reject the call: #{result['reject_message']}"
92
+ end
93
+ end
94
+
95
+ def update_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil, **kwargs)
96
+ req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
97
+ req = {
98
+ 'request_type' => 'call',
99
+ 'sender' => @identity.sender.bytes,
100
+ 'canister_id' => req_canister_id,
101
+ 'method_name' => method_name,
102
+ 'arg' => arg.hex2str,
103
+ 'ingress_expiry' => get_expiry_date
104
+ }
105
+ req_id, data = Request.sign_request(req, @identity)
106
+ eid = effective_canister_id.nil? ? canister_id : effective_canister_id
107
+ _ = call_endpoint(eid, req_id, data)
108
+ status, result = poll(eid, req_id, **kwargs)
109
+ if status == 'rejected'
110
+ raise Exception, "Rejected: #{result.to_s}"
111
+ elsif status == 'replied'
112
+ if result[0..3] == 'DIDL'
113
+ IcAgent::Candid.decode(result.to_hex, return_type)
114
+ else
115
+ # Some canisters don't use DIDL (e.g. they might encode using json instead)
116
+ result
117
+ end
118
+ else
119
+ raise Exception, "Timeout to poll result, current status: #{status.to_s}"
120
+ end
121
+ end
122
+
123
+ def read_state_raw(canister_id, paths)
124
+ req = {
125
+ 'request_type' => 'read_state',
126
+ 'sender' => @identity.sender.bytes,
127
+ 'paths' => paths,
128
+ 'ingress_expiry' => get_expiry_date
129
+ }
130
+ _, data = Request.sign_request(req, @identity)
131
+ ret = read_state_endpoint(canister_id, data)
132
+ if ret == 'Invalid path requested.'
133
+ raise ValueError, 'Invalid path requested!'
134
+ elsif ret == 'Could not parse body as read request: invalid type: byte array, expected a sequence'
135
+ raise ValueError, 'Could not parse body as read request: invalid type: byte array, expected a sequence'
136
+ end
137
+
138
+ begin
139
+ d = CBOR.decode(ret)
140
+ rescue StandardError
141
+ raise ValueError, "Unable to decode cbor value: #{ret}"
142
+ end
143
+ CBOR.decode(d.value['certificate'])
144
+ end
145
+
146
+ def request_status_raw(canister_id, req_id)
147
+ paths = [['request_status', req_id]]
148
+ cert = read_state_raw(canister_id, paths)
149
+ status = IcAgent::Certificate.lookup(['request_status', req_id, 'status'], cert)
150
+ [status, cert]
151
+ end
152
+
153
+ def poll(canister_id, req_id, delay = 1, timeout = IcAgent::DEFAULT_POLL_TIMEOUT_SECS)
154
+ status = nil
155
+ cert = nil
156
+ (timeout / delay).to_i.times do
157
+ status, cert = request_status_raw(canister_id, req_id)
158
+ break if %w[replied done rejected].include?(status)
159
+
160
+ sleep(delay)
161
+ end
162
+
163
+ if status == 'replied'
164
+ path = ['request_status', req_id, 'reply']
165
+ res = IcAgent::Certificate.lookup(path, cert)
166
+ [status, res]
167
+ elsif status == 'rejected'
168
+ path = ['request_status', req_id, 'reject_message']
169
+ msg = IcAgent::Certificate.lookup(path, cert)
170
+ [status, msg]
171
+ else
172
+ [status, _]
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,192 @@
1
+ require 'ic_agent/candid'
2
+
3
+ module IcAgent
4
+ module Ast
5
+ class Assembler
6
+ TYPE_MAPPING = {}
7
+
8
+ def self.build_single_type(child_type)
9
+ IcAgent::Candid::BaseTypes.send(child_type)
10
+ end
11
+
12
+ def self.build_blob
13
+ IcAgent::Candid::BaseTypes.vec(IcAgent::Candid::BaseTypes.nat8)
14
+ end
15
+
16
+ def self.build_opt(child_type, key_types = {})
17
+ child_type = key_types[child_type].nil? ? build_type(child_type, key_types) : key_types[child_type]
18
+ IcAgent::Candid::BaseTypes.opt(child_type)
19
+ end
20
+
21
+ def self.build_vec(child_type, key_types = {})
22
+ child_type = key_types[child_type].nil? ? build_type(child_type, key_types) : key_types[child_type]
23
+ IcAgent::Candid::BaseTypes.vec(child_type)
24
+ end
25
+
26
+ def self.build_record(child_hash, multi_types = {}, key_types = {})
27
+ child_types = {}
28
+ child_hash.each_key do |key|
29
+ if multi_types[child_hash[key].strip]
30
+ multi_type = build_type(multi_types[child_hash[key].strip], key_types, multi_types)
31
+ child_types[key] = multi_type
32
+ elsif key_types[child_hash[key].strip]
33
+ child_types[key] = key_types[child_hash[key].strip]
34
+ else
35
+ child_types[key] = build_type(child_hash[key], key_types, multi_types)
36
+ end
37
+ end
38
+ IcAgent::Candid::BaseTypes.record(child_types)
39
+ end
40
+
41
+ def self.build_variant(child_hash, multi_types = {}, key_types = {})
42
+ child_types = {}
43
+ child_hash.each_key do |key|
44
+ if multi_types[child_hash[key].strip]
45
+ multi_type = build_type(multi_types[child_hash[key].strip], multi_types)
46
+ child_types[key] = multi_type
47
+ elsif key_types[child_hash[key].strip]
48
+ child_types[key] = key_types[child_hash[key].strip]
49
+ else
50
+ child_types[key] = build_type(child_hash[key], key_types, multi_types)
51
+ end
52
+ end
53
+ IcAgent::Candid::BaseTypes.variant(child_types)
54
+ end
55
+
56
+ def self.build_type(type_str, key_types = {}, multi_types = {})
57
+ opt_code = get_opt_code(type_str)
58
+
59
+ if IcAgent::Candid::SINGLE_TYPES.include? opt_code
60
+ build_single_type(opt_code)
61
+ elsif opt_code == 'blob'
62
+ build_blob
63
+ elsif opt_code == 'opt'
64
+ type_str = recover_type(type_str, multi_types)
65
+ child_code = get_child_code(type_str, ' ')
66
+ build_opt(child_code, key_types)
67
+ elsif opt_code == 'vec'
68
+ type_str = recover_type(type_str, multi_types)
69
+ child_code = get_child_code(type_str, ' ')
70
+ build_vec(child_code, key_types)
71
+ elsif opt_code == 'record'
72
+ child_code = get_record_content(type_str)
73
+ pure_child_code, multi_types = replace_multi_type(child_code)
74
+ child_hash = {}
75
+ key_index = 0
76
+ pure_child_code.split(';').each do |item|
77
+ item_key, item_value = get_record_key_value(item, ' : ', key_index)
78
+ child_hash[item_key] = item_value
79
+ key_index += 1
80
+ end
81
+ build_record(child_hash, multi_types, key_types)
82
+ elsif opt_code == 'variant'
83
+ child_code = get_variant_content(type_str)
84
+ pure_child_code, multi_types = replace_multi_type(child_code)
85
+ child_hash = {}
86
+ pure_child_code.split(';').each do |item|
87
+ item_arr = item.strip.split(' : ')
88
+ child_hash[item_arr[0]] = (item_arr.size > 1 ? item_arr[1] : 'null')
89
+ end
90
+ build_variant(child_hash, multi_types, key_types)
91
+ end
92
+ end
93
+
94
+ def self.replace_last_occurrence(string, pattern, replacement)
95
+ last_index = string.rindex(pattern)
96
+ return string unless last_index
97
+
98
+ string[last_index..-1] = replacement
99
+ string
100
+ end
101
+
102
+ def self.get_record_content(record_str)
103
+ record_str = record_str.sub('record', '').sub('{', '')
104
+ record_str = replace_last_occurrence(record_str, '}', '')
105
+ record_str.strip
106
+ end
107
+
108
+ def self.get_variant_content(variant_str)
109
+ variant_str = variant_str.sub('variant', '').sub('{', '')
110
+ variant_str = replace_last_occurrence(variant_str, '}', '')
111
+ variant_str.strip
112
+ end
113
+
114
+ def self.get_record_key_value(item_str, index_str, key_index = 0)
115
+ first_index = item_str.index(index_str)
116
+ if first_index
117
+ key = item_str[0..first_index].strip
118
+ value = item_str[(first_index + index_str.size)..].strip
119
+ else
120
+ key = key_index.to_s
121
+ value = item_str.strip
122
+ end
123
+ return key, value
124
+ end
125
+
126
+ def self.get_opt_code(item_str)
127
+ opt_code = item_str.strip
128
+ opt_code.split(' ')[0]
129
+ end
130
+
131
+ def self.get_child_code(item_str, index_str)
132
+ first_index = item_str.index(index_str)
133
+ item_str[(first_index + index_str.size)..].strip
134
+ end
135
+
136
+ def self.replace_multi_type(type_str)
137
+ replaced_hash = {}
138
+ modified_str = type_str.gsub(/record\s*{[^{}]*}/) do |match|
139
+ rad_id = rand(100000..999999)
140
+ type_name = "record_#{rad_id}"
141
+ replaced_hash[type_name] = match
142
+ type_name
143
+ end
144
+
145
+ modified_str = modified_str.gsub(/variant\s*{[^{}]*}/) do |match|
146
+ rad_id = rand(100000..999999)
147
+ type_name = "variant_#{rad_id}"
148
+ replaced_hash[type_name] = match
149
+ type_name
150
+ end
151
+
152
+ return modified_str, replaced_hash
153
+ end
154
+
155
+ def self.get_params_refer_values(type_str)
156
+ pure_child_code, replaced_hash = replace_multi_type(type_str)
157
+ key_index = 0
158
+ value_arr = []
159
+ pure_child_code.split(';').each do |item|
160
+ _, item_value = get_record_key_value(item, ' : ', key_index)
161
+ value_arr << item_value.split(' ')
162
+ key_index += 1
163
+ end
164
+
165
+ replaced_hash.each_key do |key|
166
+ item_arr = replaced_hash[key].strip.split(';')
167
+ item_arr.each do |item|
168
+ multi_item_arr = item.strip.split(':')
169
+ if multi_item_arr.size > 1
170
+ item_value_arr = multi_item_arr[1].strip.split(' ').collect { |v| v.strip.gsub(';', '') }
171
+ item_value_arr.delete('{')
172
+ item_value_arr.delete('}')
173
+ item_value_arr.delete('{}')
174
+ value_arr << item_value_arr
175
+ else
176
+ value_arr << multi_item_arr[1].strip
177
+ end
178
+ end
179
+ value_arr.delete(key)
180
+ end
181
+ value_arr.flatten.uniq - IcAgent::Candid::ALL_TYPES - replaced_hash.keys
182
+ end
183
+
184
+ def self.recover_type(type_str, multi_types)
185
+ multi_types.each_key do |key|
186
+ type_str = type_str.gsub(key, multi_types[key])
187
+ end
188
+ type_str
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,159 @@
1
+ grammar DIDGrammar
2
+
3
+ rule body
4
+ ( type_declaration / service_declaration / comment / end_of_line )* <IcAgent::Ast::Nodes::DIDFile>
5
+ end
6
+
7
+ rule base_type
8
+ 'bool' / 'text' / 'null' / 'reserved' / 'empty' / 'principal' / 'nat8' / 'nat16' / 'nat32' / 'nat64' / 'int8' / 'int16' / 'int32' / 'int64' / 'float32' / 'float64' /
9
+ 'opt' / 'vec' / 'record' / 'variant' / 'service' / 'func' / 'nat' / 'int' / blob
10
+ end
11
+
12
+ rule base_type_single
13
+ 'bool' / 'text' / 'null' / 'reserved' / 'empty' / 'principal' / 'nat8' / 'nat16' / 'nat32' / 'nat64' / 'int8' / 'int16' / 'int32' / 'int64' / 'float32' / 'float64' / 'nat' / 'int' / 'blob'
14
+ end
15
+
16
+ rule base_type_base
17
+ base_type_single+ <IcAgent::Ast::Nodes::BaseTypeSingle>
18
+ end
19
+
20
+ rule base_type_code
21
+ [a-zA-Z0-9_]+ <IcAgent::Ast::Nodes::BaseTypeOther>
22
+ end
23
+
24
+ rule base_type_key
25
+ [a-zA-Z0-9_]+ <IcAgent::Ast::Nodes::BaseTypeKey>
26
+ end
27
+
28
+ rule base_type_vec
29
+ 'vec' space ic_all_type <IcAgent::Ast::Nodes::BaseTypeVec>
30
+ end
31
+
32
+ rule base_type_opt
33
+ 'opt' space ic_all_type <IcAgent::Ast::Nodes::BaseTypeOpt>
34
+ end
35
+
36
+ rule base_type_record
37
+ 'record' space '{' space_and_line ic_base_content space_and_line '}' <IcAgent::Ast::Nodes::BaseTypeRecord>
38
+ end
39
+
40
+ rule base_type_record_null
41
+ 'record {}' <IcAgent::Ast::Nodes::BaseTypeRecord>
42
+ end
43
+
44
+ rule base_type_variant
45
+ 'variant' space '{' space_and_line ic_base_content space_and_line '}' <IcAgent::Ast::Nodes::BaseTypeVariant>
46
+ end
47
+
48
+ rule base_type_func
49
+ 'func' space? "(" service_method_params? ")" space? "->" space? "(" service_method_return_type ")" space? method_query? <IcAgent::Ast::Nodes::BaseTypeFunc>
50
+ end
51
+
52
+ rule ic_all_type
53
+ base_type_base / base_type_vec / base_type_record / base_type_record_null / base_type_variant / base_type_opt / base_type_func / base_type_code
54
+ end
55
+
56
+ rule ic_base_content
57
+ (ic_base_type)+ <IcAgent::Ast::Nodes::BaseTypeContent>
58
+ end
59
+
60
+ rule ic_base_type
61
+ space? base_type_key space? (':' space ic_all_type)* optional_semicolon <IcAgent::Ast::Nodes::BaseTypeChild>
62
+ end
63
+
64
+ rule type_declaration
65
+ 'type' space_and_line type_name space_and_line '=' space_and_line ic_all_type ';' end_of_line <IcAgent::Ast::Nodes::TypeDeclaration>
66
+ end
67
+
68
+ rule type_name
69
+ [a-zA-Z0-9_]+ <IcAgent::Ast::Nodes::TypeName>
70
+ end
71
+
72
+ rule comment
73
+ space? '//' space? rest_of_line <IcAgent::Ast::Nodes::Comment>
74
+ end
75
+
76
+ rule service_declaration
77
+ "service" space ":" space service_name* "{" end_of_line service_methods "}" <IcAgent::Ast::Nodes::Service>
78
+ end
79
+
80
+ rule service_name
81
+ "(" ic_service_name ")" space? "->" space?
82
+ end
83
+
84
+ rule ic_service_name
85
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceName>
86
+ end
87
+
88
+ rule service_methods
89
+ (service_item)+ <IcAgent::Ast::Nodes::IcServiceMethods>
90
+ end
91
+
92
+ rule service_item
93
+ space? service_method_name space? ":" space? "(" service_method_params? ")" space? "->" space? "(" service_method_return_type ")" space? method_query? ';' end_of_line? <IcAgent::Ast::Nodes::IcServiceItem>
94
+ end
95
+
96
+ rule service_method_params
97
+ param_element (", " param_element)* <IcAgent::Ast::Nodes::IcServiceMethodParams>
98
+ end
99
+
100
+ rule service_method_return_type
101
+ [a-zA-Z0-9_{}: ]* <IcAgent::Ast::Nodes::IcServiceMethodReturn>
102
+ end
103
+
104
+ rule method_query
105
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceMethodQuery>
106
+ end
107
+
108
+ rule words
109
+ [a-zA-Z_] [a-zA-Z0-9_]*
110
+ end
111
+
112
+ rule param_element
113
+ [a-zA-Z0-9_ ]+
114
+ end
115
+
116
+ rule service_method_name
117
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceMethodName>
118
+ end
119
+
120
+ rule string_without_spaces
121
+ [\S]* <IcAgent::Ast::Nodes::StringLiteral>
122
+ end
123
+
124
+ rule rest_of_line
125
+ [^\n]* <IcAgent::Ast::Nodes::StringLiteral>
126
+ end
127
+
128
+ rule end_of_line
129
+ [\n]+
130
+ end
131
+
132
+ rule block_record
133
+ '{}'
134
+ end
135
+
136
+ rule has_end_of_line
137
+ [\n]*
138
+ end
139
+
140
+ rule splite_code
141
+ ' : '
142
+ end
143
+
144
+ rule space_and_line
145
+ [\s\n]*
146
+ end
147
+
148
+ rule space
149
+ [\s]+
150
+ end
151
+
152
+ rule start_space
153
+ [^\s]+
154
+ end
155
+
156
+ rule optional_semicolon
157
+ [;]*
158
+ end
159
+ end
@@ -0,0 +1,111 @@
1
+ grammar DIDGrammar
2
+
3
+ rule body
4
+ ( type_declaration / service_declaration / comment / end_of_line )* <IcAgent::Ast::Nodes::DIDFile>
5
+ end
6
+
7
+ rule base_type
8
+ 'bool' / 'text' / 'null' / 'reserved' / 'empty' / 'principal' / 'nat' / 'nat8' / 'nat16' / 'nat32' / 'nat64' / 'int' / 'int8' / 'int16' / 'int32' / 'int64' / 'float32' / 'float64' /
9
+ 'opt' / 'vec' / 'record' / 'variant' / 'service' / 'func'
10
+ end
11
+
12
+ rule type_declaration
13
+ "type" space type_name space "=" space type_input_base_type space "{" end_of_line? type_body "};" end_of_line <IcAgent::Ast::Nodes::TypeDeclaration>
14
+ end
15
+
16
+ rule type_name
17
+ [a-zA-Z0-9_]+ <IcAgent::Ast::Nodes::TypeName>
18
+ end
19
+
20
+ rule type_body
21
+ type_body_item* <IcAgent::Ast::Nodes::TypeBody>
22
+ end
23
+
24
+ rule type_input_base_type
25
+ base_type+ <IcAgent::Ast::Nodes::TypeInputBaseType>
26
+ end
27
+
28
+ rule type_body_item
29
+ type_body_item_obj (';' space)* <IcAgent::Ast::Nodes::TypeBodyItem>
30
+ end
31
+
32
+ rule type_body_item_obj
33
+ type_body_item_name ':'? type_body_item_value? <IcAgent::Ast::Nodes::TypeBodyItemObj>
34
+ end
35
+
36
+ rule type_body_item_name
37
+ [a-zA-Z0-9_ ]+ <IcAgent::Ast::Nodes::TypeBodyItemName>
38
+ end
39
+
40
+ rule type_body_item_value
41
+ [a-zA-Z0-9_ ]+ <IcAgent::Ast::Nodes::TypeBodyItemValue>
42
+ end
43
+
44
+ rule body_item_element
45
+ [a-zA-Z0-9_: ]+
46
+ end
47
+
48
+ rule comment
49
+ space? '//' space? rest_of_line <IcAgent::Ast::Nodes::Comment>
50
+ end
51
+
52
+ rule service_declaration
53
+ "service" space ":" space "(" ic_service_name ")" space? "->" space? "{" end_of_line service_methods "}" <IcAgent::Ast::Nodes::Service>
54
+ end
55
+
56
+ rule ic_service_name
57
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceName>
58
+ end
59
+
60
+ rule service_methods
61
+ (service_item)+ <IcAgent::Ast::Nodes::IcServiceMethods>
62
+ end
63
+
64
+ rule service_item
65
+ space? service_method_name space? ":" space? "(" service_method_params? ")" space? "->" space? "(" service_method_return_type ")" space? method_query? ';' end_of_line? <IcAgent::Ast::Nodes::IcServiceItem>
66
+ end
67
+
68
+ rule service_method_params
69
+ param_element (", " param_element)* <IcAgent::Ast::Nodes::IcServiceMethodParams>
70
+ end
71
+
72
+ rule service_method_return_type
73
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceMethodReturn>
74
+ end
75
+
76
+ rule method_query
77
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceMethodQuery>
78
+ end
79
+
80
+ rule words
81
+ [a-zA-Z_] [a-zA-Z0-9_]*
82
+ end
83
+
84
+ rule param_element
85
+ [a-zA-Z0-9_ ]+
86
+ end
87
+
88
+ rule service_method_name
89
+ [a-zA-Z0-9_]* <IcAgent::Ast::Nodes::IcServiceMethodName>
90
+ end
91
+
92
+ rule string_without_spaces
93
+ [\S]* <IcAgent::Ast::Nodes::StringLiteral>
94
+ end
95
+
96
+ rule rest_of_line
97
+ [^\n]* <IcAgent::Ast::Nodes::StringLiteral>
98
+ end
99
+
100
+ rule end_of_line
101
+ [\n]+
102
+ end
103
+
104
+ rule space
105
+ [\s]+
106
+ end
107
+
108
+ rule start_space
109
+ [^\s]+
110
+ end
111
+ end