///|
async fn main {
let files = []
@ArgParser.parse([], path => files.push(path), "", @env.args()[1:].to_array())
for file in files {
if !@fs.exists(file) {
abort("file not found: \{file}")
}
let result = @fs.read_file(file).text() |> process_file(name=file)
@fs.write_file(file, result, truncate=true)
let _ = @process.collect_output("moonfmt", ["-w", file])
println("written to \{file}")
}
}
///|
let jmop_pkg = "@Yoorkin/jmop"
///|
let jmop_optional = "\{jmop_pkg}.Optional"
///|
let jmop_object = "\{jmop_pkg}.Object"
///|
let jmop_value = "\{jmop_pkg}.Value"
///|
let jmop_nullable = "\{jmop_pkg}.Nullable"
///|
let jmop_convert_error = "\{jmop_pkg}.convert_error"
///|
let jmop_is_value = "\{jmop_pkg}.IsValue"
///|
let jmop_is_object = "\{jmop_pkg}.IsObject"
///|
let jmop_is_any = "\{jmop_pkg}.IsAny"
///|
let jmop_dyn_cast = "\{jmop_pkg}.DynCast"
///|
let jmop_fail_cast = "\{jmop_pkg}.fail_cast"
///|
fn process_file(content : String, name~ : String) -> String {
let (impls, reports) = parse_string(content, name~)
if !reports.is_empty() {
println(reports)
abort("failed to parse \{name}")
}
let directives = impls.filter_map(extract_directive)
let patches = generate_patch(directives)
apply_patch0(content, patches)
}
///|
enum TypeAnnotation {
Constr(@syntax.LongIdent, Array[TypeAnnotation])
Option(TypeAnnotation)
Tuple(Array[TypeAnnotation])
Arrow(Array[TypeAnnotation], TypeAnnotation, err~ : ErrorTypeAnnoatation)
Object(@syntax.LongIdent)
Any(Location)
} derive(ToJson)
///|
pub(all) enum ErrorTypeAnnoatation {
ErrorType(TypeAnnotation)
DefaultErrorType
NoErrorType
Noraise
MaybeError(TypeAnnotation)
} derive(ToJson)
///|
enum RefineDirective {
UnionType(Array[String])
UnionTypeOf(String)
Nothing
} derive(ToJson)
///|
enum ParamDirective {
Positional(
name~ : String,
ty~ : TypeAnnotation,
mut refine~ : RefineDirective
)
Optional(name~ : String, ty~ : TypeAnnotation, mut refine~ : RefineDirective)
Labeled(name~ : String, ty~ : TypeAnnotation, mut refine~ : RefineDirective)
} derive(ToJson)
///|
fn ParamDirective::set_refine(self : Self, value : RefineDirective) -> Unit {
match self {
Positional(..) as v => v.refine = value
Optional(..) as v => v.refine = value
Labeled(..) as v => v.refine = value
}
}
///|
fn ParamDirective::get_refine(self : Self) -> RefineDirective {
match self {
Positional(..) as v => v.refine
Optional(..) as v => v.refine
Labeled(..) as v => v.refine
}
}
///|
struct ResultDirective {
ty : TypeAnnotation
throws : Array[String]
optional : Bool
nullable : Bool
} derive(ToJson)
///|
pub fn ParamDirective::name(self : Self) -> String {
match self {
Positional(name~, ..) | Optional(name~, ..) | Labeled(name~, ..) => name
}
}
///|
fn process_error_type(ety : @syntax.ErrorType) -> ErrorTypeAnnoatation {
match ety {
ErrorType(ty~) => ErrorType(process_type_annotation(ty))
DefaultErrorType(..) => DefaultErrorType
NoErrorType => NoErrorType
Noraise(..) => Noraise
MaybeError(ty~) => MaybeError(process_type_annotation(ty))
}
}
///|
fn process_type_annotation(ty : @syntax.Type) -> TypeAnnotation {
match ty {
Option(ty~, ..) => Option(process_type_annotation(ty))
Name(constr_id~, tys~, ..) =>
Constr(constr_id.id, tys.map(process_type_annotation).to_array())
Tuple(tys~, ..) => Tuple(tys.map(process_type_annotation).to_array())
Arrow(args~, res~, err~, ..) =>
Arrow(
args.map(process_type_annotation).to_array(),
process_type_annotation(res),
err=process_error_type(err),
)
Object(constr_id) => Object(constr_id.id)
Any(loc~) => Any(loc)
}
}
///|
fn process_parameter(param : @syntax.Parameter) -> ParamDirective {
match param {
DiscardPositional(ty~, loc~) => {
ignore((ty, loc))
abort("discard parameter is not supported")
}
Positional(binder~, ty~) => {
guard ty is Some(ty) // TODO: handle error case
Positional(
name=binder.name,
ty=process_type_annotation(ty),
refine=Nothing,
)
}
Labelled(binder~, ty~) => {
guard ty is Some(ty) // TODO: handle error case
Labeled(name=binder.name, ty=process_type_annotation(ty), refine=Nothing)
}
Optional(binder~, default~, ty~) => {
guard ty is Some(ty) // TODO: handle error case
ignore(default) //TODO: use default value
Optional(name=binder.name, ty=process_type_annotation(ty), refine=Nothing)
}
QuestionOptional(binder~, ty~) => {
guard ty is Some(ty) // TODO: handle error case
Optional(name=binder.name, ty=process_type_annotation(ty), refine=Nothing)
}
}
}
///|
enum Directive {
Type(name~ : String, hole~ : Location)
Constructor(
ty~ : String,
name~ : String,
arguments~ : Map[String, ParamDirective],
result~ : ResultDirective,
bound_loc~ : BoundPosition,
hole~ : Location,
source_loc~ : String?
)
Getter(
name~ : String,
ty~ : String,
result~ : ResultDirective,
bound_loc~ : BoundPosition,
hole~ : Location,
source_loc~ : String?
)
Setter(
name~ : String,
ty~ : String,
value~ : ParamDirective?,
result~ : ResultDirective,
bound_loc~ : BoundPosition,
hole~ : Location,
source_loc~ : String?
)
Function(
name~ : String,
ty~ : String,
arguments~ : Map[String, ParamDirective],
result~ : ResultDirective,
bound_loc~ : BoundPosition,
hole~ : Location,
source_loc~ : String?
)
Method(
name~ : String,
ty~ : String,
arguments~ : Map[String, ParamDirective],
result~ : ResultDirective,
bound_loc~ : BoundPosition,
hole~ : Location,
source_loc~ : String?
)
Union(name~ : String, tys~ : Array[String], loc~ : Location)
Generated(hole~ : Location)
} derive(ToJson)
///|
enum BoundPosition {
InsertPos(Position)
ReplaceLoc(Location)
Nothing
} derive(ToJson)
///|
fn extract_directive(toplevel : @syntax.Impl) -> Directive? {
let attrs = match toplevel {
TopTypeDef(t) => t.attrs
TopFuncDef(fun_decl~, ..) => fun_decl.attrs
TopTrait(decl) => decl.attrs
TopImpl(attrs~, ..) => attrs
TopImplRelation(..) => return None
_ =>
abort(
"\{toplevel.loc()}: only impl, type def and function def are supported",
)
}
let exprs = attrs.filter_map(attr => match attr.parsed {
Some(Apply({ qual: Some("webgen"), name }, props)) =>
Some((name, props, attr.loc))
Some(Ident({ qual: Some("webgen"), name })) =>
Some((name, @list.empty(), attr.loc))
_ => None
})
guard exprs is More(main, tail=rest) else {
abort(
"error (\{toplevel.loc()}, \{attrs}, \{toplevel.to_json()}): expected ty, constructor, getter, setter",
)
}
if main.0 == "generated" {
return Some(Generated(hole=toplevel.loc()))
}
let mut is_result_nullable = false
let mut is_result_optional = false
let throws = []
let arguments = {}
let (name, ty, hole, bound_loc, result_ty) = match toplevel {
TopFuncDef(
fun_decl={
name: { name, .. },
type_name: Some({ name: Ident(name=ty), loc: type_name_loc }),
decl_params,
return_type,
quantifiers,
..,
},
decl_body=DeclBody(expr~, ..),
..
) => {
let name = if main.0 is ("getter" | "setter") {
let trimed = if name.has_prefix("get_") || name.has_prefix("set_") {
try! name[4:]
} else if name.has_prefix("is_") {
try! name[3:]
} else {
name
}
snake_to_small_camel(trimed)
} else {
name
}
for param in decl_params.unwrap() {
let d = process_parameter(param)
arguments[d.name()] = d
}
let result = return_type.map(process_type_annotation)
let bound_loc = if quantifiers.is_empty() {
InsertPos(type_name_loc.start)
} else {
let start = quantifiers.head().unwrap().name_loc.start
let last = quantifiers.last().unwrap()
let end = match last.constraints.last() {
Some(c) => c.loc.end
None => last.name_loc.end
}
ReplaceLoc(Location::{ start, end })
}
(name, ty, expr.loc(), bound_loc, result)
}
TopTypeDef({ tycon, loc, .. }) => (tycon, tycon, loc, Nothing, None)
TopTrait({ name: { name, .. }, loc, .. }) =>
(name, name, loc, Nothing, None)
TopImpl(trait_={ name, loc: trait_loc }, ..) => {
let n = longident_to_string(name)
(n, n, toplevel.loc(), InsertPos(trait_loc.start), None)
}
_ => abort("\{toplevel.loc()}: missing type name or binder name")
}
fn filter_name_prop(props : List[@attribute.Prop]) {
let result = props.filter_map(fn(prop) {
match prop {
Labeled("name", String(v)) => Some(v)
_ => None
}
})
match result {
More(r, tail=Empty) => Some(r)
Empty => None
_ => panic() // report error
}
}
let name = if filter_name_prop(main.1) is Some(v) { v } else { name }
for expr in rest {
let (name, props, loc) = expr
match name {
"result" =>
for prop in props {
match prop {
Expr(Ident({ qual: None, name: "nullable" })) =>
is_result_nullable = true
Expr(Ident({ qual: None, name: "optional" })) =>
is_result_optional = true
Expr(Apply({ qual: None, name: "error" }, props)) =>
for prop in props {
match prop {
Expr(String(s)) => throws.push(s)
_ => abort("\{loc}: invalid throws argument")
}
}
_ => abort("\{loc}: invalid result argument")
}
}
"refine" =>
for prop in props {
match prop {
Labeled(arg, String(v)) =>
match arguments.get(arg) {
None => abort("\{loc}: no such argument: \{arg}")
Some(d) => d.set_refine(UnionTypeOf(v))
}
Labeled(arg, Apply({ qual: None, name: "union" }, props)) => {
let tys = props.map(fn(p) {
match p {
Expr(String(s)) => s
_ => abort("\{loc}: invalid union argument")
}
})
match arguments.get(arg) {
None => abort("\{loc}: no such argument: \{arg}")
Some(d) => d.set_refine(UnionType(tys.to_array()))
}
}
_ => abort("\{loc}: invalid refine argument")
}
}
_ => abort("\{loc}: unknown webgen attribute: \{name}")
}
}
// TODO: better error handling
let source_loc = if arguments.contains("loc") { Some("loc") } else { None }
match main.0 {
"ty" => Some(Type(name~, hole~))
"constructor" => {
guard result_ty is Some(result_ty) else {
abort("constructor must have return type")
}
let result = {
ty: result_ty,
throws,
optional: is_result_optional,
nullable: is_result_nullable,
}
Some(
Constructor(
arguments~,
name~,
ty~,
result~,
hole~,
source_loc~,
bound_loc~,
),
)
}
"getter" => {
guard result_ty is Some(result_ty) else {
abort("getter must have return type")
}
let result = {
ty: result_ty,
throws,
optional: is_result_optional,
nullable: is_result_nullable,
}
Some(Getter(name~, ty~, result~, hole~, source_loc~, bound_loc~))
}
"setter" => {
guard result_ty is Some(result_ty) else {
abort("getter must have return type")
}
let result = {
ty: result_ty,
throws,
optional: is_result_optional,
nullable: is_result_nullable,
}
let value = arguments.get("value")
Some(Setter(name~, ty~, value~, result~, hole~, source_loc~, bound_loc~))
}
"function" => {
guard result_ty is Some(result_ty) else {
abort("getter must have return type")
}
let result = {
ty: result_ty,
throws,
optional: is_result_optional,
nullable: is_result_nullable,
}
Some(
Function(
name~,
ty~,
arguments~,
result~,
hole~,
source_loc~,
bound_loc~,
),
)
}
"method" => {
guard result_ty is Some(result_ty) else {
abort("getter must have return type")
}
let result = {
ty: result_ty,
throws,
optional: is_result_optional,
nullable: is_result_nullable,
}
Some(
Method(name~, ty~, arguments~, result~, hole~, source_loc~, bound_loc~),
)
}
"union" => {
let tys = []
for prop in main.1 {
match prop {
Expr(String(s)) => tys.push(s)
_ => abort("invalid union argument")
}
}
// TODO: fix trait location
let loc = main.2.merge(toplevel.loc())
Some(Union(name~, tys~, loc~))
}
_ => abort("\{hole}, unknown webgen attribute: \{main.0}")
}
}
///|
enum Patch {
Insert(Position, String)
Replace(Location, String)
Append(String)
Remove(Location)
} derive(Show, ToJson)
///|
fn longident_to_string(id : @syntax.LongIdent) -> String {
match id {
Dot(pkg~, id~) => "@\{pkg}.\{id}"
Ident(name~) => name
}
}
///|
fn generate_ffi_type_annotation(
ty : TypeAnnotation,
refine : RefineDirective,
) -> String {
fn go(ty) {
match ty {
Arrow(args, res, err~) => {
ignore((args, res, err))
abort("function type is not supported")
}
Tuple(tys) => "(" + tys.map(go).join(", ") + ")"
Object(name) => "&" + longident_to_string(name)
Option(ty) => "Nullable[\{go(ty)}]"
Constr(name, args) => {
let name = longident_to_string(name)
if args.is_empty() {
name
} else {
name + "[" + args.map(go).join(", ") + "]"
}
}
Any(loc) => "Any"
}
}
match refine {
Nothing => go(ty)
UnionType(_) | UnionTypeOf(_) => jmop_value
}
}
///|
fn generate_ffi_params(arguments : Map[String, ParamDirective]) -> String {
let buf = StringBuilder::new()
let mut first = true
for _, arg in arguments {
if first {
first = false
} else {
buf.write_string(", ")
}
match arg {
Positional(name~, ty~, refine~) =>
buf.write_string(
"\{name} : \{generate_ffi_type_annotation(ty, refine)}",
)
Optional(name~, ty~, refine~) =>
buf.write_string(
"\{name} : \{jmop_optional}[\{generate_ffi_type_annotation(ty, refine)}]",
)
Labeled(name~, ty~, refine~) =>
buf.write_string(
"\{name} : \{generate_ffi_type_annotation(ty, refine)}",
)
}
}
buf.to_string()
}
///|
fn generate_glude_args(arguments : Map[String, ParamDirective]) -> String {
let buf = StringBuilder::new()
let mut first = true
fn handle_nullable(arg_name : String, ty : TypeAnnotation) -> String {
match ty {
Option(_) => "\{jmop_optional}::from_option(\{arg_name})"
_ => arg_name
}
}
for _, arg in arguments {
if first {
first = false
} else {
buf.write_string(", ")
}
match arg {
Optional(name~, ty~, refine=_) =>
buf
..write_string("\{jmop_optional}::from_option(")
..write_string(handle_nullable(name, ty))
..write_string(")")
Positional(name~, ty~, refine=_) | Labeled(name~, ty~, refine=_) =>
buf.write_string(handle_nullable(name, ty))
}
match arg {
Optional(refine~, ..) | Positional(refine~, ..) | Labeled(refine~, ..) =>
match refine {
UnionType(_) | UnionTypeOf(_) => buf.write_string(".as_value()")
Nothing => ()
}
}
}
buf.to_string()
}
///|
fn generate_wrapper_type_bounds(
arguments : Map[String, ParamDirective],
) -> String {
let bounds = []
for name, arg in arguments {
match arg.get_refine() {
UnionType(tys) => abort("not implemented")
UnionTypeOf(trait_name) => {
let name = snake_to_big_camel(name)
bounds.push("\{name} : \{trait_name}")
}
Nothing => ()
}
}
match bounds {
[] => ""
_ => bounds.join(", ")
}
}
///|
fn generate_ffi_result(result : ResultDirective, self_ty : String) -> String {
match result.ty {
Option(Option(ty)) if result.nullable && result.optional =>
"\{jmop_optional}[\{jmop_nullable}[\{generate_ffi_type_annotation(ty, Nothing)}]]"
Option(ty) if result.nullable =>
"\{jmop_nullable}[\{generate_ffi_type_annotation(ty, Nothing)}]"
Option(ty) if result.optional =>
"\{jmop_optional}[\{generate_ffi_type_annotation(ty, Nothing)}]"
Option(_) => abort("result type need to be marked as nullable or optional")
Constr(Ident(name="Self"), []) => self_ty
_ => generate_ffi_type_annotation(result.ty, Nothing)
}
}
///|
fn generate_args_name(
arguments : Map[String, ParamDirective],
) -> (String?, String) {
let ins_name = arguments.get("self").map(d => d.name())
arguments.remove("self")
let args = StringBuilder::new()
let mut first = true
for _, arg in arguments {
if first {
first = false
} else {
args.write_string(", ")
}
args.write_string(arg.name())
}
(ins_name, args.to_string())
}
///|
fn generate_glude_result_handling(
glude : String,
result : ResultDirective,
source_loc : String?,
) -> String {
let glude = if result.optional && result.nullable {
"\{glude}.to_option().map(x => x.to_option())"
} else if result.optional || result.nullable {
"\{glude}.to_option()"
} else {
glude
}
if !result.throws.is_empty() && source_loc is Some(name) {
"\{jmop_convert_error}(\{name}, fn(){\{glude}})"
} else {
// TODO: report error if throws is not empty but source_loc is None
glude
}
}
///|
fn generate_patch(directives : List[Directive]) -> Array[Patch] {
let patches = []
for directive in directives {
match directive {
Method(name~, ty~, arguments~, result~, bound_loc~, hole~, source_loc~) => {
let ffi_name = "ffi_\{ty}_\{name}"
let (ins_name, args_name) = generate_args_name(arguments)
guard ins_name is Some(ins_name) else {
abort("method must have self parameter")
}
let wrapper_type_bounds = generate_wrapper_type_bounds(arguments)
let glude_args = generate_glude_args(arguments)
let glude =
$|\{ffi_name}(self, \{glude_args})
let glude = generate_glude_result_handling(glude, result, source_loc)
let ffi_result = generate_ffi_result(result, ty)
let ffi_params = generate_ffi_params(arguments)
let ffi =
#|#webgen.generated
$|extern "js" fn \{ffi_name}(ins : \{ty}, \{ffi_params}) -> \{ffi_result} =
$| "(\{ins_name},\{args_name}) => \{ins_name}.\{name}(\{args_name})"
match bound_loc {
InsertPos(pos) if wrapper_type_bounds != "" =>
patches.push(Insert(pos, "[" + wrapper_type_bounds + "]"))
ReplaceLoc(loc) if wrapper_type_bounds != "" =>
patches.push(Replace(loc, wrapper_type_bounds))
_ => ()
}
patches..push(Replace(hole, glude))..push(Append(ffi))
}
Constructor(
ty~,
name~,
arguments~,
result~,
bound_loc~,
hole~,
source_loc~
) => {
let ffi_name = "ffi_\{ty}_\{name}"
let (_, args_name) = generate_args_name(arguments)
let ffi_params = generate_ffi_params(arguments)
let wrapper_type_bounds = generate_wrapper_type_bounds(arguments)
let glude_args = generate_glude_args(arguments)
let glude =
$|\{ffi_name}(\{glude_args})
let glude = generate_glude_result_handling(glude, result, source_loc)
let ffi =
#|#webgen.generated
$|extern "js" fn \{ffi_name}(\{ffi_params}) -> \{ty} =
$| "(\{args_name}) => new \{ty}(\{args_name})"
match bound_loc {
InsertPos(pos) if wrapper_type_bounds != "" =>
patches.push(Insert(pos, "[" + wrapper_type_bounds + "]"))
ReplaceLoc(loc) if wrapper_type_bounds != "" =>
patches.push(Replace(loc, wrapper_type_bounds))
_ => ()
}
patches..push(Replace(hole, glude))..push(Append(ffi))
}
Getter(ty~, name~, result~, hole~, source_loc~, bound_loc~) => {
let glude =
$|ffi_\{ty}_get_\{name}(self)
let glude = generate_glude_result_handling(glude, result, source_loc)
let ffi_result = generate_ffi_result(result, ty)
let ffi =
#|#webgen.generated
$|extern "js" fn ffi_\{ty}_get_\{name}(ins : \{ty}) -> \{ffi_result}
$| = "(ins) => ins.\{name}"
patches..push(Replace(hole, glude))..push(Append(ffi))
}
Setter(ty~, name~, value~, hole~, source_loc~, result~, bound_loc~) => {
guard value is Some(value) else {
abort("setter must have value parameter")
}
let ffi_params = generate_ffi_params({ "value": value })
let glude =
$|ffi_\{ty}_set_\{name}(self, \{value.name()})
let glude = generate_glude_result_handling(glude, result, source_loc)
let ffi =
#|#webgen.generated
$|extern "js" fn ffi_\{ty}_get_\{name}(ins : \{ty}, \{ffi_params}) =
$| "(ins) => ins.\{name}"
let wrapper_type_bounds = generate_wrapper_type_bounds({
"value": value,
})
match bound_loc {
InsertPos(pos) if wrapper_type_bounds != "" =>
patches.push(Insert(pos, "[" + wrapper_type_bounds + "]"))
ReplaceLoc(loc) if wrapper_type_bounds != "" =>
patches.push(Replace(loc, wrapper_type_bounds))
_ => ()
}
patches..push(Replace(hole, glude))..push(Append(ffi))
}
Generated(hole~) => patches.push(Remove(hole))
Type(name~, hole~) => {
// TODO: support tuple struct syntax
let typedef =
#|#webgen.ty
$|struct \{name} (\{jmop_object})
let impl_is_any =
#|#webgen.generated
$|pub impl \{jmop_is_any} for \{name}
let impl_is_value =
#|#webgen.generated
$|pub impl \{jmop_is_value} for \{name} with as_value(self) {
$| object_from_\{name}(self).as_value()
$|}
$|
$|#webgen.generated
$|fn object_from_\{name}(x : \{name}) -> \{jmop_object} = "%identity"
let impl_is_object =
#|#webgen.generated
$|pub impl \{jmop_is_object} for \{name}
let impl_dyn_cast =
#|#webgen.generated
$|pub impl \{jmop_dyn_cast} for \{name} with from_value(x) {
$| if x.instance_of("\{name}") {
$| unsafe_value_to_\{name}(x)
$| } else {
$| \{jmop_fail_cast}(target="\{name}")
$| }
$|}
$|
#|#webgen.generated
$|fn unsafe_value_to_\{name}(x : \{jmop_value}) -> \{name} = "%identity"
patches
..push(Replace(hole, typedef))
..push(Append(impl_is_any))
..push(Append(impl_is_value))
..push(Append(impl_is_object))
..push(Append(impl_dyn_cast))
ignore((name, hole))
}
Union(name~, tys~, loc~) => {
let tys_str = tys.map(s => "\"\{s}\"").join(", ")
let trait_def =
$|#webgen.union(\{tys_str})
$|trait \{name} : \{jmop_is_value} {}
let impls = tys
.map(s => (
$|#webgen.generated
$|pub impl \{name} for \{s}
))
.join("\n\n")
patches..push(Replace(loc, trait_def))..push(Append(impls))
}
Function(..) => ()
}
}
patches
}
///|
fn snake_to_small_camel(s : StringView) -> String {
// TODO: verify input string
let parts = s.split("_").collect()
let buf = StringBuilder::new()
for i, part in parts {
if i == 0 {
buf.write_string(part.to_lower().to_string())
} else {
buf
..write_string(try! part[0:1].to_upper().to_string())
..write_string(try! part[1:].to_lower().to_string())
}
}
buf.to_string()
}
///|
fn snake_to_big_camel(s : StringView) -> String {
// TODO: verify input string
let parts = s.split("_").collect()
let buf = StringBuilder::new()
for i, part in parts {
buf
..write_string(try! part[0:1].to_upper().to_string())
..write_string(try! part[1:].to_lower().to_string())
}
buf.to_string()
}
///|
test "snake_to_small_camel" {
inspect(snake_to_small_camel("hello_world"), content="helloWorld")
inspect(snake_to_small_camel("Hello_World_one"), content="helloWorldOne")
inspect(snake_to_small_camel("hello"), content="hello")
inspect(snake_to_small_camel("h"), content="h")
inspect(snake_to_small_camel(""), content="")
}
///|
fn remove_block_line(source : String) -> String {
let lines = source.split("\n").collect()
let filtered = lines.filter_map(fn(line) {
if line.has_prefix("///|") {
None
} else {
Some(line)
}
})
filtered.join("\n")
}
///|
fn apply_patch0(source : String, patches : Array[Patch]) -> String {
let appended = []
let replacements = []
for patch in patches {
match patch {
Insert(pos, s) =>
replacements.push(point(pos.lnum - 1, pos.column() - 1, s))
Replace(loc, s) =>
replacements.push(
multiline(
(loc.start.lnum - 1, loc.start.column() - 1),
(loc.end.lnum - 1, loc.end.column() - 2),
s,
),
)
Remove(loc) =>
replacements.push(
multiline(
(loc.start.lnum - 1, loc.start.column() - 1),
(loc.end.lnum - 1, loc.end.column() - 2),
"",
),
)
Append(text) => appended.push(text)
}
}
let buf = StringBuilder::new(size_hint=source.length() * 2)
buf.write_string(splice(source, replacements))
for text in appended {
buf.write_string("\n")
buf.write_string(text)
}
buf.to_string() |> remove_block_line
}