///|
fn find_block_span(
blocks : Array[@compiler.SfcBlock],
kind : @compiler.BlockKind,
) -> @compiler.SourceSpan {
for block in blocks {
if block.kind == kind {
return block.span
}
}
{ start: 0, end: 0 }
}
///|
fn ToolingDocument::find_block_content_span(
self : ToolingDocument,
blocks : Array[@compiler.SfcBlock],
kind : @compiler.BlockKind,
) -> @compiler.SourceSpan? {
for block in blocks {
if block.kind != kind {
continue
}
if block.content.length() == 0 {
return Some(block.span)
}
return self
.find_substring_in_span(block.content, block.span)
.map(fn(start) { { start, end: start + block.content.length() } })
}
None
}
///|
fn ToolingDocument::find_binding_definition_span(
self : ToolingDocument,
search_span : @compiler.SourceSpan,
binding : String,
) -> @compiler.SourceSpan? {
match self.find_substring_in_span("let " + binding, search_span) {
Some(start) => Some({ start: start + 4, end: start + 4 + binding.length() })
None => self.find_token_in_span(binding, search_span, search_span.start)
}
}
///|
fn ToolingDocument::find_field_definition_span(
self : ToolingDocument,
search_span : @compiler.SourceSpan,
name : String,
) -> @compiler.SourceSpan? {
match self.find_substring_in_span("\"" + name + "\"", search_span) {
Some(start) => Some({ start: start + 1, end: start + 1 + name.length() })
None =>
match self.find_substring_in_span("'" + name + "'", search_span) {
Some(start) =>
Some({ start: start + 1, end: start + 1 + name.length() })
None => self.find_token_in_span(name, search_span, search_span.start)
}
}
}
///|
fn ToolingDocument::find_member_refs(
self : ToolingDocument,
search_span : @compiler.SourceSpan,
contract_kind : String,
binding_name : String,
) -> Array[ToolingMemberRef] {
let refs : Array[ToolingMemberRef] = []
let mut cursor = search_span.start
while true {
match self.find_token_in_span(binding_name, search_span, cursor) {
Some(binding_span) => {
let member_span = self.member_span_after_binding(binding_span)
cursor = binding_span.end
match member_span {
Some(member_span) =>
refs.push({
contract_kind,
binding_name,
member_name: self.span_text(member_span),
binding_span,
member_span,
})
None => ()
}
}
None => break
}
}
refs
}
///|
fn ToolingDocument::find_resolved_template_ref_member_refs(
self : ToolingDocument,
search_span : @compiler.SourceSpan,
binding_name : String,
) -> Array[ToolingMemberRef] {
let refs : Array[ToolingMemberRef] = []
let mut cursor = search_span.start
while true {
match
self.find_substring_in_span_from(
"resolve_template_ref", search_span, cursor,
) {
Some(start) => {
cursor = start + "resolve_template_ref".length()
match self.parse_resolved_template_ref_member_ref(search_span, start) {
Some((resolved_binding_name, binding_span, member_span)) =>
if resolved_binding_name == binding_name {
refs.push({
contract_kind: "template ref",
binding_name,
member_name: self.span_text(member_span),
binding_span,
member_span,
})
}
None => ()
}
}
None => break
}
}
refs
}
///|
fn ToolingDocument::parse_resolved_template_ref_member_ref(
self : ToolingDocument,
search_span : @compiler.SourceSpan,
start : Int,
) -> (String, @compiler.SourceSpan, @compiler.SourceSpan)? {
let chars = self.chars
let mut cursor = start + "resolve_template_ref".length()
while cursor < search_span.end &&
(chars[cursor] == ' ' || chars[cursor] == '\t') {
cursor += 1
}
if cursor >= search_span.end || chars[cursor] != '(' {
return None
}
let open_paren = cursor
let close_paren = match
self.find_matching_paren_forward(open_paren, search_span.end) {
Some(index) => index
None => return None
}
let binding_span = match
self.trim_span_whitespace(open_paren + 1, close_paren) {
Some(span) => span
None => return None
}
let binding_name = self.span_text(binding_span)
if not(ToolingDocument::identifier_literal(binding_name)) {
return None
}
cursor = close_paren + 1
while cursor < search_span.end &&
(chars[cursor] == ' ' || chars[cursor] == '\t') {
cursor += 1
}
if cursor >= search_span.end || chars[cursor] != '.' {
return None
}
cursor += 1
while cursor < search_span.end &&
(chars[cursor] == ' ' || chars[cursor] == '\t') {
cursor += 1
}
if cursor >= search_span.end || not(is_identifier_char(chars[cursor])) {
return None
}
let member_start = cursor
while cursor < search_span.end && is_identifier_char(chars[cursor]) {
cursor += 1
}
Some((binding_name, binding_span, { start: member_start, end: cursor }))
}
///|
fn ToolingDocument::matching_open_paren_backward(
self : ToolingDocument,
close_paren : Int,
) -> Int? {
let chars = self.chars
let mut depth = 0
let mut cursor = close_paren + 1
while cursor > 0 {
cursor -= 1
match chars[cursor] {
')' => depth += 1
'(' => {
depth -= 1
if depth == 0 {
return Some(cursor)
}
}
_ => ()
}
}
None
}
///|
fn ToolingDocument::find_matching_paren_forward(
self : ToolingDocument,
open_paren : Int,
limit : Int,
) -> Int? {
let chars = self.chars
let mut depth = 0
let mut cursor = open_paren
while cursor < limit {
match chars[cursor] {
'(' => depth += 1
')' => {
depth -= 1
if depth == 0 {
return Some(cursor)
}
}
_ => ()
}
cursor += 1
}
None
}
///|
fn ToolingDocument::trim_span_whitespace(
self : ToolingDocument,
start : Int,
end_ : Int,
) -> @compiler.SourceSpan? {
let chars = self.chars
let mut trimmed_start = start
while trimmed_start < end_ &&
(chars[trimmed_start] == ' ' || chars[trimmed_start] == '\t') {
trimmed_start += 1
}
let mut trimmed_end = end_
while trimmed_end > trimmed_start &&
(chars[trimmed_end - 1] == ' ' || chars[trimmed_end - 1] == '\t') {
trimmed_end -= 1
}
if trimmed_start >= trimmed_end {
None
} else {
Some({ start: trimmed_start, end: trimmed_end })
}
}
///|
fn ToolingDocument::identifier_literal(source : String) -> Bool {
if source.length() == 0 {
return false
}
for ch in source {
if not(is_identifier_char(ch)) {
return false
}
}
true
}
///|
fn ToolingDocument::member_span_after_binding(
self : ToolingDocument,
binding_span : @compiler.SourceSpan,
) -> @compiler.SourceSpan? {
let chars = self.chars
let mut cursor = binding_span.end
while cursor < chars.length() &&
(chars[cursor] == ' ' || chars[cursor] == '\t') {
cursor += 1
}
if cursor >= chars.length() || chars[cursor] != '.' {
return None
}
cursor += 1
while cursor < chars.length() &&
(chars[cursor] == ' ' || chars[cursor] == '\t') {
cursor += 1
}
if cursor >= chars.length() || not(is_identifier_char(chars[cursor])) {
return None
}
let start = cursor
while cursor < chars.length() && is_identifier_char(chars[cursor]) {
cursor += 1
}
Some({ start, end: cursor })
}
///|
fn overlaps_definition(
ref_ : ToolingMemberRef,
bindings : Array[ToolingBindingDef],
members : Array[ToolingMemberDef],
) -> Bool {
for binding in bindings {
if spans_overlap(ref_.binding_span, binding.span) {
return true
}
}
for entry in members {
if spans_overlap(ref_.member_span, entry.span) {
return true
}
}
false
}
///|
fn find_binding(
bindings : Array[ToolingBindingDef],
contract_kind : String,
binding_name : String,
) -> ToolingBindingDef? {
for binding in bindings {
if binding.contract_kind == contract_kind &&
binding.binding_name == binding_name {
return Some(binding)
}
}
None
}
///|
fn find_binding_by_kind(
bindings : Array[ToolingBindingDef],
contract_kind : String,
) -> ToolingBindingDef? {
for binding in bindings {
if binding.contract_kind == contract_kind {
return Some(binding)
}
}
None
}
///|
fn find_member(
members : Array[ToolingMemberDef],
binding_name : String,
name : String,
) -> ToolingMemberDef? {
for entry in members {
if entry.binding_name == binding_name && entry.name == name {
return Some(entry)
}
}
None
}
///|
fn canonical_binding_name(contract_kind : String) -> String {
contract_kind
}
///|
fn uppercase_first(source : String) -> String {
if source.length() == 0 {
return source
}
let chars = source.to_array()
let first = chars[0]
let upper = if first >= 'a' && first <= 'z' {
(first.to_int() - 'a'.to_int() + 'A'.to_int()).unsafe_to_char()
} else {
first
}
let sb = StringBuilder::new()
sb.write_char(upper)
sb.write_string(
String::unsafe_substring(source, start=1, end=source.length()),
)
sb.to_string()
}
///|
fn ToolingDocument::find_substring_in_span(
self : ToolingDocument,
needle : String,
span : @compiler.SourceSpan,
) -> Int? {
let source_chars = self.chars
let needle_chars = needle.to_array()
if needle_chars.length() == 0 || span.start >= source_chars.length() {
return None
}
let last = min_int(
span.end - needle_chars.length(),
source_chars.length() - needle_chars.length(),
)
let mut cursor = span.start
while cursor <= last {
let mut matched = true
for offset in 0.. Int? {
let source_chars = self.chars
let needle_chars = needle.to_array()
if needle_chars.length() == 0 || span.start >= source_chars.length() {
return None
}
let last = min_int(
span.end - needle_chars.length(),
source_chars.length() - needle_chars.length(),
)
let mut cursor = if start_offset > span.start {
start_offset
} else {
span.start
}
while cursor <= last {
let mut matched = true
for offset in 0.. @compiler.SourceSpan? {
let chars = self.chars
let token_chars = token.to_array()
let last = min_int(
span.end - token_chars.length(),
chars.length() - token_chars.length(),
)
let mut cursor = if start_offset > span.start {
start_offset
} else {
span.start
}
while cursor <= last {
let mut matched = true
for offset in 0..= chars.length() ||
not(is_identifier_char(chars[end_]))
if matched && before_ok && after_ok {
return Some({ start: cursor, end: end_ })
}
cursor += 1
}
None
}
///|
fn min_int(a : Int, b : Int) -> Int {
if a < b {
a
} else {
b
}
}