Slide 28 de 74

Casando vetores e strings

Padrões de vetor
proc main(): void {
	command := ["move", "north", "2"]
	if command is ["move", dir, ...steps] {
		echo "{dir}, {len(steps)} a mais"
	} else if command is ["stop"] {
		echo "parar"
	}
}
Modelos de string, e onde um buraco para
proc main(): void {
	if "/users/42/posts/7" is "/users/{id}/posts/{postId}" {
		echo "{id} / {postId}"            // 42 / 7
	}
	if "a/b/c" is "{x}/{y}" {
		echo "{x} | {y}"                  // a | b/c — o menor primeiro buraco
	}
	if "/users/42/extra" is "/users/{id}" {
		echo id                           // 42/extra — um último buraco pega o resto
	}
	if "/users/42/extra" is "/users/{id is (\d+)}" {
		echo "nunca: só dígitos, e o modelo cobre a string inteira"
	}
}
Um buraco que diz o que aceita
func rota(caminho: Str): Str {
	// Só dígitos chegam em id, então /usuarios/ninguem cai para
	// o próximo ramo em vez de casar com este.
	if caminho is "/usuarios/{id is (\d+)}" {
		return "usuário #{id}"
	}
	// Os dois tipos de buraco se misturam à vontade — são uma
	// construção só.
	if caminho is "/api/v{versao is (\d+)}/{resto}" {
		return "v{versao}, depois {resto}"
	}
	// Chaves dentro dos parênteses são da própria regex: isto são
	// quatro dígitos, não uma interpolação.
	if caminho is "/logs/{ano is (\d{4})}" {
		return "logs de {ano}"
	}
	return "sem correspondência"
}

proc main(): void {
	echo rota("/usuarios/42")        // usuário #42
	echo rota("/usuarios/ninguem")   // sem correspondência
	echo rota("/api/v2/coisas")      // v2, depois coisas
	echo rota("/logs/2026")          // logs de 2026
}
O que um padrão de string recusa
proc main(): void {
	path := "/users/42"

	// Cada um destes é recusado antes de o programa ser construído:
	// if path is "/users/{id}{name}" { }        // dois buracos abertos lado a lado
	// if path is "/users/{len(path)}" { }       // um buraco vincula, não calcula
	// if path is "{a is (a{2,1})}" { }          // {2,1} conta para trás
	// if path is "{a is ((?=b)c)}" { }          // sem lookaround
	// if path is "{a is ((b)\1)}" { }           // e sem retrorreferência

	// Vale: o primeiro buraco diz onde termina.
	if path is "/users/{id is (\d+)}{rest}" {
		echo "{id}[{rest}]"
	}
}
Padrões dentro de padrões
type Route {
	Page { segments: Str[dyn] }
	Missing
}

func resolve(path: Str): Result<Route, Str> {
	if path == "" {
		return Result.Error("vazio")
	}
	return Result.Ok(Route.Page(split(path, ",")))
}

proc main(): void {
	path := "foo/abc/bar,x,y"

	// Um Result com uma Page, com um vetor cujo primeiro elemento
	// é uma string com um buraco: um padrão só.
	if resolve(path) is Result.Ok(Route.Page(["foo/{padding}/bar", ...rest])) {
		echo "{padding}, depois mais {len(rest)}"    // abc, depois mais 2
	}

	// O mesmo casamento, escrito como a cadeia que ele abrevia.
	if resolve(path) is Result.Ok(route) && route is Route.Page(parts) && parts is [head, ...rest] && head is "foo/{padding}/bar" {
		echo "{padding}, depois mais {len(rest)}"
	}

	// Um literal fica onde ficaria um nome: o campo precisa ser igual a ele.
	if resolve("") is Result.Error("vazio") {
		echo "nada para resolver"
	}
}

Um padrão de vetor casa elemento por elemento. Um elemento é um literal — um Int, Float, Str, Atom ou Bool — um nome que o vincula, ou um padrão próprio, e um ...rest no final vincula o que vier depois como um vetor. Sem ele, os tamanhos precisam ser iguais.

Um padrão de string é um modelo cujos {buracos} vinculam o texto entre os seus trechos literais. O modelo cobre a string inteira, e cada buraco pega o menor texto que deixa o resto casar — então "a/b/c" is "{x}/{y}" dá x a e y b/c, e um buraco no final pega tudo o que sobrou: "/users/42/extra" is "/users/{id}" vincula id a 42/extra. Essa é a armadilha de rotas que um buraco que diz o que aceita evita: {id is (\d+)} vincula só o que a expressão regular casa.

Nada entre esses parênteses é escapado pela string — {year is (\d{4})} são quatro dígitos, e não uma interpolação de 4 — e a expressão regular é lida em tempo de compilação, então uma malformada é um erro de compilação dizendo o que há de errado. Um padrão precisa ter uma leitura só: dois buracos abertos não podem ficar lado a lado sem texto entre eles, embora um buraco que diz o que aceita termine a si mesmo; e um buraco é um nome simples, já que um padrão vincula e não calcula. Uma string sendo construída, e não casada, não pode ter um buraco de regex.

Padrões se aninham. Onde quer que um padrão vincule um nome — o campo de uma variante, o elemento de um vetor — ele pode conter um padrão no lugar, e o todo continua sendo um casamento só: r is Result.Ok(Route.Page(["home", ...rest])). Ele é exatamente a cadeia de && que abrevia, o valor de fora primeiro, e compila para o mesmo programa. O campo de uma variante também pode conter um literal ao qual ele precisa ser igual. Um padrão vincula um nome uma vez só, e um padrão de variante precisa ser do tipo com que é casado; o contrário é um erro de compilação.