├── src ├── errors.ml ├── errors.mli ├── generatorCalls.mli ├── generatorFuncDefs.mli ├── generatorResult.mli ├── generateCond.mli ├── generate.mli ├── generatorDatums.mli ├── generateFor.mli ├── stringGen.mli ├── stdLib.mli ├── generateLinear.mli ├── generateLoop.mli ├── util.mli ├── dune ├── stdLib.ml ├── util.ml ├── generateCond.ml ├── transform.mli ├── generateLinear.ml ├── stringGen.ml ├── context.mli ├── config.mli ├── moonsmith.ml ├── generatorDatums.ml ├── genUtil.mli ├── generate.ml ├── generateFor.ml ├── generatorCalls.ml ├── context.ml ├── generateLoop.ml ├── ast.mli ├── generatorResult.ml ├── generatorFuncDefs.ml ├── transform.ml ├── config.ml ├── genUtil.ml └── ast.ml ├── lua └── lib.lua ├── dune-project ├── .github └── workflows │ └── build.yml ├── moonsmith.opam ├── moonsmith.yml ├── .gitignore ├── test └── run-test.py ├── LICENSE └── README.md /src/errors.ml: -------------------------------------------------------------------------------- 1 | exception ConfigError of string 2 | exception InternalError of string 3 | -------------------------------------------------------------------------------- /src/errors.mli: -------------------------------------------------------------------------------- 1 | exception ConfigError of string 2 | exception InternalError of string 3 | -------------------------------------------------------------------------------- /src/generatorCalls.mli: -------------------------------------------------------------------------------- 1 | (** Generates calls statements for functions and methods defined in [ctx]. *) 2 | val generate : Context.t -> Context.t 3 | -------------------------------------------------------------------------------- /src/generatorFuncDefs.mli: -------------------------------------------------------------------------------- 1 | (** Generates top-level function and method definitions for the [ctx]. *) 2 | val generate : Context.t -> Context.t 3 | -------------------------------------------------------------------------------- /src/generatorResult.mli: -------------------------------------------------------------------------------- 1 | (** Generates statements that combine all [ctx.ctx_datum_stmts] in some 2 | result and print it. *) 3 | val generate : Context.t -> Context.t 4 | -------------------------------------------------------------------------------- /src/generateCond.mli: -------------------------------------------------------------------------------- 1 | (** Generates a random conditional statement that mutates some local or global 2 | data in the function. *) 3 | val generate : Context.t -> Ast.env -> Ast.stmt 4 | -------------------------------------------------------------------------------- /src/generate.mli: -------------------------------------------------------------------------------- 1 | (** This module provides interfaces for generation a random Lua AST from the 2 | user-defined config. *) 3 | 4 | (** Entry point of code generation. *) 5 | val generate : Config.t -> string 6 | -------------------------------------------------------------------------------- /src/generatorDatums.mli: -------------------------------------------------------------------------------- 1 | (** Generates global datums for the context [ctx]. 2 | These variables mutate in the functions and methods and later combined to 3 | the result string. *) 4 | val generate : Context.t -> Context.t 5 | -------------------------------------------------------------------------------- /src/generateFor.mli: -------------------------------------------------------------------------------- 1 | (** Generates a random for loop statement that mutates some local or global 2 | data in the function. A generated for can be both: generic or numeric type. *) 3 | val generate : Context.t -> Ast.env -> Ast.stmt 4 | -------------------------------------------------------------------------------- /src/stringGen.mli: -------------------------------------------------------------------------------- 1 | (** This module used to generate a random strings from the pre-defined word 2 | list. *) 3 | 4 | val gen_string : unit -> string 5 | 6 | val gen_int_string : unit -> string 7 | 8 | val gen_regexp_string : unit -> string 9 | -------------------------------------------------------------------------------- /src/stdLib.mli: -------------------------------------------------------------------------------- 1 | (** This module handles code generation for calls of functions from the Lua's 2 | standard library as well as for functions from the moonsmith's helper 3 | library. *) 4 | 5 | val mk_funccall : string -> Ast.expr list -> Ast.expr 6 | 7 | val mk_ident : ?ty:Ast.ty -> string -> Ast.expr 8 | -------------------------------------------------------------------------------- /src/generateLinear.mli: -------------------------------------------------------------------------------- 1 | (** Generates a random mutation statement that never changes control flow 2 | (i.e. no loops, no conditions). *) 3 | val generate : Context.t -> Ast.env -> Ast.stmt 4 | 5 | (** Generates sequence of [num] random linear statements. *) 6 | val generate_stmts : Context.t -> Ast.env -> int -> Ast.stmt list 7 | -------------------------------------------------------------------------------- /src/generateLoop.mli: -------------------------------------------------------------------------------- 1 | (** Generates a random loop (until or while) statement that mutates some local 2 | or global data in the function. The resulted statements also may include 3 | definition of some control variables, defined out of the loop. The 4 | generated loop always terminates. *) 5 | val generate : Context.t -> Ast.env -> Ast.stmt list 6 | -------------------------------------------------------------------------------- /src/util.mli: -------------------------------------------------------------------------------- 1 | (** Randomly chooses one element from the list. *) 2 | val choose_one_exn : 'a list -> 'a 3 | 4 | val choose_lazy_exn : ('a lazy_t) list -> 'a 5 | 6 | val choose : (bool * 'a lazy_t) list -> 'a lazy_t -> 'a 7 | 8 | (** Replaces element in the given list. *) 9 | val replace : 'a list -> int -> 'a -> 'a list 10 | 11 | val endswith : string -> string -> bool 12 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (public_name moonsmith) 3 | (libraries core_kernel clap fileutils yaml) 4 | ; Enable for debugging: 5 | ; (modes byte) 6 | (preprocess 7 | (pps ppx_deriving.show ppx_deriving.eq))) 8 | 9 | (env 10 | (dev 11 | (flags 12 | ; Make warnings non-fatal 13 | (:standard -warn-error -A))) 14 | (release 15 | (ocamlopt_flags :standard -ccopt -static))) 16 | 17 | (install 18 | (package moonsmith) 19 | (section share) 20 | (files (../lua/lib.lua as lua/lib.lua))) 21 | -------------------------------------------------------------------------------- /src/stdLib.ml: -------------------------------------------------------------------------------- 1 | let mk_funccall name args = 2 | let open Ast in 3 | let fcf_func = IdentExpr{ id_id = -1; 4 | id_name = name; 5 | id_ty = TyFunction } 6 | in 7 | let fc_ty = FCFunc{ fcf_func } in 8 | FuncCallExpr{ fc_id = -1; 9 | fc_ty; 10 | fc_args = args } 11 | 12 | let mk_ident ?(ty = Ast.TyAny) name = 13 | Ast.IdentExpr{ id_id = -1; 14 | id_name = name; 15 | id_ty = ty} 16 | -------------------------------------------------------------------------------- /lua/lib.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------- 2 | -- Utility functions. -- 3 | -------------------------------------------------------- 4 | ms = {} 5 | 6 | -- Generates a random integer for an arbitrary table. 7 | -- @param t table 8 | function ms.table_to_int(t) 9 | acc = 0 10 | for i, v in ipairs(t) do 11 | if type(v) == "number" then 12 | acc = acc + v 13 | else 14 | acc = acc & i 15 | end 16 | end 17 | return acc + #t 18 | end 19 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (using menhir 2.0) 3 | 4 | (generate_opam_files true) 5 | 6 | (name moonsmith) 7 | (version 0.2.0) 8 | (authors "Georgiy Komarov") 9 | (license LGPL-3.0-or-later) 10 | (source (github jubnzv/moonsmith)) 11 | (maintainers "Georgiy Komarov ") 12 | 13 | (package 14 | (name moonsmith) 15 | (synopsis "Random generator of Lua programs") 16 | (description "This tool can be used to evaluate tooling that works with Lua: parsers, transpilers, code analyzers, etc.") 17 | (depends 18 | (ocaml (>= 4.08)) 19 | (fileutils :build) 20 | (core_kernel :build) 21 | (clap :build) 22 | (ppxlib :build) 23 | (ppx_deriving :build) 24 | (ppx_deriving_yojson :build) 25 | (yojson :build))) 26 | -------------------------------------------------------------------------------- /src/util.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let choose_one_exn l = 4 | List.nth_exn l @@ Random.int @@ List.length l 5 | 6 | let choose kv fallback = 7 | let l = List.fold_left 8 | kv 9 | ~init:[] 10 | ~f:(fun acc (k, v) -> if k then acc @ [v] else acc) 11 | in if List.is_empty l then Lazy.force fallback 12 | else Lazy.force (choose_one_exn l) 13 | 14 | let choose_lazy_exn vs = 15 | let l = List.fold_left 16 | vs 17 | ~init:[] 18 | ~f:(fun acc v -> acc @ [v]) 19 | in 20 | Lazy.force (choose_one_exn l) 21 | 22 | let replace l idx v = 23 | List.mapi l 24 | ~f:(fun i x -> if i = idx then v else x) 25 | 26 | let endswith s1 s2 = 27 | let len1 = String.length s1 and len2 = String.length s2 in 28 | if len1 < len2 then false 29 | else 30 | let sub = String.sub s1 ~pos:(len1 - len2) ~len:(len2) in 31 | String.equal sub s2 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-18.04 13 | strategy: 14 | matrix: 15 | ocaml-version: [ '4.11.0' ] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Setup OCaml ${{ matrix.ocaml-version }} 20 | uses: avsm/setup-ocaml@master 21 | with: 22 | ocaml-version: ${{ matrix.ocaml-version }} 23 | - name: Build 24 | run: | 25 | opam install -y dune core_kernel clap ppx_deriving ppx_deriving_yojson ppxlib yojson fileutils yaml 26 | eval $(opam env) 27 | dune build @install 28 | mkdir -p output 29 | dune install --prefix output 30 | - name: Prepare to test 31 | run: | 32 | sudo apt-get install -y lua5.3 33 | sudo ln -sf /usr/bin/lua{5.3,} 34 | mkdir -p test/out 35 | - name: Test 36 | run: ./test/run-test.py -b output/bin/moonsmith -n 2500 -t 2 37 | -------------------------------------------------------------------------------- /moonsmith.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Random generator of Lua programs" 5 | description: 6 | "This tool can be used to evaluate tooling that works with Lua: parsers, transpilers, code analyzers, etc." 7 | maintainer: ["Georgiy Komarov "] 8 | authors: ["Georgiy Komarov"] 9 | license: "LGPL-3.0-or-later" 10 | homepage: "https://github.com/jubnzv/moonsmith" 11 | bug-reports: "https://github.com/jubnzv/moonsmith/issues" 12 | depends: [ 13 | "dune" {>= "2.0"} 14 | "ocaml" {>= "4.08"} 15 | "fileutils" {build} 16 | "core_kernel" {build} 17 | "clap" {build} 18 | "ppxlib" {build} 19 | "ppx_deriving" {build} 20 | "ppx_deriving_yojson" {build} 21 | "yojson" {build} 22 | ] 23 | build: [ 24 | ["dune" "subst"] {pinned} 25 | [ 26 | "dune" 27 | "build" 28 | "-p" 29 | name 30 | "-j" 31 | jobs 32 | "@install" 33 | "@runtest" {with-test} 34 | "@doc" {with-doc} 35 | ] 36 | ] 37 | dev-repo: "git+https://github.com/jubnzv/moonsmith.git" 38 | -------------------------------------------------------------------------------- /src/generateCond.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let generate ctx env = 4 | let open Ast in 5 | let gen_cond () = 6 | let cond_lhs = 7 | match Random.int_incl 0 3 with 8 | | 0 -> Context.peek_random_datum_exn ctx 9 | | _ -> !(env_peek_random_exn env) 10 | in 11 | let cond_rhs, cond_op = 12 | match get_essential_ty cond_lhs with 13 | | Some ty -> 14 | (GenUtil.gen_simple_expr ~ty:ty (), GenUtil.gen_compare_binop ty) 15 | | None -> 16 | (NilExpr, OpEq) 17 | in 18 | BinExpr{ bin_lhs = cond_lhs; 19 | bin_op = cond_op; 20 | bin_rhs = cond_rhs } 21 | in 22 | let gen_body () = 23 | let block = GenUtil.gen_empty_block env in 24 | match block with 25 | | BlockStmt block -> 26 | (* NOTE: We are working in the parent environment. No new variables 27 | inside "if" blocks. *) 28 | let body = 29 | (Random.int_incl 1 3) |> GenerateLinear.generate_stmts ctx env 30 | in BlockStmt{ block with block_stmts = body } 31 | | _ -> failwith "Impossible: Body of the function is not a BlockStmt" 32 | in 33 | let gen_else () = 34 | if Random.bool () then None 35 | else Some(gen_body ()) 36 | in 37 | IfStmt{ if_cond = gen_cond (); 38 | if_body = gen_body (); 39 | if_else = gen_else () } 40 | 41 | -------------------------------------------------------------------------------- /src/transform.mli: -------------------------------------------------------------------------------- 1 | (** This module contains functions that transforms Lua expressions between 2 | various types. *) 3 | 4 | (** Converts given expression with its type to a new expression that results as [TyNil]. *) 5 | val to_nil : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 6 | 7 | (** Converts given expression with its type to a new expression that results as [TyBoolean]. *) 8 | val to_boolean : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 9 | 10 | (** Converts given expression with its type to a new expression that results as [TyInt]. *) 11 | val to_int : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 12 | 13 | (** Converts given expression with its type to a new expression that results as [TyFloat]. *) 14 | val to_float : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 15 | 16 | (** Converts given expression with its type to a new expression that results as [TyString]. *) 17 | val to_string : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 18 | 19 | (** Converts given expression with its type to a new expression that results as [TyIntString]. *) 20 | val to_int_string : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 21 | 22 | (** Converts given expression with its type to a new expression that results as [TyFunction]. *) 23 | val to_function : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 24 | 25 | (** Converts given expression with its type to a new expression that results as [TyTable]. *) 26 | val to_table : Context.t -> Ast.ty -> Ast.expr -> Ast.expr 27 | -------------------------------------------------------------------------------- /src/generateLinear.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let generate ctx env = 4 | (* Peek lhs for mutation. Prefer local variables to global datums. *) 5 | let peek_lhs () = 6 | match Random.int_incl 0 3 with 7 | (* TODO: Sometimes we could unintentionally assign a new value 8 | to datum. This will override previous method definitions. 9 | So this case needs some special logic. *) 10 | (* | 0 -> Context.peek_random_datum_exn ctx *) 11 | | _ -> !(Ast.env_peek_random_exn env) 12 | in 13 | (* Randomly peek some idents for the rhs. 14 | Or just generate some simple expressions. *) 15 | let peek_rhs_parts () = 16 | let rhs_num = Random.int_incl 2 3 in 17 | let peek () = 18 | match Random.int_incl 0 8 with 19 | (* TODO: Need to figure out with types of the lhs. *) 20 | (* | 0 -> Context.peek_random_datum_exn ctx *) 21 | | 1 | 2 | 3 -> !(Ast.env_peek_random_exn env) 22 | | _ -> GenUtil.gen_simple_expr () 23 | in 24 | let rec gen acc = 25 | if (List.length acc) >= rhs_num then acc 26 | else begin 27 | gen (acc @ [peek ()]) 28 | end 29 | in 30 | gen [] 31 | in 32 | let lhs = peek_lhs () in 33 | let ty = match lhs with 34 | | IdentExpr id -> id.id_ty 35 | | _ -> failwith "Impossible: Not IdentExpr was generated" 36 | in 37 | let rhs_opt = peek_rhs_parts () |> GenUtil.combine_to_typed_expr ctx ty in 38 | match rhs_opt with 39 | | Some rhs -> begin 40 | Ast.AssignStmt{ assign_local = false; 41 | assign_lhs = [lhs]; 42 | assign_rhs = [rhs]; } 43 | end 44 | | None -> 45 | Errors.ConfigError "Can't combine expressions using this config. Try to enable more functions." |> raise 46 | 47 | let generate_stmts ctx env num = 48 | let rec aux acc = 49 | if num <= List.length acc then acc 50 | else aux (acc @ [generate ctx env]) 51 | in aux [] 52 | -------------------------------------------------------------------------------- /src/stringGen.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let wordlist = 4 | [ "Lorem"; "ipsum"; "dolor"; "sit"; "amet"; "consectetur"; 5 | "adipiscing"; "elit"; "sed"; "do"; "eiusmod"; "tempor"; "incididunt"; "ut"; 6 | "labore"; "et"; "dolore"; "magna"; "aliqua"; "Ut"; "enim"; "ad"; "minim"; 7 | "veniam"; "quis"; "nostrud"; "exercitation"; "ullamco"; "laboris"; "nisi"; 8 | "ut"; "aliquip"; "ex"; "ea"; "commodo"; "consequat"; "Duis"; "aute"; "irure"; 9 | "dolor"; "in"; "reprehenderit"; "in"; "voluptate"; "velit"; "esse"; "cillum"; 10 | "dolore"; "eu"; "fugiat"; "nulla"; "pariatur"; "Excepteur"; "sint"; "occaecat"; 11 | "cupidatat"; "non"; "proident"; "sunt"; "in"; "culpa"; "qui"; "officia"; 12 | "deserunt"; "mollit"; "anim"; "id"; "est"; "laborum"; ] 13 | 14 | let gen_string () = 15 | let len = List.length wordlist in 16 | let rec aux acc words_left = 17 | if words_left >= List.length acc then 18 | let rand_idx = Random.int_incl 0 (len - 1) in 19 | aux (acc @ [List.nth_exn wordlist rand_idx]) (( - ) words_left 1) 20 | else 21 | acc 22 | in 23 | let num_words = Random.int_incl 1 (len / 10) in 24 | aux [] num_words |> String.concat ~sep:" " 25 | 26 | let gen_int_string () = 27 | string_of_int @@ Random.int_incl 1 10 28 | 29 | (* See:https://www.lua.org/pil/20.2.html *) 30 | let regex_letters = [ "a"; "c"; "d"; "l"; "p"; "s"; "u"; "w"; "x"; "z" ] 31 | and regex_modifiers = [ "+"; "*"; "-"; "?" ] 32 | 33 | let gen_regexp_string () = 34 | let patterns_num = Random.int_incl 1 3 in 35 | let rec aux acc = 36 | if patterns_num > List.length acc then 37 | let regexp = 38 | [ "%"; 39 | Util.choose_one_exn regex_letters; 40 | match Random.bool () with 41 | | true -> Util.choose_one_exn regex_modifiers 42 | | false -> "" ] 43 | |> String.concat ~sep:"" 44 | in aux (acc @ [regexp]) 45 | else acc 46 | in 47 | aux [] |> String.concat ~sep:"" 48 | -------------------------------------------------------------------------------- /src/context.mli: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Random code generation context. *) 4 | type t = { 5 | mutable ctx_datum_stmts: Ast.stmt list; 6 | (** Statements that defines a global data on the top-level. *) 7 | 8 | ctx_funcdef_stmts: Ast.stmt list; 9 | (** Functions and methods defined on the top-level. *) 10 | 11 | ctx_call_stmts: Ast.stmt list; 12 | (** Function calls defined on the top-level. *) 13 | 14 | ctx_result_stmts: Ast.stmt list; 15 | (** Statements that combine and print result data. *) 16 | 17 | mutable ctx_global_env: Ast.env; 18 | (** Global environment for the top-level. *) 19 | 20 | ctx_config : Config.t; 21 | (** User-defined configuration. *) 22 | 23 | mutable ctx_table_fields_map: (int, int list, Int.comparator_witness) Base.Map.t; 24 | (** Map that associates ids of OOP tables with ids of definitions of their 25 | fields. *) 26 | 27 | mutable ctx_func_def_map: (int, Ast.stmt ref, Int.comparator_witness) Base.Map.t; 28 | (** Map that associates ids of FuncDefStmts with pointer to their AST nodes. *) 29 | 30 | ctx_seed: int; 31 | (** Seed used to initialize PRG. *) 32 | } 33 | 34 | val mk_context : Config.t -> t 35 | 36 | (** Adds given expression to the global environment. *) 37 | val add_to_global_env : t -> Ast.expr -> unit 38 | 39 | (** Returns a list of available tables defined in [ctx.ctx_datum_stmts]. *) 40 | val get_datum_tables : t -> Ast.stmt list 41 | 42 | (** Peeks random lhs of [ctx.ctx_datum_stmts]. *) 43 | val peek_random_datum_exn : t -> Ast.expr 44 | 45 | (** Peeks random lhs of [ctx.ctx_datum_stmts] which has requested type. 46 | Returns None if there is no datums with such type. *) 47 | val peek_typed_datum : t -> Ast.ty -> Ast.expr option 48 | 49 | (** Same as [get_datum_tables], but also returns indexes in the 50 | [ctx.ctx_datum_stmts]. *) 51 | val get_datum_tables_i : t -> (int * Ast.expr) list 52 | 53 | (** Generates the unique index. *) 54 | val get_free_idx : unit -> int 55 | -------------------------------------------------------------------------------- /moonsmith.yml: -------------------------------------------------------------------------------- 1 | general: 2 | "indent": " " 3 | 4 | # Path to external Lua module used in runtime. 5 | "lib_path": "lua/lib.lua" 6 | 7 | # Minimum number of datums defined on the top-level. 8 | "min_toplevel_datums": 5 9 | 10 | # Maximum number of datums defined on the top-level. 11 | "max_toplevel_datums": 10 12 | 13 | # Minimum number of functions and methods defined on the top-level. 14 | "min_toplevel_funcdefs": 4 15 | 16 | # Maximum number of functions and methods defined on the top-level. 17 | "max_toplevel_funcdefs": 8 18 | 19 | # Prints generated program to stdout. 20 | "stdout": false 21 | 22 | # Seed used to initialize random generator. If null, the seed will be 23 | # choosen randomly. 24 | "seed": null 25 | 26 | language: 27 | # If true, generate some random OOP methods for the datum tables. 28 | "gen_oop_methods": true 29 | 30 | # Use hexademical floats introduced in Lua 5.3. 31 | "use_hex_floats": true 32 | 33 | # Use length ('#') operator on strings and tables. 34 | "use_length": true 35 | 36 | # Use 'pairs' function 37 | "use_pairs": true 38 | 39 | # Use 'ipairs' function 40 | "use_ipairs": true 41 | 42 | # Use tostring() 43 | "use_tostring": true 44 | 45 | # Use tonumber() 46 | "use_tonumber": true 47 | 48 | # Use string.upper() 49 | "use_string_upper": true 50 | 51 | # Use string.lower() 52 | "use_string_lower": true 53 | 54 | # Use string.sub() 55 | "use_string_sub": true 56 | 57 | # Use string.reverse() 58 | "use_string_reverse": true 59 | 60 | # Use string.gsub() 61 | "use_string_gsub": true 62 | 63 | # Use string.len() 64 | "use_string_len": true 65 | 66 | # Use math.type() 67 | "use_math_type": true 68 | 69 | # Use math.ult() 70 | "use_math_ult": true 71 | 72 | # Use math.sin() 73 | "use_math_sin": true 74 | 75 | # Use math.cos() 76 | "use_math_cos": true 77 | 78 | # Use math.tan() 79 | "use_math_tan": true 80 | 81 | # Use math.abs() 82 | "use_math_abs": true 83 | 84 | # Use math.pi 85 | "use_math_pi": true 86 | 87 | # Use math.log() 88 | "use_math_log": true 89 | 90 | # Use math.floor() 91 | "use_math_floor": true 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | .merlin 3 | *.install 4 | 5 | # Parser files 6 | *.conflicts 7 | *.automaton 8 | *messages_complete.txt 9 | 10 | .#* 11 | 12 | src/lexer*.ml 13 | src/parser.ml 14 | src/parser.mli 15 | iec_checker.native 16 | 17 | doc/grammar.html 18 | 19 | _build/ 20 | .dump.json 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | 29 | # Distribution / packaging 30 | .Python 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib64/ 38 | parts/ 39 | sdist/ 40 | var/ 41 | wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Django stuff: 75 | *.log 76 | local_settings.py 77 | db.sqlite3 78 | 79 | # Flask stuff: 80 | instance/ 81 | .webassets-cache 82 | 83 | # Scrapy stuff: 84 | .scrapy 85 | 86 | # Sphinx documentation 87 | docs/_build/ 88 | 89 | # PyBuilder 90 | target/ 91 | 92 | # Jupyter Notebook 93 | .ipynb_checkpoints 94 | 95 | # IPython 96 | profile_default/ 97 | ipython_config.py 98 | 99 | # pyenv 100 | .python-version 101 | 102 | # celery beat schedule file 103 | celerybeat-schedule 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | output 136 | assets 137 | _build 138 | test/out 139 | -------------------------------------------------------------------------------- /src/config.mli: -------------------------------------------------------------------------------- 1 | (** User-defined options for generating random programs. *) 2 | type t = { 3 | c_indent: string; 4 | (** Indentation symbols used in generated Lua code. *) 5 | 6 | c_lib_path: string option; 7 | (** Path to external Lua module used in runtime. *) 8 | 9 | c_min_toplevel_datums: int; 10 | (** Minimum number of datums defined on the top-level. *) 11 | 12 | c_max_toplevel_datums: int; 13 | (** Maximum number of datums defined on the top-level. *) 14 | 15 | c_min_toplevel_funcdefs: int; 16 | (** Minimum number of functions and methods defined on the top-level. *) 17 | 18 | c_max_toplevel_funcdefs: int; 19 | (** Maximum number of functions and methods defined on the top-level. *) 20 | 21 | c_stdout: bool; 22 | (** Prints generated program to stdout. *) 23 | 24 | c_seed: int option; 25 | (** Seed used to initialize random generator. If None, seed will be choosen randomly. *) 26 | 27 | c_gen_oop_methods: bool; 28 | (** If true, generate some random OOP methods for the datum tables. *) 29 | 30 | c_use_hex_floats: bool; 31 | (** Use hexademical floats introduced in Lua 5.3. *) 32 | 33 | (* *** Language features *** *) 34 | 35 | c_use_length: bool; 36 | (** Use length ('#') operators on strings and tables. *) 37 | 38 | c_use_pairs: bool; 39 | (** Use 'pairs' function. *) 40 | 41 | c_use_ipairs: bool; 42 | (** Use 'ipairs' function. *) 43 | 44 | c_use_tostring: bool; 45 | (** Use tostring(). *) 46 | 47 | c_use_tonumber: bool; 48 | (** Use tonumber(). *) 49 | 50 | c_use_string_upper: bool; 51 | (** Use string.upper(). *) 52 | 53 | c_use_string_lower: bool; 54 | (** Use string.lower(). *) 55 | 56 | c_use_string_sub: bool; 57 | (** Use string.sub(). *) 58 | 59 | c_use_string_reverse: bool; 60 | (** Use string.reverse(). *) 61 | 62 | c_use_string_gsub: bool; 63 | (** Use string.gsub(). *) 64 | 65 | c_use_string_len: bool; 66 | (** Use string.len(). *) 67 | 68 | c_use_math_type: bool; 69 | (** Use math.type() *) 70 | 71 | c_use_math_ult: bool; 72 | (** Use math.ult() *) 73 | 74 | c_use_math_sin: bool; 75 | (** Use math.sin() *) 76 | 77 | c_use_math_cos: bool; 78 | (** Use math.cos() *) 79 | 80 | c_use_math_tan: bool; 81 | (** Use math.tan() *) 82 | 83 | c_use_math_abs: bool; 84 | (** Use math.abs() *) 85 | 86 | c_use_math_pi: bool; 87 | (** Use math.pi *) 88 | 89 | c_use_math_log: bool; 90 | (** Use math.log() *) 91 | 92 | c_use_math_floor: bool; 93 | (** Use math.floor(). *) 94 | } 95 | 96 | val from_yaml : string -> t option 97 | 98 | val mk_default : unit -> t 99 | -------------------------------------------------------------------------------- /src/moonsmith.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Creates default path to Lua library using information about distribution 4 | installation paths. *) 5 | let sanitize_libpath libpath = 6 | if Sys.file_exists libpath then Some(libpath) 7 | else 8 | let opam_path = Printf.sprintf "%s/../share/moonsmith/%s" 9 | (Filename.dirname @@ FileUtil.which Sys.argv.(0)) 10 | libpath in 11 | if Sys.file_exists opam_path then Some(opam_path) 12 | else None 13 | 14 | let set_seed c s = 15 | match int_of_string_opt s with 16 | | Some i -> begin 17 | if i > 0 then { c with Config.c_seed = Some(i) } 18 | else { c with Config.c_seed = Some(-i) } 19 | end 20 | | None -> c 21 | 22 | let () = 23 | Clap.description "Random generator of Lua programs"; 24 | 25 | let out = 26 | Clap.default_string 27 | ~short:'o' 28 | ~description: 29 | "Location of the generated Lua file" 30 | ~placeholder:"OUTPUT" 31 | "out.lua" 32 | and configpath = 33 | Clap.default_string 34 | ~short:'c' 35 | ~description: 36 | "Path to configuration file. If not set, the default options will be used." 37 | ~placeholder:"CONFIGPATH" 38 | "moonsmith.yml" 39 | and libpath = 40 | Clap.default_string 41 | ~short:'I' 42 | ~description: 43 | "Path to extra Lua module used in runtime. If not exists, it won't be used." 44 | ~placeholder:"LIBPATH" 45 | "lua/lib.lua" 46 | and nolib = 47 | Clap.default_int 48 | ~short:'n' 49 | ~description: 50 | "Don't use extra Lua module." 51 | ~placeholder:"NOLIB" 52 | 0 53 | and seed = 54 | Clap.default_string 55 | ~short:'s' 56 | ~description: 57 | "Seed used to initialize random generator. If not set, seed will be choosen randomly." 58 | ~placeholder:"SEED" 59 | "" 60 | and stdout = 61 | Clap.default_int 62 | ~short:'S' 63 | ~description: 64 | "Print generated program to stdout" 65 | ~placeholder:"STDOUT" 66 | 0 67 | in 68 | 69 | Clap.close (); 70 | 71 | let c = 72 | if Sys.file_exists configpath then 73 | Config.from_yaml configpath 74 | |> Option.value ~default:(Config.mk_default ()) 75 | else 76 | Config.mk_default () 77 | in 78 | let c = set_seed c seed in 79 | let c = { c with c_stdout = phys_equal stdout 1 || c.Config.c_stdout } in 80 | let c = 81 | if not @@ phys_equal nolib 1 then { c with c_lib_path = sanitize_libpath libpath } 82 | else { c with c_lib_path = None } 83 | in 84 | let program = Generate.generate c in 85 | let oc = Out_channel.create out in 86 | Out_channel.output_string oc program; 87 | Out_channel.flush oc; 88 | Out_channel.close oc; 89 | if c.Config.c_stdout then Printf.printf "%s\n" program else (); 90 | -------------------------------------------------------------------------------- /src/generatorDatums.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Generates a datum table that may be an object in OOP sense. It may 4 | have methods and could be inherited. The methods can be randomly generated 5 | later by the [GeneratorFuncDefs] generator. 6 | For more details, see: https://www.lua.org/pil/16.html *) 7 | let gen_datum_table ctx idx id_name = 8 | let id = Ast.IdentExpr{ id_id = idx; 9 | id_name; 10 | id_ty = TyTable } 11 | in 12 | ctx.Context.ctx_table_fields_map <- Map.set 13 | ~key:idx 14 | ~data:([]) 15 | ctx.Context.ctx_table_fields_map; 16 | id 17 | 18 | let gen_datum_ident ctx = 19 | let idx = Context.get_free_idx () in 20 | let id_name = Printf.sprintf "datum%d" idx in 21 | if Random.bool () then 22 | Ast.IdentExpr{ id_id = idx; 23 | id_name; 24 | id_ty = GenUtil.gen_simple_ty () } 25 | else gen_datum_table ctx idx id_name 26 | 27 | (** Generates a random initializer for the given [lhs]. *) 28 | let gen_datum_value ctx lhs = 29 | let open Ast in 30 | match lhs with 31 | | IdentExpr lhs_id -> begin 32 | match lhs_id.id_ty with 33 | | TyBoolean -> if Random.bool() then TrueExpr else FalseExpr 34 | | TyInt -> IntExpr(Random.int_incl (-100) 100) 35 | | TyFloat -> FloatExpr(Random.float 100.0) 36 | | TyString -> let v = StringGen.gen_string () in StringExpr(v) 37 | | TyIntString -> let v = StringGen.gen_int_string () in StringExpr(v) 38 | | TyTable -> begin 39 | let table_expr = GenUtil.gen_hash_table_init () in 40 | let added_ids = get_table_key_ids table_expr 41 | and prev_data = 42 | Map.find_exn ctx.Context.ctx_table_fields_map lhs_id.id_id 43 | in 44 | ctx.Context.ctx_table_fields_map <- Map.set 45 | ~key:lhs_id.id_id 46 | ~data:(prev_data @ added_ids) 47 | ctx.Context.ctx_table_fields_map; 48 | table_expr 49 | end 50 | | TyFunction | TyThread | TyUserdata | TyNil | TyAny -> failwith "Unsupported type for gen_datum_value" 51 | end 52 | | _ -> failwith "Expected IdentExpr" 53 | 54 | (** Generates a random datum block in the given [ctx]. *) 55 | let gen_random_datum ctx = 56 | let lhs = gen_datum_ident ctx in 57 | let rhs = gen_datum_value ctx lhs in 58 | Context.add_to_global_env ctx lhs; 59 | Ast.AssignStmt{ assign_local = Random.bool (); 60 | assign_lhs = [lhs]; 61 | assign_rhs = [rhs]; } 62 | 63 | let generate (ctx : Context.t) = 64 | let open Context in 65 | let open Config in 66 | let rec aux acc num = 67 | if List.length acc >= num then acc 68 | else aux (acc @ [gen_random_datum ctx]) num 69 | in 70 | let ctx_datum_stmts = aux [] @@ Random.int_incl 71 | ctx.ctx_config.c_min_toplevel_datums 72 | ctx.ctx_config.c_max_toplevel_datums 73 | in 74 | { ctx with ctx_datum_stmts = ctx_datum_stmts } 75 | -------------------------------------------------------------------------------- /src/genUtil.mli: -------------------------------------------------------------------------------- 1 | (** This module contains various utilities used in generating a random AST. *) 2 | 3 | (** Randomly generates an essential Lua type. 4 | NOTE: userdata and thread types are unsupported. *) 5 | val gen_ty : unit -> Ast.ty 6 | 7 | (** Randomly generates on of the following types: 8 | + Boolean 9 | + Int 10 | + Float 11 | + String *) 12 | val gen_simple_ty : unit -> Ast.ty 13 | 14 | (** Generates a random init for an array table expression. 15 | See: https://www.lua.org/pil/11.1.html *) 16 | val gen_array_table_init : unit -> Ast.expr 17 | 18 | (** Generates a random init for an hash table expression. *) 19 | val gen_hash_table_init : ?ty:Ast.ty -> unit -> Ast.expr 20 | 21 | (** Generates a simple random expression of the given type. 22 | If type is not specified, chooses randomly between: 23 | + Boolean 24 | + Int 25 | + Float 26 | + String *) 27 | val gen_simple_expr : ?ty:Ast.ty -> ?always_positive:bool -> unit -> Ast.expr 28 | 29 | (** Generates comparison operator that could be applied to two expressions with 30 | the given type. *) 31 | val gen_compare_binop : Ast.ty -> Ast.operator 32 | 33 | (** Creates a new identifier in the [env]. 34 | 35 | The created identifier won't implicitly added to the environment, because 36 | in some cases we should delay this. For example, we want to avoid the 37 | following situation: 38 | local a1, a2 = "foo", a1 39 | a1 is used before it was defined, so this causes a problem. 40 | 41 | So, user *must* call [Ast.env_flush_pending_bindings] after adding statements 42 | when [add_now] is false. *) 43 | val gen_ident : ?add_now:bool -> ?name:(string option) -> Ast.env -> Ast.expr 44 | 45 | (** Generates a [BlockStmt] without any nested statements. *) 46 | val gen_dummy_block : unit -> Ast.stmt 47 | 48 | (** Generates an appropriate rhs for assign the given expression. There 49 | can be global datums [ctx.ctx_datum_stmts] and local definitions from 50 | the given environment in the rhs. 51 | The expression must be [IdentExpr] with a known type. *) 52 | val gen_rhs_to_assign_ident : Context.t -> Ast.env -> Ast.expr -> Ast.expr 53 | 54 | (** Generates an initializer statement for the identifier with known type. 55 | The generated initializer is just a random expression without any use of 56 | the contextual information. 57 | NOTE: userdata and thread types are unsupported. *) 58 | val gen_init_stmt_for_ident : ?assign_local:bool -> Ast.expr -> Ast.stmt 59 | 60 | (** Takes a random binding from the [env] or one of its parents. 61 | Returns None if environment and parents are empty. *) 62 | val take_random_binding : Ast.env -> Ast.expr ref option 63 | 64 | (** Takes a list of expression and combines them to a single expression that 65 | will have requested result type. *) 66 | val combine_to_typed_expr : Context.t -> Ast.ty -> Ast.expr list -> Ast.expr option 67 | 68 | (** Extends the BlockStmt [block] adding the given [stmts] to the end of the 69 | block. *) 70 | val extend_block_stmt : Ast.stmt -> Ast.stmt list -> Ast.stmt 71 | 72 | (** Generates an empty block statement. *) 73 | val gen_empty_block : ?is_loop:bool -> Ast.env -> Ast.stmt 74 | -------------------------------------------------------------------------------- /src/generate.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | 4 | let get_imports_code ctx = 5 | match ctx.Context.ctx_config.Config.c_lib_path with 6 | | Some p -> In_channel.read_all p 7 | | None -> "" 8 | 9 | (** Converts all statement in the [ctx] to Lua code. *) 10 | let ctx_to_string ctx = 11 | let open Ast in 12 | let stmts_to_s ?(cr=true) stmts = 13 | List.fold_left 14 | stmts 15 | ~init:[] 16 | ~f:(fun acc stmt -> acc @ [stmt_to_s ctx.Context.ctx_config stmt ~cr:cr]) 17 | |> String.concat ~sep:"\n" 18 | in 19 | let header = Printf.sprintf({|-------------------------------------------------------- 20 | -- This code was automatically generated by moonsmith -- 21 | -- https://github.com/jubnzv/moonsmith -- 22 | -- -- 23 | -- Seed: %20d -- 24 | -------------------------------------------------------- 25 | |}) ctx.ctx_seed 26 | and imports = get_imports_code ctx 27 | and datums_header = Printf.sprintf({|-------------------------------------------------------- 28 | -- Global datums definitions (%3d statements) -- 29 | --------------------------------------------------------|}) (List.length ctx.ctx_datum_stmts) 30 | and datums_code = stmts_to_s ~cr:false ctx.ctx_datum_stmts 31 | and funcdefs_header = Printf.sprintf({| 32 | -------------------------------------------------------- 33 | -- Function definitions (%3d functions) -- 34 | --------------------------------------------------------|}) (List.length ctx.ctx_funcdef_stmts) 35 | and funcdefs_code = stmts_to_s ctx.ctx_funcdef_stmts 36 | and calls_header = Printf.sprintf({|-------------------------------------------------------- 37 | -- Calling functions -- 38 | --------------------------------------------------------|}) 39 | and calls_code = stmts_to_s ~cr:false ctx.ctx_call_stmts 40 | and result_header = Printf.sprintf({| 41 | -------------------------------------------------------- 42 | -- Combining and printing result -- 43 | --------------------------------------------------------|}) 44 | and result_code = stmts_to_s ~cr:false ctx.ctx_result_stmts 45 | in 46 | String.concat [header; 47 | imports; 48 | datums_header; 49 | datums_code; 50 | funcdefs_header; 51 | funcdefs_code; 52 | calls_header; 53 | calls_code; 54 | result_header; 55 | result_code] 56 | ~sep:"\n" 57 | 58 | (** Generates the whole program for the given [ctx]. 59 | A typical generated program is represented by the following parts: 60 | - data definition 61 | - methods and free functions that mutates the data 62 | - some driver code to call generated functions and methods 63 | - code that combines and prints the global datums *) 64 | let gen_program ctx = 65 | GeneratorDatums.generate ctx 66 | |> GeneratorFuncDefs.generate 67 | |> GeneratorCalls.generate 68 | |> GeneratorResult.generate 69 | |> ctx_to_string 70 | 71 | let generate (c : Config.t) = 72 | let ctx = Context.mk_context c in 73 | Random.init ctx.ctx_seed; 74 | gen_program ctx 75 | -------------------------------------------------------------------------------- /test/run-test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # This script runs moonsmith to generate a random program, then runs a Lua 4 | # interpreter to execute it. If moonsmith hangs, or a Lua interpreter throws an 5 | # exception or can't evaluate result in 1 second, this test will be marked as 6 | # failed. 7 | # 8 | 9 | import argparse 10 | import logging 11 | import os 12 | import signal 13 | import subprocess 14 | import sys 15 | 16 | from typing import Optional 17 | 18 | 19 | logging.basicConfig( 20 | format='[%(asctime)s] %(levelname)-8s %(message)s', 21 | level=logging.INFO, 22 | datefmt='%Y-%m-%d %H:%M:%S') 23 | 24 | 25 | def signal_handler(sig, frame): 26 | sys.exit(0) 27 | 28 | 29 | def append_file(filename, line): 30 | with open(filename, 'a') as f: 31 | f.write(line) 32 | 33 | 34 | def get_comment_header(out): 35 | comment_lines = ["\n"] 36 | for line in out.split("\n"): 37 | comment_lines.append("-- " + line) 38 | comment_lines = comment_lines[:len(comment_lines)-1] 39 | return "\n".join(comment_lines) 40 | 41 | 42 | def run(binary: str, tests_num: Optional[int], timeout: int): 43 | i = 0 44 | try: 45 | subprocess.check_output(["mkdir", "-p", "test/out"]) 46 | except subprocess.CalledProcessError as e: 47 | out = e.output.decode("utf-8") 48 | print(f"Can't create test/out: {out}") 49 | return -1 50 | 51 | logging.info(f"Starting test") 52 | 53 | if tests_num != None: 54 | tests_num = int(tests_num) 55 | 56 | while True: 57 | if i % 100 == 0 and i != 0: 58 | logging.info(f"Passed {i} tests") 59 | if i == tests_num: 60 | break 61 | i = i + 1 62 | try: 63 | out = subprocess.check_output([binary, "-S", "1"], 64 | stderr=subprocess.STDOUT, 65 | timeout=timeout) 66 | except subprocess.CalledProcessError as e: 67 | print(f"{i}: moonsmith's exception: {e}") 68 | continue 69 | try: 70 | out = subprocess.check_output(["lua", "out.lua"], 71 | stderr=subprocess.STDOUT, 72 | timeout=timeout) 73 | except subprocess.CalledProcessError as e: 74 | out = e.output.decode("utf-8") 75 | print(f"{i}: Exception while executing Lua: {out}") 76 | # Write to the end to see correct number of line for an error. 77 | append_file("out.lua", get_comment_header(out)) 78 | subprocess.run(["cp", "out.lua", f"test/out/{i}.lua"]) 79 | except subprocess.TimeoutExpired: 80 | print(f"{i}: Timeout while executing Lua") 81 | subprocess.run(["cp", "out.lua", f"test/out/{i}.lua"]) 82 | 83 | 84 | if __name__ == '__main__': 85 | parser = argparse.ArgumentParser() 86 | parser.add_argument('-b', '--binary', 87 | default='_build/default/src/moonsmith.exe', 88 | help='Path to moonsmith binary') 89 | parser.add_argument('-t', '--timeout', 90 | default=1, 91 | help='Timeout to execute program (sec.)') 92 | parser.add_argument('-n', '--tests-num', 93 | default=None, 94 | help='Number of tests to run.') 95 | args = parser.parse_args() 96 | signal.signal(signal.SIGINT, signal_handler) 97 | run(args.binary, args.tests_num, int(args.timeout)) 98 | -------------------------------------------------------------------------------- /src/generateFor.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let gen_loop_block env = 4 | GenUtil.gen_empty_block env 5 | 6 | let gen_number_for ctx env = 7 | let open Ast in 8 | let (nfor_init, nfor_limit, nfor_step) = 9 | if Random.bool () then 10 | IntExpr(Random.int_incl 0 5), 11 | IntExpr(Random.int_incl 10 20), 12 | IntExpr(Random.int_incl 1 3) 13 | else 14 | IntExpr(Random.int_incl 10 20), 15 | IntExpr(Random.int_incl 0 5), 16 | IntExpr(Random.int_incl 1 3) 17 | in 18 | let block = gen_loop_block env in 19 | let block = 20 | (Random.int_incl 1 3) 21 | |> GenerateLinear.generate_stmts ctx env 22 | |> GenUtil.extend_block_stmt block 23 | in 24 | NumForStmt{ nfor_name = "i"; 25 | nfor_init; 26 | nfor_limit; 27 | nfor_step; 28 | nfor_body = block; } 29 | 30 | let can_generate_generic_loop ctx = 31 | if ctx.Context.ctx_config.Config.c_use_pairs || 32 | ctx.Context.ctx_config.Config.c_use_ipairs then 33 | true else false 34 | 35 | (** Generates function call expression for iterator for a generic for loop. *) 36 | let gen_iterator_exn ctx arg = 37 | let open Ast in 38 | let names = [] in 39 | let names = 40 | if ctx.Context.ctx_config.Config.c_use_pairs 41 | then names @ ["pairs"] else names 42 | in 43 | let names = 44 | if ctx.Context.ctx_config.Config.c_use_ipairs 45 | then names @ ["ipairs"] else names 46 | in 47 | let name = Util.choose_one_exn names in 48 | FuncCallExpr{ fc_id = -1; 49 | fc_ty = FCFunc{ fcf_func = 50 | IdentExpr{ id_id = Context.get_free_idx (); 51 | id_name = name; 52 | id_ty = TyFunction } }; 53 | fc_args = [arg] } 54 | 55 | (** Generates variables used as lhs in the genertic for loop. 56 | It also includes them in the environment of [BlockStmt] which is body of 57 | the loop. *) 58 | let gen_for_names for_body = 59 | let open Ast in 60 | let gen_var name ty env = 61 | let var = IdentExpr{ id_id = Context.get_free_idx (); 62 | id_name = name; 63 | id_ty = ty } 64 | in 65 | env_add_binding env var; 66 | var 67 | in 68 | let gen_wildcard () = 69 | IdentExpr{ id_id = -1; 70 | id_name = "_"; 71 | id_ty = TyNil } 72 | in 73 | match for_body with 74 | | BlockStmt block -> begin 75 | let env = block.block_env in 76 | match Random.int_incl 0 3 with 77 | | 0 -> [(gen_var "i" TyInt env); gen_wildcard ()] 78 | | 1 -> [gen_wildcard (); (gen_var "v" TyAny env)] 79 | | 2 -> [gen_wildcard (); gen_wildcard ()] 80 | | _ -> [(gen_var "i" TyInt env); (gen_var "v" TyAny env)] 81 | end 82 | | _ -> failwith "Impossible: Body of a ForStmt is not a BlockStmt" 83 | 84 | let gen_generic_for ctx env = 85 | let open Ast in 86 | let iterator_arg = 87 | env_find_binding_with_ty env TyTable 88 | |> Option.value ~default:(GenUtil.gen_array_table_init ()) 89 | in 90 | let iterator_funccall = gen_iterator_exn ctx iterator_arg in 91 | let block = gen_loop_block env in 92 | let for_names = gen_for_names block in 93 | let block = 94 | (Random.int_incl 1 3) 95 | |> GenerateLinear.generate_stmts ctx env 96 | |> GenUtil.extend_block_stmt block 97 | in 98 | ForStmt{ for_names; 99 | for_exprs = [iterator_funccall]; 100 | for_body = block } 101 | 102 | let generate ctx env = 103 | let gen = 104 | if not @@ can_generate_generic_loop ctx then gen_number_for 105 | else if Random.bool () then gen_number_for 106 | else gen_generic_for 107 | in 108 | gen ctx env 109 | -------------------------------------------------------------------------------- /src/generatorCalls.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Generates a call statement for a free function with definition of its 4 | parameters, using information about types of the arguments. *) 5 | let gen_fcall_from_fdef stmt = 6 | let open Ast in 7 | match stmt with 8 | | FuncDefStmt fd -> begin 9 | let gen_varags () = 10 | let rec aux acc num = 11 | let len = List.length acc in 12 | if num > len then 13 | let idx = Context.get_free_idx () 14 | and id_ty = Util.choose_one_exn [TyInt; TyFloat; TyBoolean; TyString] 15 | in 16 | let varg = IdentExpr{ id_id = idx; 17 | id_name = Printf.sprintf "vararg%d" len; 18 | id_ty } 19 | in 20 | aux (acc @ [varg]) num 21 | else acc 22 | in 23 | aux [] (Random.int_incl 0 3) 24 | in 25 | (* Generate arguments for function call with their initialization 26 | statements. *) 27 | let (fc_args, fcf_init_stmts) = 28 | (* If function supports varargs, add some. *) 29 | let varags = if fd.fd_has_varags then gen_varags () else [] in 30 | List.fold_left 31 | (fd.fd_args @ varags) 32 | ~init:[] 33 | ~f:(fun acc expr -> begin 34 | match expr with 35 | | IdentExpr id -> begin 36 | let idx = (Context.get_free_idx ()) in 37 | let new_name = Printf.sprintf "%s_%s" fd.fd_name id.id_name in 38 | let new_ident = IdentExpr{ id_id = idx; 39 | id_name = new_name; 40 | id_ty = id.id_ty } in 41 | let init = GenUtil.gen_init_stmt_for_ident ~assign_local:true new_ident in 42 | acc @ [(new_ident, init)] 43 | end 44 | | _ -> failwith "gen_varags works incorrectly" 45 | end) 46 | |> Caml.List.split 47 | in 48 | (* Set type of the function call. *) 49 | let fc_ty = match fd.fd_receiver with 50 | | Some r -> FCMethod { fcm_receiver = r; 51 | fcm_method = fd.fd_name } 52 | | None -> begin 53 | let fcf_func = IdentExpr{ id_id = Context.get_free_idx (); 54 | id_name = fd.fd_name; 55 | id_ty = TyFunction } 56 | in 57 | FCFunc{ fcf_func } 58 | end 59 | in 60 | (* Assign result to variables. *) 61 | let results = List.fold_left 62 | fd.fd_ty 63 | ~init:[] 64 | ~f:(fun acc ty -> begin 65 | let idx = Context.get_free_idx () 66 | and len = List.length acc in 67 | acc @ [IdentExpr{ id_id = idx; 68 | id_name = Printf.sprintf "r_%s_%d" fd.fd_name len; 69 | id_ty = ty }] 70 | end) 71 | in 72 | (* Generate call expression. *) 73 | fcf_init_stmts @ [AssignStmt{ assign_local = true; 74 | assign_lhs = results; 75 | assign_rhs = [FuncCallExpr{ fc_id = fd.fd_id; 76 | fc_ty; 77 | fc_args }]}] 78 | end 79 | | _ -> failwith "Expected FuncDefStmt" 80 | 81 | let generate (ctx : Context.t) = 82 | let open Context in 83 | let call_stmts = List.fold_left 84 | ctx.ctx_funcdef_stmts 85 | ~init:[] 86 | ~f:(fun acc stmt -> begin 87 | match stmt with 88 | | Ast.FuncDefStmt _ -> acc @ gen_fcall_from_fdef stmt 89 | | _ -> acc 90 | end) 91 | in { ctx with ctx_call_stmts = call_stmts } 92 | -------------------------------------------------------------------------------- /src/context.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Random code generation context. *) 4 | type t = { 5 | mutable ctx_datum_stmts: Ast.stmt list; 6 | (** Statements that defines a global data on the top-level. *) 7 | 8 | ctx_funcdef_stmts: Ast.stmt list; 9 | (** Functions and methods defined on the top-level. *) 10 | 11 | ctx_call_stmts: Ast.stmt list; 12 | (** Function calls defined on the top-level. *) 13 | 14 | ctx_result_stmts: Ast.stmt list; 15 | (** Statements that combine and print result data. *) 16 | 17 | mutable ctx_global_env: Ast.env; 18 | (** Global environment for the top-level. *) 19 | 20 | ctx_config : Config.t; 21 | (** User-defined configuration. *) 22 | 23 | mutable ctx_table_fields_map: (int, int list, Int.comparator_witness) Base.Map.t; 24 | (** Map that associates ids of OOP tables with ids of definitions of their 25 | fields. *) 26 | 27 | mutable ctx_func_def_map: (int, Ast.stmt ref, Int.comparator_witness) Base.Map.t; 28 | (** Map that associates ids of FuncDefStmts with pointer to their AST nodes. *) 29 | 30 | ctx_seed: int; 31 | (** Seed used to initialize PRG. *) 32 | } 33 | 34 | let mk_context (c : Config.t) = 35 | let ctx_global_env = Ast.env_mk () 36 | and ctx_seed = 37 | match c.c_seed with Some(v) -> v | _ -> Random.bits () 38 | and ctx_func_def_map = Map.empty (module Int) 39 | and ctx_table_fields_map = Map.empty (module Int) in 40 | let ctx = { ctx_datum_stmts = []; 41 | ctx_funcdef_stmts = []; 42 | ctx_call_stmts = []; 43 | ctx_result_stmts = []; 44 | ctx_config = c; 45 | ctx_func_def_map; 46 | ctx_table_fields_map; 47 | ctx_global_env; 48 | ctx_seed; } 49 | in 50 | ctx 51 | (* let ctx = gen_standard_functions ctx in *) 52 | 53 | let add_to_global_env ctx expr = 54 | Ast.env_add_binding ctx.ctx_global_env expr 55 | 56 | let get_datum_tables ctx = 57 | let ff = function 58 | | Ast.AssignStmt assign -> begin 59 | if not @@ phys_equal 1 @@ List.length assign.assign_lhs then false 60 | else match List.nth_exn assign.assign_lhs 0 with 61 | | Ast.IdentExpr id -> begin 62 | match id.id_ty with 63 | | Ast.TyTable -> true 64 | | _ -> false 65 | end 66 | | _ -> false 67 | end 68 | | _ -> false 69 | in List.filter ctx.ctx_datum_stmts ~f:ff 70 | 71 | let peek_random_datum_exn ctx = 72 | let open Ast in 73 | match Util.choose_one_exn ctx.ctx_datum_stmts with 74 | | AssignStmt assign -> List.nth_exn assign.assign_lhs 0 75 | | _ -> failwith "Expected AssignStmt" 76 | 77 | let peek_typed_datum ctx ty = 78 | let open Ast in 79 | let filter stmt = 80 | match stmt with 81 | | AssignStmt assign -> begin 82 | match List.nth_exn assign.assign_lhs 0 with 83 | | IdentExpr id -> begin 84 | if equal_ty id.id_ty ty then true 85 | else false 86 | end 87 | | _ -> failwith "Impossible: Found non-IdentExpr in LHS of the AssignStmt" 88 | end 89 | | _ -> failwith "Expected AssignStmt" 90 | in 91 | let datums_with_same_ty = 92 | List.filter ctx.ctx_datum_stmts ~f:filter 93 | in 94 | if List.is_empty datums_with_same_ty then None 95 | else begin 96 | match Util.choose_one_exn datums_with_same_ty with 97 | | AssignStmt assign -> List.nth assign.assign_lhs 0 98 | | _ -> failwith "Expected AssignStmt" 99 | end 100 | 101 | let get_datum_tables_i ctx = 102 | let ff i acc stmt = 103 | match stmt with 104 | | Ast.AssignStmt assign -> begin 105 | if not @@ phys_equal 1 @@ List.length assign.assign_lhs then acc 106 | else match List.nth_exn assign.assign_lhs 0 with 107 | | Ast.IdentExpr id -> begin 108 | match id.id_ty with 109 | | Ast.TyTable -> acc @ [(i, Ast.IdentExpr(id))] 110 | | _ -> acc 111 | end 112 | | _ -> acc 113 | end 114 | | _ -> acc 115 | in 116 | List.foldi 117 | ctx.ctx_datum_stmts 118 | ~init:[] 119 | ~f:ff 120 | 121 | let get_free_idx = 122 | let n = ref (-1) in 123 | fun () -> incr n; !n 124 | -------------------------------------------------------------------------------- /src/generateLoop.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Termination condition. *) 4 | type cond_term = 5 | | CondRaising (* need to raise the value to terminate *) 6 | | CondLowering (* need to lower the value to terminate *) 7 | | CondFalse (* need to set to true to terminate *) 8 | | CondTrue (* need to set to false to terminate *) 9 | 10 | (** Generates condition variable used to terminate the loop. We intentionally 11 | don't add it to any environment, because we don't want to perform any 12 | random mutation with this variable. *) 13 | let gen_cond_var () = 14 | let open Ast in 15 | let idx = Context.get_free_idx () in 16 | let id_name = Printf.sprintf "cond%d" idx in 17 | let (rhs, cond_term, id_ty) = match Random.int_incl 0 3 with 18 | | 0 -> (IntExpr(Random.int_incl 0 10), CondRaising, TyInt) 19 | | 1 -> (IntExpr(Random.int_incl 100 125), CondLowering, TyInt) 20 | | 2 -> (FalseExpr, CondTrue, TyBoolean) 21 | | _ -> (TrueExpr, CondFalse, TyBoolean) 22 | in 23 | let ident = IdentExpr{ id_id = idx; 24 | id_name; 25 | id_ty } 26 | in 27 | let assign = AssignStmt{ assign_local = true; 28 | assign_lhs = [ident]; 29 | assign_rhs = [rhs] } 30 | in 31 | (ident, assign, cond_term) 32 | 33 | (** Generates condition of the loop using the [cond] variable. *) 34 | let gen_loop_cond cond_var_ident cond_term = 35 | let open Ast in 36 | match get_essential_ty cond_var_ident with 37 | | Some TyInt -> begin 38 | let (bin_rhs, bin_op) = match cond_term with 39 | | CondRaising -> 40 | let v = IntExpr(Random.int_incl 20 50) in 41 | if Random.bool () then (v, OpLt) else (v, OpLte) 42 | | CondLowering -> 43 | let v = IntExpr(Random.int_incl 50 75) in 44 | if Random.bool () then (v, OpGt) else (v, OpGte) 45 | | _ -> failwith "Generated incorrect condition for the loop" 46 | in 47 | BinExpr{ bin_lhs = cond_var_ident; 48 | bin_op; 49 | bin_rhs } 50 | end 51 | | Some TyBoolean -> begin 52 | let (bin_rhs, bin_op) = match cond_term with 53 | | CondTrue -> 54 | if Random.bool () then (TrueExpr, OpEq) 55 | else (FalseExpr, OpNeq) 56 | | CondFalse -> 57 | if Random.bool () then (FalseExpr, OpEq) 58 | else (TrueExpr, OpNeq) 59 | | _ -> failwith "Generated incorrect condition for the loop" 60 | in 61 | BinExpr{ bin_lhs = cond_var_ident; 62 | bin_op; 63 | bin_rhs } 64 | end 65 | | _ -> failwith "Generated incorrect condition for the loop" 66 | 67 | let gen_term_stmt cond_var_ident cond_term = 68 | let open Ast in 69 | let rhs = match cond_term with 70 | | CondRaising -> BinExpr{ bin_lhs = cond_var_ident; 71 | bin_op = OpAdd; 72 | bin_rhs = IntExpr(Random.int_incl 1 3)} 73 | | CondLowering -> BinExpr{ bin_lhs = cond_var_ident; 74 | bin_op = OpSub; 75 | bin_rhs = IntExpr(Random.int_incl 1 3)} 76 | | CondTrue -> TrueExpr 77 | | CondFalse -> FalseExpr 78 | in AssignStmt{ assign_local = false; 79 | assign_lhs = [cond_var_ident]; 80 | assign_rhs = [rhs] } 81 | 82 | let gen_loop_block ctx env cond_var_ident cond_term = 83 | let block = GenUtil.gen_empty_block env in 84 | let term_stmt = gen_term_stmt cond_var_ident cond_term in 85 | match block with 86 | | Ast.BlockStmt block -> 87 | let body = (Random.int_incl 1 3) |> GenerateLinear.generate_stmts ctx env in 88 | let body = block.block_stmts @ body @ [term_stmt] in 89 | Ast.BlockStmt{ block with block_stmts = body } 90 | | _ -> failwith "Expected BlockStmt" 91 | 92 | let generate ctx env = 93 | let ast_loop_ty = if Random.bool () then Ast.Repeat else Ast.While in 94 | let (cond_var_ident, cond_var_def, cond_term) = gen_cond_var () in 95 | let loop_cond = gen_loop_cond cond_var_ident cond_term in 96 | let loop_block = gen_loop_block ctx env cond_var_ident cond_term in 97 | let loop = Ast.LoopStmt{ loop_cond; 98 | loop_block; 99 | loop_ty = ast_loop_ty } 100 | in 101 | [cond_var_def; loop] 102 | -------------------------------------------------------------------------------- /src/ast.mli: -------------------------------------------------------------------------------- 1 | (** Essential types present in Lua. *) 2 | type ty = 3 | | TyNil 4 | | TyBoolean 5 | | TyInt 6 | | TyFloat 7 | | TyIntString 8 | | TyString 9 | | TyUserdata 10 | | TyFunction 11 | | TyThread 12 | | TyTable 13 | | TyAny 14 | [@@deriving eq, show] 15 | 16 | (** See: https://www.lua.org/manual/5.3/manual.html#3.4.1 *) 17 | type operator = 18 | | OpAdd (* + *) 19 | | OpSub (* - *) 20 | | OpMul (* * *) 21 | | OpDiv (* / *) 22 | | OpFloor (* // *) 23 | | OpPow (* ^ *) 24 | | OpMod (* % *) 25 | | OpConcat (* .. *) 26 | | OpLt (* < *) 27 | | OpLte (* <= *) 28 | | OpGt (* > *) 29 | | OpGte (* >= *) 30 | | OpEq (* == *) 31 | | OpNeq (* ~= *) 32 | | OpBAnd (* & *) 33 | | OpBOr (* ~ *) 34 | | OpBRs (* >> *) 35 | | OpBLs (* << *) 36 | | OpBNot (* ~ *) 37 | | OpAnd (* and *) 38 | | OpOr (* or *) 39 | | OpNot (* not *) 40 | | OpLen (* # *) 41 | 42 | type env = { mutable env_bindings: expr ref list; 43 | mutable env_pending_bindings: expr ref list; 44 | mutable env_parent: env ref option; 45 | mutable env_children: env ref list; } 46 | 47 | and expr = 48 | | TrueExpr 49 | | FalseExpr 50 | | NilExpr 51 | | IntExpr of int 52 | | FloatExpr of float 53 | | StringExpr of string 54 | | IdentExpr of { id_id: int; 55 | id_name: string; 56 | id_ty: ty } 57 | | AttrGetExpr of { ag_obj: expr; 58 | ag_key: expr } 59 | | TableExpr of table_ty 60 | | LambdaExpr of { lambda_args: expr list; 61 | lambda_body: stmt; } 62 | | FuncCallExpr of { fc_id: int; 63 | fc_ty: func_call; 64 | fc_args: expr list } 65 | | UnExpr of { un_op: operator; 66 | un_expr: expr } 67 | | BinExpr of { bin_lhs: expr; 68 | bin_op: operator; 69 | bin_rhs: expr } 70 | and table_ty = 71 | | TArray of { table_elements: expr list } 72 | | THashMap of { table_fields: table_field list } 73 | and table_field = 74 | { tf_key: expr; 75 | tf_value: expr } 76 | and func_call = 77 | | FCMethod of { fcm_receiver: string; 78 | fcm_method: string; } 79 | | FCFunc of { fcf_func: expr } 80 | 81 | and stmt = 82 | | AssignStmt of { assign_local: bool; 83 | assign_lhs: expr list; 84 | assign_rhs: expr list } 85 | | FuncCallStmt of { fc_expr: expr } 86 | | DoBlockStmt of { do_block: stmt } 87 | | LoopStmt of { loop_cond: expr; 88 | loop_block: stmt; 89 | loop_ty: loop_type } 90 | | IfStmt of { if_cond: expr; 91 | if_body: stmt; 92 | if_else: stmt option } 93 | | NumForStmt of { nfor_name: string; 94 | nfor_init: expr; 95 | nfor_limit: expr; 96 | nfor_step: expr; 97 | nfor_body: stmt } 98 | | ForStmt of { for_names: expr list; 99 | for_exprs: expr list; 100 | for_body: stmt } 101 | | FuncDefStmt of { fd_id: int; 102 | (** Receiver (object) which this function belongs to. 103 | If not None, the function is a method. *) 104 | fd_local: bool; 105 | fd_receiver: string option; 106 | fd_name: string; 107 | fd_args: expr list; 108 | fd_has_varags: bool; 109 | fd_body: stmt; 110 | fd_ty: ty list } 111 | | ReturnStmt of { return_exprs: expr list } 112 | | BreakStmt 113 | | BlockStmt of { block_stmts: stmt list; 114 | block_is_loop: bool; 115 | block_env: env } 116 | and loop_type = 117 | | While 118 | | Repeat 119 | 120 | (** Generates unique identifier used in hash tables. *) 121 | val mki : unit -> int 122 | 123 | val ty_to_s : ty -> string 124 | 125 | (** Returns type of the given [expr] when it is known. *) 126 | val get_essential_ty : expr -> ty option 127 | 128 | (** Returns a list of identifier ids for fields from a HashMap table. *) 129 | val get_table_key_ids : expr -> int list 130 | 131 | (** Returns true iff given two types can be used in comparison operations. *) 132 | val types_are_comparable : ty -> ty -> bool 133 | 134 | (** Helper function to gen an environment from the BlockStmt. 135 | Throws an exception if the given [stmt] is not BlockStmt. *) 136 | val get_block_env_exn : stmt -> env 137 | 138 | val op_to_s : operator -> string 139 | 140 | val env_mk : unit -> env 141 | val env_empty : env -> bool 142 | val env_has_parent : env -> bool 143 | val env_get_parent_exn : env -> env 144 | val env_peek_random_exn : env -> expr ref 145 | val env_shuffle_local_bindings : env -> expr list 146 | 147 | (** Find binding with apropriate type in the given environment and its parents 148 | up to [depth]. If [depth] not set, it searches in all available parents. *) 149 | val env_find_binding_with_ty : ?depth:int -> env -> ty -> expr option 150 | 151 | val env_add_binding : env -> expr -> unit 152 | val env_add_pending_binding : env -> expr -> unit 153 | val env_flush_pending_bindings : env -> unit 154 | val env_add_child : env -> env -> unit 155 | 156 | val stmt_to_s : ?cr:bool -> ?depth:int -> Config.t -> stmt -> string 157 | -------------------------------------------------------------------------------- /src/generatorResult.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Gets [expr] that always evaluated to an integer value and 4 | extends it with some random computations. *) 5 | let gen_numeric_assign_rhs expr = 6 | expr 7 | 8 | let get_id_name = function 9 | | Ast.IdentExpr id -> Printf.sprintf "res_%s" id.id_name 10 | | _ -> failwith "Expected IdentExpr" 11 | 12 | (* TODO: It doesn't handle multiple arguments returned from the functions. But 13 | we don't do this for now. *) 14 | let gen_rhs ctx stmt = 15 | let open Ast in 16 | match stmt with 17 | | AssignStmt assign -> begin 18 | let lhs = List.nth_exn assign.assign_lhs 0 in 19 | match lhs with 20 | | IdentExpr id -> begin 21 | match id.id_ty with 22 | | TyNil -> IntExpr(1) 23 | | TyBoolean -> begin 24 | (* Generate alternative of ternary operation in Lua: 25 | http://lua-users.org/wiki/TernaryOperator *) 26 | let bin_rhs = BinExpr{ bin_lhs = IntExpr(1); 27 | bin_op = OpOr; 28 | bin_rhs = IntExpr(0); } 29 | in 30 | BinExpr{ bin_lhs = IdentExpr(id); 31 | bin_op = OpAnd; 32 | bin_rhs } 33 | |> gen_numeric_assign_rhs 34 | end 35 | | TyInt -> gen_numeric_assign_rhs lhs 36 | | TyFloat -> Transform.to_int ctx TyFloat lhs 37 | |> gen_numeric_assign_rhs 38 | | TyString -> Transform.to_int ctx TyString lhs 39 | |> gen_numeric_assign_rhs 40 | | TyIntString -> Transform.to_int ctx TyIntString lhs 41 | |> gen_numeric_assign_rhs 42 | | TyTable -> Transform.to_int ctx TyTable lhs 43 | |> gen_numeric_assign_rhs 44 | | TyUserdata | TyThread | TyAny | TyFunction -> IntExpr(1) 45 | end 46 | | _ -> failwith "Impossible: Found non-IdentExpr in LHS of the assignment" 47 | end 48 | | _ -> failwith "Expected AssignStmt" 49 | 50 | (** Generates statements that assign new temporary variable to result of a 51 | function call. *) 52 | let func_result_to_num ctx acc funccall_stmt = 53 | let open Ast in 54 | match funccall_stmt with 55 | | AssignStmt assign -> begin 56 | if not @@ phys_equal 1 @@ List.length assign.assign_rhs then acc 57 | else match List.nth_exn assign.assign_rhs 0 with 58 | | FuncCallExpr _ -> begin 59 | let func_result = (List.nth_exn assign.assign_lhs 0) in 60 | let id_name = get_id_name func_result in 61 | let lhs = IdentExpr{ id_id = Context.get_free_idx (); 62 | id_name; 63 | id_ty = TyInt} 64 | and rhs = gen_rhs ctx funccall_stmt 65 | in acc @ [AssignStmt{ assign_local = true; 66 | assign_lhs = [lhs]; 67 | assign_rhs = [rhs] }] 68 | end 69 | | _ -> acc 70 | end 71 | | _ -> acc 72 | 73 | (** Generates new statements that assign new temporary variables used to 74 | calculate the result. *) 75 | let datum_to_num ctx acc datum_stmt = 76 | let open Ast in 77 | let id_name = match datum_stmt with 78 | | AssignStmt assign -> get_id_name (List.nth_exn assign.assign_lhs 0) 79 | | _ -> failwith "Expected AssignStmt" 80 | in 81 | let lhs = IdentExpr{ id_id = Context.get_free_idx (); 82 | id_name; 83 | id_ty = TyInt} 84 | and rhs = gen_rhs ctx datum_stmt 85 | in acc @ [AssignStmt{ assign_local = true; 86 | assign_lhs = [lhs]; 87 | assign_rhs = [rhs] }] 88 | 89 | (** Generates a random operator used to combine results. *) 90 | let gen_random_combine_op () = 91 | let open Ast in 92 | match Random.bool () with 93 | | true -> OpAdd (* + *) 94 | | false -> OpSub (* - *) 95 | 96 | (** Generates an assign statements that combines generated intermediate 97 | results to a single number. *) 98 | let gen_combine_stmt num_stmts = 99 | let open Ast in 100 | let combine acc stmt = 101 | match acc with 102 | | None -> begin 103 | match stmt with 104 | | AssignStmt assign -> Some(List.nth_exn assign.assign_lhs 0) 105 | | _ -> failwith "Expected AssignStmt" 106 | end 107 | | Some acc -> begin 108 | match stmt with 109 | | AssignStmt assign -> begin 110 | let bin_lhs = List.nth_exn assign.assign_lhs 0 in 111 | let bin = BinExpr{ bin_lhs; 112 | bin_op = gen_random_combine_op (); 113 | bin_rhs = acc } 114 | in 115 | Some(bin) 116 | end 117 | | _ -> failwith "Expected AssignStmt" 118 | end 119 | in 120 | let combined_expr = List.fold_left 121 | num_stmts 122 | ~init:None 123 | ~f:combine 124 | in 125 | let lhs = IdentExpr{ id_id = Context.get_free_idx (); 126 | id_name = "RESULT"; 127 | id_ty = TyInt; } 128 | and rhs = Option.value combined_expr ~default:(IntExpr(42)) 129 | in 130 | AssignStmt{ assign_local = false; 131 | assign_lhs = [lhs]; 132 | assign_rhs = [rhs]; } 133 | 134 | (** Generates a print statement for the combined result. *) 135 | let gen_print_stmt ctx combine_stmt = 136 | let open Ast in 137 | let result_id = match combine_stmt with 138 | | AssignStmt assign -> begin 139 | match List.nth_exn assign.assign_lhs 0 with 140 | | IdentExpr id -> IdentExpr(id) 141 | | _ -> failwith "Impossible: Found non-IdentExpr in LHS of the assignment" 142 | end 143 | | _ -> failwith "Expected AssignStmt" 144 | in 145 | FuncCallStmt{ fc_expr = StdLib.mk_funccall "print" [ Transform.to_int ctx TyFloat result_id ] } 146 | 147 | let generate (ctx : Context.t) = 148 | let open Context in 149 | let datum_to_num' = datum_to_num ctx in 150 | let func_result_to_num' = func_result_to_num ctx in 151 | let func_results_stmts = List.fold_left 152 | ctx.ctx_call_stmts 153 | ~init:[] 154 | ~f:func_result_to_num' 155 | and datum_stmts = List.fold_left 156 | ctx.ctx_datum_stmts 157 | ~init:[] 158 | ~f:datum_to_num' 159 | in 160 | let result_stmts = datum_stmts @ func_results_stmts in 161 | let combine_stmt = gen_combine_stmt result_stmts in 162 | let print_stmt = gen_print_stmt ctx combine_stmt in 163 | { ctx with ctx_result_stmts = result_stmts @ [combine_stmt; print_stmt] } 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/generatorFuncDefs.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Generate expressions used in ReturnStmt of the function. *) 4 | let gen_return_exprs ctx env return_types = 5 | let open Ast in 6 | let aux ty = 7 | match env_shuffle_local_bindings env with 8 | | [x] -> x 9 | | l -> GenUtil.combine_to_typed_expr ctx ty l 10 | |> Option.value ~default:(GenUtil.gen_simple_expr ~ty:ty ()) 11 | in 12 | let get_ty = function 13 | | TyNil -> aux TyBoolean 14 | | TyTable -> aux TyTable 15 | | TyBoolean -> aux TyBoolean 16 | | TyInt -> aux TyInt 17 | | TyFloat -> aux TyFloat 18 | | TyString -> aux TyString 19 | | TyIntString -> aux TyIntString 20 | | TyThread | TyUserdata | TyFunction | TyAny -> NilExpr 21 | in 22 | (* Function that returns doesn't have expr types is a routine. We don't 23 | need a return statement at all then. *) 24 | if List.is_empty return_types then None 25 | else Some(List.fold_left return_types 26 | ~init:[] 27 | ~f:(fun acc e -> acc @ [get_ty e])) 28 | 29 | (** Randomly peeks a table name and index in [ctx_datum_stmts]. 30 | It will become a receiver (object) for a new method. 31 | If returns None, this means, that it will be a free function. *) 32 | let peek_receiver ctx = 33 | if not ctx.Context.ctx_config.Config.c_gen_oop_methods then None 34 | else match Random.bool () with 35 | | false -> None 36 | | true -> begin 37 | let tables_idxes = Context.get_datum_tables_i ctx in 38 | if List.is_empty tables_idxes then None 39 | else begin 40 | let (idx, table) = Util.choose_one_exn tables_idxes in 41 | match table with 42 | | Ast.IdentExpr id -> Some(idx, id.id_name) 43 | | _ -> None 44 | end 45 | end 46 | 47 | (** Randomly generates some types that this function returns. *) 48 | let gen_return_types () = 49 | let rec aux acc num = 50 | if List.length acc >= num then acc 51 | else aux (acc @ [GenUtil.gen_ty ()]) (num - 1) 52 | in 53 | aux [] (Random.int_incl 1 2) 54 | 55 | (** Generates function arguments and places them in the function's 56 | environment. *) 57 | let gen_args max_args env = 58 | let gen_arg n env = 59 | let name = Printf.sprintf "a%d" n in 60 | GenUtil.gen_ident ~add_now:true ~name:(Some(name)) env 61 | in 62 | let rec aux acc num_args = 63 | let n = List.length acc in 64 | if n >= num_args then 65 | acc 66 | else 67 | aux (acc @ [gen_arg n env]) num_args 68 | in 69 | let num_args = Random.int_incl 0 max_args in 70 | aux [] num_args 71 | 72 | (** Adds a [method_id] to the methods of the datum table that has index [idx] 73 | in the [ctx.ctx_datum_stmts]. *) 74 | let add_method_to_datum_table ctx method_id idx = 75 | let open Context in 76 | let open Ast in 77 | match List.nth_exn ctx.ctx_datum_stmts idx with 78 | | AssignStmt s -> begin 79 | match List.nth_exn s.assign_lhs 0 with 80 | | IdentExpr id -> begin 81 | match id.id_ty with 82 | | TyTable -> begin 83 | let method_ids = Map.find_exn ctx.ctx_table_fields_map id.id_id in 84 | let method_ids = method_ids @ [method_id] in 85 | ctx.Context.ctx_table_fields_map <- Map.set 86 | ~key:idx 87 | ~data:(method_ids) 88 | ctx.Context.ctx_table_fields_map; 89 | end 90 | | _ -> failwith "A given symbol is not a table" 91 | end 92 | | _ -> failwith "Assign statement contains not IdentExpr in lhs" 93 | end 94 | | _ -> failwith "Expected AssignStmt" 95 | 96 | (** Generates a random statement that defines some local variables in the 97 | given [env] *) 98 | let gen_local_def_stmt ctx env = 99 | let stmts_in_assign = Random.int_incl 1 3 in 100 | let rec aux acc = 101 | if (List.length acc) > stmts_in_assign then 102 | acc 103 | else begin 104 | let lhs = GenUtil.gen_ident env 105 | ~name:(Some(Printf.sprintf "v%d" @@ Context.get_free_idx ())) 106 | in 107 | let rhs = GenUtil.gen_rhs_to_assign_ident ctx env lhs in 108 | aux (acc @ [(lhs, rhs)]) 109 | end 110 | in 111 | let (assign_lhs, assign_rhs) = aux [] |> Caml.List.split in 112 | Ast.env_flush_pending_bindings env; 113 | Ast.AssignStmt{ assign_local = Random.bool (); 114 | assign_lhs; 115 | assign_rhs } 116 | 117 | (** Generates a random statements that changes some data in the in the given 118 | [env] or changes some global datum in [ctx.ctx_datum_stmts]. *) 119 | let gen_mutation_stmts ctx env = 120 | match Random.int_incl 0 4 with 121 | | 0 | 1 -> [GenerateLinear.generate ctx env] 122 | | 2 -> [GenerateCond.generate ctx env] 123 | | 3 -> GenerateLoop.generate ctx env 124 | | _ -> [GenerateFor.generate ctx env] 125 | 126 | (** Generates a block statement that will be a body of the generated 127 | function. *) 128 | let gen_body ctx block = 129 | let open Ast in 130 | let rec gen_nested_stmt gen acc env num = 131 | if List.length acc >= num then 132 | acc 133 | else 134 | let stmt = gen ctx env in 135 | let acc = acc @ [stmt] in 136 | gen_nested_stmt gen acc env num 137 | in 138 | let rec gen_nested_stmts gen acc env num = 139 | if List.length acc >= num then 140 | acc 141 | else 142 | let stmts = gen ctx env in 143 | let acc = acc @ stmts in 144 | gen_nested_stmts gen acc env num 145 | in 146 | match block with 147 | | BlockStmt block -> begin 148 | let num_local_defs = Random.int_incl 2 3 in 149 | let local_def_stmts = 150 | gen_nested_stmt gen_local_def_stmt 151 | [] 152 | block.block_env 153 | num_local_defs 154 | in 155 | let num_mutations = Random.int_incl 4 6 in 156 | let mutation_stmts = 157 | gen_nested_stmts gen_mutation_stmts 158 | [] 159 | block.block_env 160 | num_mutations 161 | in 162 | BlockStmt{ block with block_stmts = local_def_stmts @ mutation_stmts } 163 | end 164 | | _ -> failwith "Impossible: Body of a ForStmt is not a BlockStmt" 165 | 166 | (** Fills body of the given function. *) 167 | let fill_funcdef ctx fd = 168 | let open Ast in 169 | match fd with 170 | | FuncDefStmt fd -> begin 171 | let fd_body = gen_body ctx fd.fd_body in 172 | let fd_body = 173 | match gen_return_exprs ctx (get_block_env_exn fd.fd_body) fd.fd_ty with 174 | | Some return_exprs -> begin 175 | [ReturnStmt{ return_exprs }] |> GenUtil.extend_block_stmt fd_body 176 | end 177 | | None -> fd_body 178 | in 179 | FuncDefStmt{ fd with fd_body = fd_body} 180 | end 181 | | _ -> failwith "Impossible: Body of a ForStmt is not a BlockStmt" 182 | 183 | (** Generates a random function defined on the top-level. 184 | Generated function contains an empty body. *) 185 | let gen_funcdef ctx = 186 | let open Ast in 187 | let fd_id = mki () in 188 | let (fd_name, fd_receiver, fd_local) = match peek_receiver ctx with 189 | | None -> (Printf.sprintf "func%d" @@ Context.get_free_idx (), None, Random.bool ()) 190 | | Some (datums_idx, receiver_name) -> begin 191 | let name = Printf.sprintf "m%d" @@ Context.get_free_idx () in 192 | add_method_to_datum_table ctx fd_id datums_idx; 193 | (name, Some(receiver_name), false) (* methods can not be local *) 194 | end 195 | and fd_body = GenUtil.gen_empty_block ctx.ctx_global_env in 196 | let fd_args = gen_args 5 (get_block_env_exn fd_body) 197 | and fd_has_varags = if phys_equal 0 @@ Random.int_incl 0 10 then true else false 198 | and fd_ty = gen_return_types () in 199 | let fd = FuncDefStmt{ fd_id; 200 | fd_local; 201 | fd_receiver; 202 | fd_name; 203 | fd_args; 204 | fd_has_varags; 205 | fd_body; 206 | fd_ty } 207 | in 208 | ctx.Context.ctx_func_def_map <- Map.set 209 | ~key:fd_id 210 | ~data:(ref fd) 211 | ctx.Context.ctx_func_def_map; 212 | fd 213 | 214 | let generate (ctx : Context.t) = 215 | let open Context in 216 | let open Config in 217 | let rec aux acc num = 218 | if List.length acc >= num then acc 219 | else aux (acc @ [gen_funcdef ctx]) num 220 | in 221 | let ctx_funcdef_stmts = aux [] @@ Random.int_incl 222 | ctx.ctx_config.c_min_toplevel_funcdefs 223 | ctx.ctx_config.c_max_toplevel_funcdefs 224 | in 225 | let ctx_funcdef_stmts = List.fold_left 226 | ctx_funcdef_stmts 227 | ~init:[] 228 | ~f:(fun acc fd -> acc @ [fill_funcdef ctx fd]) 229 | in 230 | { ctx with ctx_funcdef_stmts = ctx_funcdef_stmts } 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moonsmith 2 | 3 | ![](https://github.com/jubnzv/moonsmith/workflows/Build/badge.svg) 4 | 5 | moonsmith is a generator of random Lua 5.3 programs. 6 | 7 | This tool can be used to evaluate tooling that works with Lua: parsers, transpilers, code analyzers, etc. 8 | 9 | ## Description 10 | 11 | moonsmith generates a syntactically and semantically correct Lua program, which always successfully terminates in a short time. 12 | 13 | The core idea behind the algorithm of generated programs is to generate some global data and a few functions and methods, which mutate it. At the end of top-level a generated program calls all functions and combines results to a single integer. Then it prints this integer to the stdout. 14 | 15 | Here is an example of randomly generated program: 16 | 17 |
18 | out.lua 19 | 20 | ```lua 21 | -------------------------------------------------------- 22 | -- This code was automatically generated by moonsmith -- 23 | -- https://github.com/jubnzv/moonsmith -- 24 | -- -- 25 | -- Seed: 42 -- 26 | -------------------------------------------------------- 27 | 28 | -------------------------------------------------------- 29 | -- Utility functions. -- 30 | -------------------------------------------------------- 31 | ms = {} 32 | 33 | -- Generates a random integer for an arbitrary table. 34 | -- @param t table 35 | function ms.table_to_int(t) 36 | acc = 0 37 | for i, v in ipairs(t) do 38 | if type(v) == "number" then 39 | acc = acc + v 40 | else 41 | acc = acc & i 42 | end 43 | end 44 | return acc + #t 45 | end 46 | 47 | -------------------------------------------------------- 48 | -- Global datums definitions ( 5 statements) -- 49 | -------------------------------------------------------- 50 | datum0 = {} 51 | datum1 = {} 52 | local datum2 = {} 53 | datum3 = {} 54 | datum4 = 33 55 | 56 | -------------------------------------------------------- 57 | -- Function definitions ( 5 functions) -- 58 | -------------------------------------------------------- 59 | -- @return float 60 | function datum2:m5() 61 | local v22, v24, v26 = [[et 62 | ullamco 63 | laborum]], 62.085799, 26 64 | local v28, v30, v32 = "laborum in", -48.609145, "tempor quis" 65 | v24 = 5.729946 - math.pi - -(math.pi) 66 | v22 = "anim" 67 | local cond35 = 9 68 | repeat 69 | v24 = math.cos(v24) - 56.973951 - -(math.pi) 70 | v24 = math.abs(v24) - 73.589347 - -(math.abs(v24)) 71 | cond35 = cond35 + 3 72 | until cond35 <= 45 73 | v22 = "minim culpa eiusmod" 74 | local cond37 = 5 75 | repeat 76 | v22 = math.type(85) 77 | v22 = math.type(53) 78 | cond37 = cond37 + 0x3 79 | until cond37 <= 41 80 | return math.pi - -(math.cos(v24)) 81 | end 82 | 83 | -- @param a0 float 84 | -- @param a1 boolean 85 | -- @param a2 string 86 | -- @param a3 int 87 | -- @param a4 string 88 | -- @return nil 89 | function func6(a0, a1, a2, a3, a4) 90 | v39, v41 = -97.502688, "2" 91 | local v43, v45, v47, v49 = "nisi", -77, a2, a1 92 | v51, v53 = datum4, a1 93 | for _, v in ipairs(datum3) do 94 | a2 = a4 95 | v39 = math.pi - -(72.747706) 96 | end 97 | v43 = math.type(-78) 98 | for i, _ in ipairs(datum1) do 99 | v41 = "1" 100 | end 101 | if a3 ~= 44 then 102 | a3 = -50 + ~ -78 103 | a0 = 80.030963 - 2.273377 - -(math.abs(9)) 104 | a3 = 94 + ~ string.len([[nulla]]) 105 | else 106 | a3 = -61 - ~ #(a2) 107 | a4 = math.type(a3) 108 | end 109 | return false ~= a1 == "adipiscing nostrud" == a2 ~= math.ult(a3, 4) ~= [[et 110 | nisi 111 | dolore]] ~= a4 == true == "nostrud exercitation cillum" == v41 == not "in do reprehenderit pariatur" ~= v43 112 | end 113 | 114 | -- @param a0 string 115 | -- @return string 116 | local func12 = function (a0) 117 | v62, v64, v66 = false, "1", [[2]] 118 | v68, v70, v72, v74 = a0, datum4, v62, "veniam dolore" 119 | v76, v78, v80 = "nostrud qui consequat", [[proident 120 | aliquip 121 | Ut]], "9" 122 | if v64 ~= "3" then 123 | v62 = math.ult(-98, -6) == not true ~= false 124 | a0 = v64 125 | a0 = tostring(-73) 126 | else 127 | v64 = "5" 128 | v64 = tostring(-60) 129 | end 130 | v66 = "1" 131 | a0 = v64 132 | if a0 ~= "4" then 133 | v62 = false ~= "quis nisi nisi" == a0 ~= not math.ult(-11, 12) 134 | v62 = false ~= not "ut" ~= "esse proident in" 135 | v62 = false == false == not false == v62 136 | else 137 | v62 = true ~= true == false == not "adipiscing consequat" == a0 138 | v66 = a0 139 | a0 = "8" 140 | end 141 | if v62 == true then 142 | a0 = "2" 143 | else 144 | v64 = v66 145 | v62 = math.ult(-4, 11) == "proident in fugiat laborum" == v64 ~= not math.ult(-37, 0x5) 146 | end 147 | return a0 148 | end 149 | 150 | -- @param a0 string 151 | -- @param a1 string 152 | -- @param a2 boolean 153 | -- @param a3 boolean 154 | -- @return float 155 | local func14 = function (a0, a1, a2, a3) 156 | local v82, v84 = -93.709911, 78.054212 157 | local v86, v88, v90 = [[9]], "8", true 158 | if datum3 == {} then 159 | v84 = 49.225268 + -(math.abs(113.449562)) 160 | a2 = true == a3 == not true == true 161 | v82 = 7.013714 + math.pi - -(math.pi) 162 | else 163 | v82 = 6.035996 + math.sin(v84) - -(math.pi) 164 | a2 = false == a3 ~= not false ~= true 165 | end 166 | v82 = math.sin(v84) + math.pi + -(math.abs(0.302638)) 167 | for _, v in ipairs(datum1) do 168 | a1 = "8" 169 | end 170 | local cond94 = false 171 | repeat 172 | a0 = math.type(0x21) 173 | a0 = "consequat in elit" 174 | cond94 = true 175 | until cond94 ~= false 176 | return math.pi + 63.696019 - 13.263112 + 0x1.9b9000b262431p+2 - math.abs(v82) + -(math.sin(v84)) 177 | end 178 | 179 | -- @param a0 float 180 | -- @param a1 string 181 | -- @return nil 182 | local func19 = function (a0, a1) 183 | v96, v98, v100, v102 = -39.158628, -15, "4", a0 184 | v104, v106, v108, v110 = "4", "qui proident", "9", "7" 185 | v96 = math.tan(v96) + math.pi + -(17.112416) 186 | if a0 == -29.895999 then 187 | a1 = "nulla quis" 188 | v98 = math.floor(v96) & ~ -17 189 | else 190 | v96 = math.pi + -(35.132890) 191 | end 192 | v98 = math.floor(a0) ~ -54 + ~ 55 193 | v96 = math.pi - -(math.pi) 194 | for i=0,0xe,2 do 195 | v96 = math.tan(v96) + -(math.tan(51.779741)) 196 | v98 = -38 ~ -(string.len("cillum")) 197 | end 198 | return false ~= "aute fugiat veniam nostrud" ~= a1 == false == not math.ult(v98, 0xd) 199 | end 200 | 201 | -------------------------------------------------------- 202 | -- Calling functions -- 203 | -------------------------------------------------------- 204 | local r_m5_0 = datum2:m5() 205 | local func6_a0 = 41.181777 206 | local func6_a1 = true 207 | local func6_a2 = "sed in in" 208 | local func6_a3 = -31 209 | local func6_a4 = "voluptate sed laborum ea" 210 | local r_func6_0 = func6(func6_a0, func6_a1, func6_a2, func6_a3, func6_a4) 211 | local func12_a0 = "9" 212 | local r_func12_0 = func12(func12_a0) 213 | local func14_a0 = "officia dolor" 214 | local func14_a1 = [[1]] 215 | local func14_a2 = true 216 | local func14_a3 = true 217 | local r_func14_0 = func14(func14_a0, func14_a1, func14_a2, func14_a3) 218 | local func19_a0 = 95.503889 219 | local func19_a1 = [[eu 220 | consequat 221 | esse]] 222 | local r_func19_0 = func19(func19_a0, func19_a1) 223 | 224 | -------------------------------------------------------- 225 | -- Combining and printing result -- 226 | -------------------------------------------------------- 227 | local res_datum0 = ms.table_to_int(datum0) 228 | local res_datum1 = #(datum1) 229 | local res_datum2 = ms.table_to_int(datum2) 230 | local res_datum3 = #(datum3) 231 | local res_datum4 = datum4 232 | local res_r_m5_0 = math.floor(r_m5_0) 233 | local res_r_func6_0 = 1 234 | local res_r_func12_0 = math.floor(r_func12_0 + 1) 235 | local res_r_func14_0 = math.floor(r_func14_0) 236 | local res_r_func19_0 = 1 237 | RESULT = res_r_func19_0 + res_r_func14_0 - res_r_func12_0 + res_r_func6_0 + res_r_m5_0 - res_datum4 + res_datum3 - res_datum2 - res_datum1 + res_datum0 238 | print(math.floor(RESULT)) 239 | ``` 240 | 241 |
242 | 243 | ## Installation 244 | 245 | The simplest way to install the tool is to download it from the [releases page](https://github.com/jubnzv/moonsmith/releases). 246 | 247 | Otherwise you should build it from sources. 248 | 249 | Install the latest OCaml compiler and opam. Consider installation instructions at [ocaml.org](https://ocaml.org/docs/install.html) and [opam.ocaml.org](https://opam.ocaml.org/doc/Install.html). 250 | 251 | Then clone the repository and install required dependencies: 252 | 253 | ```bash 254 | git clone https://github.com/jubnzv/moonsmith.git 255 | cd moonsmith 256 | opam install --deps-only . # first time only 257 | ``` 258 | 259 | Then build and install `moonsmith` binary: 260 | 261 | ```bash 262 | dune build --profile release 263 | dune install --prefix output 264 | ``` 265 | 266 | You'll get a statically linked binary at `output/bin/moonsmith`. 267 | 268 | ## Usage 269 | 270 | You can simply call binary to get randomly-generated Lua program at `out.lua`: 271 | 272 | ```bash 273 | output/bin/moonsmith 274 | ``` 275 | 276 | The complete set of command-line options is available through `--help` option. 277 | 278 | To perform evaluation of your tool that works with Lua, it may be convenient to write a script that generates a random program and runs your tool over it to check output or return code. You can check an example of such script in the test suite: [run-test.py](./test/run-test.py). 279 | 280 | You can also provide some subtle configuration using a configuration file. By default, moonsmith looks it at `moonsmith.yml`, but you can set the specific file using `-c` command-line option. Using this file you can disable some Lua constructions which your tooling doesn't support yet. See the default config at [moonsmith.yml](./moonsmith.yml). 281 | -------------------------------------------------------------------------------- /src/transform.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Generates expression that converts the given [IdentExpr] from 4 | float to integer. *) 5 | let float_to_int ctx expr = 6 | let can_convert_with_floor () = 7 | ctx.Context.ctx_config.Config.c_use_tostring && 8 | ctx.Context.ctx_config.Config.c_use_string_sub && 9 | ctx.Context.ctx_config.Config.c_use_length 10 | in 11 | let open Ast in 12 | if ctx.Context.ctx_config.Config.c_use_math_floor then begin 13 | StdLib.mk_funccall "math.floor" [expr] 14 | end 15 | else if can_convert_with_floor () then begin 16 | (* Floor with '// 1' and convert to string using 'tostring()'. 17 | Then remove .0 from the string and convert to integer. *) 18 | let floored = BinExpr{ bin_lhs = expr; 19 | bin_op = OpFloor; 20 | bin_rhs = IntExpr(1) } 21 | in 22 | let tostring = StdLib.mk_funccall "tostring" [floored] in 23 | let len_expr = BinExpr{ bin_lhs = UnExpr{ un_op = OpLen; 24 | un_expr = tostring }; 25 | bin_op = OpSub; 26 | bin_rhs = IntExpr(2) } 27 | in 28 | StdLib.mk_funccall "string.sub" [tostring; IntExpr(1); len_expr] 29 | end 30 | else (* Well, we can't convert it in such restricted case. *) 31 | IntExpr(Random.int_incl 1 10) 32 | 33 | (** Generates expression that converts the given [IdentExpr] from 34 | string to integer. *) 35 | let string_to_int ctx expr = 36 | let open Ast in 37 | let open Context in 38 | let open Config in 39 | let fallback () = IntExpr(Random.int_incl 1 10) in 40 | Util.choose [(ctx.ctx_config.c_use_length, 41 | lazy (UnExpr{ un_op = OpLen; un_expr = expr })); 42 | (ctx.ctx_config.c_use_string_len, 43 | lazy (StdLib.mk_funccall "string.len" [expr]))] 44 | @@ lazy (fallback ()) 45 | 46 | (** Generates expression that converts the given [IdentExpr] from 47 | string containing only integers to integer. *) 48 | let int_string_to_int ctx expr = 49 | let open Ast in 50 | let open Context in 51 | let open Config in 52 | let fallback () = IntExpr(Random.int_incl 1 10) in 53 | Util.choose [(ctx.ctx_config.c_use_tonumber), 54 | lazy (StdLib.mk_funccall "tonumber" [expr]); 55 | (* Use string conversion: "123"+1*) 56 | (true, 57 | lazy (BinExpr{ bin_lhs = expr; 58 | bin_op = OpAdd; 59 | bin_rhs = IntExpr(Random.int_incl 0 3) } 60 | |> float_to_int ctx))] 61 | @@ lazy (fallback ()) 62 | 63 | (** Generates expression that converts the given [IdentExpr] from 64 | table to integer. *) 65 | let table_to_int ctx expr = 66 | let open Ast in 67 | let open Config in 68 | let open Context in 69 | let fallback () = IntExpr(Random.int_incl 1 10) in 70 | Util.choose [(ctx.ctx_config.c_use_length), 71 | lazy (UnExpr{ un_op = OpLen; un_expr = expr }); 72 | (Option.is_some ctx.ctx_config.c_lib_path), 73 | lazy (StdLib.mk_funccall "ms.table_to_int" [expr])] 74 | @@ lazy (fallback ()) 75 | 76 | let to_nil ctx ty expr = 77 | let open Ast in 78 | let open Config in 79 | let open Context in 80 | let fallback () = NilExpr in 81 | match ty with 82 | | TyString -> begin 83 | (* Using tonumber() with non-numeric string results as nil. *) 84 | Util.choose [(ctx.ctx_config.c_use_tonumber), 85 | lazy (StdLib.mk_funccall "tonumber" [expr])] 86 | @@ lazy (fallback ()) 87 | end 88 | | TyInt | TyFloat -> begin 89 | (* Using 'nil and ' always results as nil. *) 90 | Util.choose [(true), 91 | lazy (BinExpr{ bin_lhs = NilExpr; 92 | bin_op = OpAnd; 93 | bin_rhs = expr })] 94 | @@ lazy (fallback ()) 95 | end 96 | | TyNil -> expr 97 | | _ -> fallback () 98 | 99 | let to_boolean ctx ty expr = 100 | let open Ast in 101 | let open Context in 102 | let open Config in 103 | let fallback () = if Random.bool () then TrueExpr else FalseExpr in 104 | match ty with 105 | | TyBoolean -> begin 106 | Util.choose_lazy_exn [ 107 | lazy (BinExpr{ bin_lhs = fallback (); 108 | bin_op = Util.choose_one_exn [OpEq; OpNeq]; 109 | bin_rhs = expr }); 110 | lazy (expr);] 111 | end 112 | | TyInt -> begin 113 | Util.choose [(ctx.ctx_config.c_use_math_ult), 114 | lazy (StdLib.mk_funccall "math.ult" [expr; IntExpr(Random.int_incl (-20) 20)]) ] 115 | @@ lazy (fallback ()) 116 | end 117 | | TyFloat -> fallback () 118 | | TyString | TyIntString -> begin 119 | Util.choose [(true), 120 | lazy (BinExpr{ bin_lhs = StringExpr(StringGen.gen_string ()); 121 | bin_op = Util.choose_one_exn [OpEq; OpNeq]; 122 | bin_rhs = expr })] 123 | @@ lazy (fallback ()) 124 | end 125 | | TyTable | TyFunction -> begin 126 | Util.choose [(true), 127 | lazy (BinExpr{ bin_lhs = expr; 128 | bin_op = Util.choose_one_exn [OpEq; OpNeq]; 129 | bin_rhs = NilExpr })] 130 | @@ lazy (fallback ()) 131 | end 132 | | TyNil -> begin 133 | (* Some logical operations between Booleans and nils returns Booleans. *) 134 | Util.choose_lazy_exn [ 135 | lazy (BinExpr{ bin_lhs = TrueExpr; 136 | bin_op = OpOr; 137 | bin_rhs = expr }); 138 | lazy (BinExpr{ bin_lhs = expr; 139 | bin_op = OpAnd; 140 | bin_rhs = FalseExpr }); 141 | (* 'not nil' always returns true. *) 142 | lazy (UnExpr{ un_op = OpNot; 143 | un_expr = expr }) ] 144 | end 145 | | TyUserdata | TyThread | TyAny -> fallback () 146 | 147 | let to_int ctx ty expr = 148 | let open Ast in 149 | let fallback () = IntExpr(Random.int_incl (-100) 100) in 150 | match ty with 151 | | TyNil -> fallback () 152 | | TyBoolean -> fallback () 153 | | TyInt -> expr 154 | | TyFloat -> float_to_int ctx expr 155 | | TyString -> string_to_int ctx expr 156 | | TyIntString -> int_string_to_int ctx expr 157 | | TyFunction -> fallback () (* TODO: Check return type and call, but prevent recursion. *) 158 | | TyTable -> table_to_int ctx expr 159 | | TyThread | TyUserdata | TyAny -> fallback () 160 | 161 | let to_float ctx ty expr = 162 | let open Ast in 163 | let open Context in 164 | let open Config in 165 | let fallback () = 166 | if ctx.ctx_config.c_use_math_pi && Random.bool () then 167 | StdLib.mk_ident ~ty:TyFloat "math.pi" 168 | else 169 | FloatExpr(Random.float 100.0) 170 | in 171 | match ty with 172 | | TyInt | TyFloat -> begin 173 | Util.choose [(ctx.ctx_config.c_use_math_sin, 174 | lazy (StdLib.mk_funccall "math.sin" [expr])); 175 | (ctx.ctx_config.c_use_math_cos, 176 | lazy (StdLib.mk_funccall "math.cos" [expr])); 177 | (ctx.ctx_config.c_use_math_tan, 178 | lazy (StdLib.mk_funccall "math.tan" [expr])); 179 | (ctx.ctx_config.c_use_math_abs, 180 | lazy (StdLib.mk_funccall "math.abs" [expr])); 181 | ] 182 | @@ lazy (fallback ()) 183 | end 184 | | _ -> fallback () 185 | 186 | (** Generates a small lambda function that converts an argument string by some 187 | way. *) 188 | let generate_str_lambda ctx = 189 | let open Ast in 190 | let open Config in 191 | let open Context in 192 | let arg = IdentExpr{ id_id = -1; 193 | id_name = "s"; 194 | id_ty = TyFunction } 195 | in 196 | let fallback () = arg in 197 | let body_expr = Util.choose [(ctx.ctx_config.c_use_string_lower, 198 | lazy (StdLib.mk_funccall "string.lower" [arg])); 199 | (ctx.ctx_config.c_use_string_upper, 200 | lazy (StdLib.mk_funccall "string.upper" [arg])); 201 | (true, 202 | lazy (fallback ()))] 203 | @@ lazy (fallback ()) 204 | in 205 | let lambda_body = ReturnStmt{ return_exprs = [body_expr] } in 206 | LambdaExpr{ lambda_args = [arg]; 207 | lambda_body } 208 | 209 | let to_string ctx ty expr = 210 | let open Ast in 211 | let open Config in 212 | let open Context in 213 | let fallback () = StringExpr(StringGen.gen_string ()) in 214 | match ty with 215 | | TyInt -> begin 216 | if ctx.ctx_config.c_use_math_type then 217 | StdLib.mk_funccall "math.type" [expr] 218 | else 219 | fallback () 220 | end 221 | | TyFloat -> begin 222 | if ctx.ctx_config.c_use_math_type then 223 | StdLib.mk_funccall "math.type" [expr] 224 | else 225 | if Random.bool () then fallback () else expr 226 | end 227 | | TyString -> begin 228 | (* Functions from the `string` module are horrible slow. Reduce their calls as much as possible. *) 229 | Util.choose [(ctx.ctx_config.c_use_string_reverse && phys_equal 0 @@ Random.int_incl 0 100, 230 | lazy (StdLib.mk_funccall "string.reverse" [expr])); 231 | (ctx.ctx_config.c_use_string_gsub && phys_equal 0 @@ Random.int_incl 0 100, 232 | lazy (StdLib.mk_funccall "string.gsub" [expr; 233 | Ast.StringExpr(StringGen.gen_regexp_string ()); 234 | Ast.StringExpr("")])); 235 | (* string.gsub with a function as an argument: https://www.lua.org/pil/20.3.html *) 236 | (ctx.ctx_config.c_use_string_gsub && phys_equal 0 @@ Random.int_incl 0 100, 237 | lazy (StdLib.mk_funccall "string.gsub" [expr; 238 | Ast.StringExpr(StringGen.gen_regexp_string ()); 239 | generate_str_lambda ctx])); 240 | (true, 241 | lazy (expr))] 242 | @@ lazy (fallback ()) 243 | end 244 | | _ -> fallback () 245 | 246 | let to_int_string ctx ty expr = 247 | let open Ast in 248 | let open Config in 249 | let open Context in 250 | let fallback () = StringExpr(StringGen.gen_int_string ()) in 251 | match ty with 252 | | TyInt -> begin 253 | if ctx.ctx_config.c_use_tostring then 254 | StdLib.mk_funccall "tostring" [expr] 255 | else 256 | fallback () 257 | end 258 | | TyFloat -> begin 259 | Util.choose [(ctx.ctx_config.c_use_tostring), 260 | lazy (StdLib.mk_funccall "tostring" [float_to_int ctx expr])] 261 | @@ lazy (fallback ()) 262 | end 263 | | TyIntString -> expr 264 | | _ -> fallback () 265 | 266 | let to_function _ _ expr = 267 | let open Ast in 268 | let fallback () = 269 | let lambda_body = 270 | ReturnStmt{ return_exprs = [expr] } 271 | in 272 | LambdaExpr{ lambda_args = []; 273 | lambda_body } 274 | in 275 | fallback () 276 | 277 | let to_table _ ty expr = 278 | let open Ast in 279 | let fallback () = 280 | let dummy = IntExpr(Random.int_incl (-10) 10) in 281 | TableExpr(TArray{ table_elements = [dummy] }) 282 | in 283 | match ty with 284 | | TyTable -> expr 285 | | TyFloat | TyInt -> begin 286 | (* Generate an array table. *) 287 | Util.choose_lazy_exn [lazy (TableExpr(TArray{ table_elements = [expr] })); 288 | lazy (fallback ())] 289 | end 290 | | TyString | TyIntString | TyBoolean | TyNil -> begin 291 | (* Generate a hashmap table. *) 292 | Util.choose_lazy_exn [ 293 | lazy (let idx = Context.get_free_idx () in 294 | TableExpr(THashMap{ 295 | table_fields = [{ tf_key = IdentExpr{ id_id = idx; 296 | id_name = Printf.sprintf "t%d" idx; 297 | id_ty = ty}; 298 | tf_value = expr }] })); 299 | lazy (fallback ())] 300 | end 301 | | TyThread | TyUserdata | TyAny | TyFunction -> fallback () 302 | -------------------------------------------------------------------------------- /src/config.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** User-defined options for generating random programs. *) 4 | type t = { 5 | c_indent: string; 6 | (** Indentation symbols used in generated Lua code. *) 7 | 8 | c_lib_path: string option; 9 | (** Path to external Lua module used in runtime. *) 10 | 11 | c_min_toplevel_datums: int; 12 | (** Minimum number of datums defined on the top-level. *) 13 | 14 | c_max_toplevel_datums: int; 15 | (** Maximum number of datums defined on the top-level. *) 16 | 17 | c_min_toplevel_funcdefs: int; 18 | (** Minimum number of functions and methods defined on the top-level. *) 19 | 20 | c_max_toplevel_funcdefs: int; 21 | (** Maximum number of functions and methods defined on the top-level. *) 22 | 23 | c_stdout: bool; 24 | (** Prints generated program to stdout. *) 25 | 26 | c_seed: int option; 27 | (** Seed used to initialize random generator. If None, seed will be choosen randomly. *) 28 | 29 | c_gen_oop_methods: bool; 30 | (** If true, generate some random OOP methods for the datum tables. *) 31 | 32 | c_use_hex_floats: bool; 33 | (** Use hexademical floats introduced in Lua 5.3. *) 34 | 35 | (* *** Language features *** *) 36 | 37 | c_use_length: bool; 38 | (** Use length ('#') operators on strings and tables. *) 39 | 40 | c_use_pairs: bool; 41 | (** Use 'pairs' function. *) 42 | 43 | c_use_ipairs: bool; 44 | (** Use 'ipairs' function. *) 45 | 46 | c_use_tostring: bool; 47 | (** Use tostring(). *) 48 | 49 | c_use_tonumber: bool; 50 | (** Use tonumber(). *) 51 | 52 | c_use_string_upper: bool; 53 | (** Use string.upper(). *) 54 | 55 | c_use_string_lower: bool; 56 | (** Use string.lower(). *) 57 | 58 | c_use_string_sub: bool; 59 | (** Use string.sub(). *) 60 | 61 | c_use_string_reverse: bool; 62 | (** Use string.reverse(). *) 63 | 64 | c_use_string_gsub: bool; 65 | (** Use string.gsub(). *) 66 | 67 | c_use_string_len: bool; 68 | (** Use string.len(). *) 69 | 70 | c_use_math_type: bool; 71 | (** Use math.type() *) 72 | 73 | c_use_math_ult: bool; 74 | (** Use math.ult() *) 75 | 76 | c_use_math_sin: bool; 77 | (** Use math.sin() *) 78 | 79 | c_use_math_cos: bool; 80 | (** Use math.cos() *) 81 | 82 | c_use_math_tan: bool; 83 | (** Use math.tan() *) 84 | 85 | c_use_math_abs: bool; 86 | (** Use math.abs() *) 87 | 88 | c_use_math_pi: bool; 89 | (** Use math.pi *) 90 | 91 | c_use_math_log: bool; 92 | (** Use math.log() *) 93 | 94 | c_use_math_floor: bool; 95 | (** Use math.floor(). *) 96 | } 97 | 98 | let mk_default () = 99 | { c_indent = " "; 100 | c_lib_path = Some("lua/lib.lua"); 101 | c_min_toplevel_datums = 5; 102 | c_max_toplevel_datums = 10; 103 | c_min_toplevel_funcdefs = 4; 104 | c_max_toplevel_funcdefs = 8; 105 | c_stdout = false; 106 | c_seed = None; 107 | c_gen_oop_methods = true; 108 | c_use_hex_floats = true; 109 | c_use_length = true; 110 | c_use_pairs = true; 111 | c_use_ipairs = true; 112 | c_use_tostring = true; 113 | c_use_tonumber = true; 114 | c_use_string_upper = true; 115 | c_use_string_lower = true; 116 | c_use_string_sub = true; 117 | c_use_string_reverse = true; 118 | c_use_string_gsub = true; 119 | c_use_string_len = true; 120 | c_use_math_type = true; 121 | c_use_math_ult = true; 122 | c_use_math_sin = true; 123 | c_use_math_cos = true; 124 | c_use_math_tan = true; 125 | c_use_math_abs = true; 126 | c_use_math_pi = true; 127 | c_use_math_log = true; 128 | c_use_math_floor = true; } 129 | 130 | let get_yaml_value_exn (v : Yaml.yaml) = 131 | match v with 132 | | `Scalar scalar -> scalar.value 133 | | _ -> failwith "Configuration file is broken." 134 | 135 | let set_config_value acc fn = 136 | Caml.Option.fold 137 | ~none:None 138 | ~some:fn 139 | acc 140 | 141 | (** [set_general_settings] parses the [general] option from the configuration 142 | file. *) 143 | let set_general_settings acc (mapping : Yaml.mapping) = 144 | let f = (fun acc ((k : Yaml.yaml),v) -> begin 145 | match k with 146 | | `Scalar scalar -> begin 147 | match scalar.value with 148 | | "indent" -> 149 | set_config_value acc 150 | (fun c -> Some({ c with c_indent = get_yaml_value_exn v })) 151 | | "lib_path" -> 152 | set_config_value acc 153 | (fun c -> Some({ c with c_lib_path = Some(get_yaml_value_exn v) })) 154 | | "min_toplevel_datums" -> 155 | set_config_value acc 156 | (fun c -> Some({ c with c_min_toplevel_datums = 157 | int_of_string @@ get_yaml_value_exn v })) 158 | | "max_toplevel_datums" -> 159 | set_config_value acc 160 | (fun c -> Some({ c with c_max_toplevel_datums = 161 | int_of_string @@ get_yaml_value_exn v })) 162 | | "min_toplevel_funcdefs" -> 163 | set_config_value acc 164 | (fun c -> Some({ c with c_min_toplevel_funcdefs = 165 | int_of_string @@ get_yaml_value_exn v })) 166 | | "max_toplevel_funcdefs" -> 167 | set_config_value acc 168 | (fun c -> Some({ c with c_max_toplevel_funcdefs = 169 | int_of_string @@ get_yaml_value_exn v })) 170 | | "stdout" -> 171 | set_config_value acc 172 | (fun c -> Some({ c with c_stdout = 173 | bool_of_string @@ get_yaml_value_exn v })) 174 | | "seed" -> 175 | set_config_value acc 176 | (fun c -> Some({ c with c_seed = 177 | match get_yaml_value_exn v with 178 | | "null" -> None 179 | | num -> Some(int_of_string num) })) 180 | | _ -> begin 181 | Printf.printf "Unexpected configuration option: %s\n" scalar.value; 182 | acc 183 | end 184 | end 185 | | _ -> begin 186 | Printf.printf "Configuration file is broken\n"; 187 | acc 188 | end 189 | end) 190 | in 191 | List.fold_left 192 | mapping.m_members 193 | ~init:acc 194 | ~f:f 195 | 196 | (** [set_language_settings] parses [language] option from the configuration 197 | file. *) 198 | let set_language_settings acc (mapping : Yaml.mapping) = 199 | let f = (fun acc ((k : Yaml.yaml),v) -> begin 200 | match k with 201 | | `Scalar scalar -> begin 202 | match scalar.value with 203 | | "gen_oop_methods" -> 204 | set_config_value acc 205 | (fun c -> Some({ c with c_gen_oop_methods = 206 | bool_of_string @@ get_yaml_value_exn v })) 207 | | "use_hex_floats" -> 208 | set_config_value acc 209 | (fun c -> Some({ c with c_use_hex_floats = 210 | bool_of_string @@ get_yaml_value_exn v })) 211 | | "use_length" -> 212 | set_config_value acc 213 | (fun c -> Some({ c with c_use_length = 214 | bool_of_string @@ get_yaml_value_exn v })) 215 | | "use_pairs" -> 216 | set_config_value acc 217 | (fun c -> Some({ c with c_use_pairs = 218 | bool_of_string @@ get_yaml_value_exn v })) 219 | | "use_ipairs" -> 220 | set_config_value acc 221 | (fun c -> Some({ c with c_use_ipairs = 222 | bool_of_string @@ get_yaml_value_exn v })) 223 | | "use_tostring" -> 224 | set_config_value acc 225 | (fun c -> Some({ c with c_use_tostring = 226 | bool_of_string @@ get_yaml_value_exn v })) 227 | | "use_tonumber" -> 228 | set_config_value acc 229 | (fun c -> Some({ c with c_use_tonumber = 230 | bool_of_string @@ get_yaml_value_exn v })) 231 | | "use_string_upper" -> 232 | set_config_value acc 233 | (fun c -> Some({ c with c_use_string_upper = 234 | bool_of_string @@ get_yaml_value_exn v })) 235 | | "use_string_lower" -> 236 | set_config_value acc 237 | (fun c -> Some({ c with c_use_string_lower = 238 | bool_of_string @@ get_yaml_value_exn v })) 239 | | "use_string_sub" -> 240 | set_config_value acc 241 | (fun c -> Some({ c with c_use_string_sub = 242 | bool_of_string @@ get_yaml_value_exn v })) 243 | | "use_string_reverse" -> 244 | set_config_value acc 245 | (fun c -> Some({ c with c_use_string_reverse = 246 | bool_of_string @@ get_yaml_value_exn v })) 247 | | "use_string_gsub" -> 248 | set_config_value acc 249 | (fun c -> Some({ c with c_use_string_gsub = 250 | bool_of_string @@ get_yaml_value_exn v })) 251 | | "use_string_len" -> 252 | set_config_value acc 253 | (fun c -> Some({ c with c_use_string_len = 254 | bool_of_string @@ get_yaml_value_exn v })) 255 | | "use_math_type" -> 256 | set_config_value acc 257 | (fun c -> Some({ c with c_use_math_type = 258 | bool_of_string @@ get_yaml_value_exn v })) 259 | | "use_math_ult" -> 260 | set_config_value acc 261 | (fun c -> Some({ c with c_use_math_ult = 262 | bool_of_string @@ get_yaml_value_exn v })) 263 | | "use_math_sin" -> 264 | set_config_value acc 265 | (fun c -> Some({ c with c_use_math_sin = 266 | bool_of_string @@ get_yaml_value_exn v })) 267 | | "use_math_cos" -> 268 | set_config_value acc 269 | (fun c -> Some({ c with c_use_math_cos = 270 | bool_of_string @@ get_yaml_value_exn v })) 271 | | "use_math_tan" -> 272 | set_config_value acc 273 | (fun c -> Some({ c with c_use_math_tan = 274 | bool_of_string @@ get_yaml_value_exn v })) 275 | | "use_math_abs" -> 276 | set_config_value acc 277 | (fun c -> Some({ c with c_use_math_abs = 278 | bool_of_string @@ get_yaml_value_exn v })) 279 | | "use_math_pi" -> 280 | set_config_value acc 281 | (fun c -> Some({ c with c_use_math_pi = 282 | bool_of_string @@ get_yaml_value_exn v })) 283 | | "use_math_log" -> 284 | set_config_value acc 285 | (fun c -> Some({ c with c_use_math_log = 286 | bool_of_string @@ get_yaml_value_exn v })) 287 | | "use_math_floor" -> 288 | set_config_value acc 289 | (fun c -> Some({ c with c_use_math_floor = 290 | bool_of_string @@ get_yaml_value_exn v })) 291 | | _ -> begin 292 | Printf.printf "Unexpected configuration option: %s\n" scalar.value; 293 | acc 294 | end 295 | end 296 | | _ -> begin 297 | Printf.printf "Configuration file is broken\n"; 298 | acc 299 | end 300 | end) 301 | in 302 | List.fold_left 303 | mapping.m_members 304 | ~init:acc 305 | ~f:f 306 | 307 | let from_yaml filepath = 308 | let yaml = In_channel.read_all filepath 309 | |> Yaml.yaml_of_string 310 | |> Caml.Result.get_ok 311 | in 312 | match yaml with 313 | | `O mapping -> begin 314 | List.fold_left 315 | mapping.m_members 316 | ~init:(Some(mk_default ())) 317 | ~f:(fun acc (k, v) -> begin 318 | match k with 319 | | `Scalar scalar -> begin 320 | match scalar.value with 321 | | "general" -> begin 322 | match v with 323 | | `O mapping -> set_general_settings acc mapping 324 | | _ -> begin 325 | Printf.printf "Configuration file is broken\n"; 326 | acc 327 | end 328 | end 329 | | "language" -> begin 330 | match v with 331 | | `O mapping -> set_language_settings acc mapping 332 | | _ -> begin 333 | Printf.printf "Configuration file is broken\n"; 334 | acc 335 | end 336 | end 337 | | _ -> begin 338 | Printf.printf "Unexpected configuration option: %s\n" scalar.value; 339 | acc 340 | end 341 | end 342 | | _ -> None 343 | end) 344 | end 345 | | _ -> None 346 | -------------------------------------------------------------------------------- /src/genUtil.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | let gen_ty () = 4 | let open Ast in 5 | match Random.int_incl 0 7 with 6 | | 0 -> TyNil 7 | | 1 -> TyBoolean 8 | | 2 -> TyInt 9 | | 3 -> TyFloat 10 | | 4 -> TyString 11 | | 5 -> TyIntString 12 | | 6 -> TyFunction 13 | | _ -> TyTable 14 | 15 | let gen_simple_ty () = 16 | let open Ast in 17 | Util.choose_one_exn [TyBoolean; TyInt; TyFloat; TyString; TyIntString] 18 | 19 | let gen_array_table_init () = 20 | let open Ast in 21 | let gen_array args_num = 22 | let gen acc = 23 | if args_num <= List.length acc then 24 | [IntExpr(Random.int_incl (-20) 20)] 25 | |> List.append acc 26 | else acc 27 | in 28 | let table_elements = gen [] in 29 | TableExpr(TArray{ table_elements }) 30 | in 31 | Random.int_incl 1 5 |> gen_array 32 | 33 | let gen_simple_expr always_positive = 34 | let open Ast in 35 | match Random.int_incl 0 4 with 36 | | 0 -> TrueExpr 37 | | 1 -> FalseExpr 38 | | 2 -> 39 | let (l, r) = 40 | if always_positive then (0, 200) 41 | else (-100, 100) 42 | in 43 | IntExpr(Random.int_incl l r) 44 | | 3 -> 45 | let (l, r) = 46 | if always_positive then (-100.0), 100.0 47 | else 0.0, 200.0 48 | in 49 | FloatExpr(Random.float_range l r) 50 | | _ -> StringExpr(StringGen.gen_string ()) 51 | 52 | let gen_simple_expr ?(ty = Ast.TyAny) ?(always_positive = false) () = 53 | let open Ast in 54 | match ty with 55 | | TyAny -> gen_simple_expr always_positive 56 | | TyNil -> NilExpr 57 | | TyBoolean -> begin 58 | match Random.bool () with 59 | | true -> TrueExpr 60 | | _ -> FalseExpr 61 | end 62 | | TyInt -> 63 | if always_positive then IntExpr(Random.int_incl 0 150) 64 | else IntExpr(Random.int_incl (-100) 100) 65 | | TyFloat -> 66 | if always_positive then FloatExpr(Random.float 150.0) 67 | else FloatExpr(Random.float_range (-100.0) 100.0) 68 | | TyString -> StringExpr(StringGen.gen_string ()) 69 | | TyIntString -> StringExpr(StringGen.gen_int_string ()) 70 | | TyTable -> gen_array_table_init () 71 | | TyFunction -> begin 72 | let lambda_body = 73 | ReturnStmt{ return_exprs = [IntExpr(Random.int_incl (-100) (100))] } 74 | in 75 | LambdaExpr{ lambda_args = []; 76 | lambda_body } 77 | end 78 | | TyThread | TyUserdata -> NilExpr 79 | 80 | let gen_hash_table_init ?(ty = Ast.TyAny) () = 81 | let open Ast in 82 | let gen_hashmap args_num = 83 | let gen acc = 84 | if args_num > List.length acc then 85 | let idx = Context.get_free_idx () in 86 | let tf_key = IdentExpr{ id_id = idx; 87 | id_name = Printf.sprintf "field%d" idx; 88 | id_ty = ty } 89 | and tf_value = gen_simple_expr ~ty:ty () in 90 | acc @ [{ tf_key; tf_value }] 91 | else acc 92 | in 93 | let table_fields = gen [] in 94 | TableExpr(THashMap{ table_fields }) 95 | in 96 | Random.int_incl 1 5 |> gen_hashmap 97 | 98 | let gen_compare_binop ty = 99 | let open Ast in 100 | match ty with 101 | | TyInt | TyFloat -> begin 102 | match Random.int_incl 0 6 with 103 | | 0 -> OpEq 104 | | 2 -> OpNeq 105 | | 3 -> OpLt 106 | | 4 -> OpLte 107 | | 5 -> OpGt 108 | | _ -> OpGte 109 | end 110 | | TyNil | TyBoolean | TyString | TyIntString | TyUserdata 111 | | TyFunction | TyThread | TyTable | TyAny -> if Random.bool () then OpEq else OpNeq 112 | 113 | let gen_ident ?(add_now=false) ?(name=None) env = 114 | let open Ast in 115 | let idx = Context.get_free_idx () in 116 | let name = Option.value name ~default:(Printf.sprintf "v%d" idx) in 117 | let i = IdentExpr{ id_id = idx; 118 | id_name = name; 119 | id_ty = (gen_simple_ty ()) } in 120 | let (_ : unit) = 121 | if add_now then begin env_add_binding env i; end 122 | else begin env_add_pending_binding env i; end 123 | in 124 | i 125 | 126 | let gen_dummy_block () = 127 | let block_env = Ast.env_mk () in 128 | Ast.BlockStmt { block_stmts = []; 129 | block_is_loop = false; 130 | block_env } 131 | 132 | let gen_rhs_to_assign_ident ctx env expr = 133 | let open Ast in 134 | match expr with 135 | | IdentExpr id -> begin 136 | match Random.int_incl 0 2 with 137 | | 0 (* generate a simple expr *) -> begin 138 | gen_simple_expr ~ty:(id.id_ty) () 139 | end 140 | | 1 (* try to peek a local expr *) -> begin 141 | if env_empty env then 142 | gen_simple_expr ~ty:(id.id_ty) () 143 | else begin 144 | let binds_with_same_type = List.filter 145 | env.env_bindings 146 | ~f:(fun expr_ref -> begin 147 | match !expr_ref with 148 | | IdentExpr nid -> begin 149 | if equal_ty nid.id_ty id.id_ty then true 150 | else false 151 | end 152 | | _ -> false (* who knows, let's just skip *) 153 | end) 154 | in 155 | if List.is_empty binds_with_same_type then 156 | gen_simple_expr ~ty:(id.id_ty) () 157 | else begin 158 | ! (Util.choose_one_exn binds_with_same_type) 159 | end 160 | end 161 | end 162 | | _ (* try to peek a global datum *) -> begin 163 | Context.peek_typed_datum ctx id.id_ty 164 | |> Option.value ~default:(gen_simple_expr ~ty:(id.id_ty) ()) 165 | end 166 | end 167 | | _ -> failwith "Impossible: Trying to generate RHS for not IdentExpr" 168 | 169 | let gen_init_stmt_for_ident ?(assign_local = false) expr = 170 | let open Ast in 171 | let gen_init_stmt rhs = 172 | AssignStmt{ assign_local; 173 | assign_lhs = [expr]; 174 | assign_rhs = [rhs] } 175 | in 176 | match expr with 177 | | IdentExpr id -> begin 178 | match id.id_ty with 179 | | TyNil -> gen_init_stmt NilExpr 180 | | TyBoolean -> 181 | let rhs = match Random.bool () with 182 | | true -> TrueExpr 183 | | _ -> FalseExpr 184 | in 185 | gen_init_stmt rhs 186 | | TyInt -> IntExpr(Random.int_incl (-100) 100) |> gen_init_stmt 187 | | TyFloat -> FloatExpr(Random.float 100.0) |> gen_init_stmt 188 | | TyString -> StringExpr(StringGen.gen_string ()) |> gen_init_stmt 189 | | TyIntString -> StringExpr(StringGen.gen_int_string ()) |> gen_init_stmt 190 | | TyFunction -> begin 191 | let lambda_body = 192 | ReturnStmt{ return_exprs = [IntExpr(Random.int_incl (-100) (100))] } 193 | in 194 | LambdaExpr{ lambda_args = []; 195 | lambda_body } 196 | |> gen_init_stmt 197 | end 198 | | TyTable -> gen_array_table_init () |> gen_init_stmt 199 | | TyAny -> gen_init_stmt NilExpr 200 | | TyUserdata | TyThread -> failwith "Userdata and Thread types are unsupported" 201 | 202 | end 203 | | _ -> failwith "Impossible: Got not IdentExpr as LHS of the assign statement" 204 | 205 | let take_random_binding env = 206 | let open Ast in 207 | let rec aux ?(use_this_level = false) env level visited = 208 | (* Use this environment and visited ones. *) 209 | let stop_here () = 210 | if (env_empty env) then 211 | if List.is_empty visited then 212 | None 213 | else 214 | aux ~use_this_level:true !(List.hd_exn visited) 0 [] 215 | else (* This env is not empty. Use it. *) 216 | aux ~use_this_level:true env level visited 217 | (* Walk to the parent environment. We want to reach the depth [level]. *) 218 | and walk_previous () = 219 | visited @ [ref env] 220 | |> aux (env_get_parent_exn env) (level - 1) 221 | in 222 | if use_this_level then 223 | if env_empty env then None 224 | else Some(env_peek_random_exn env) 225 | else if not (env_empty env) then 226 | if (env_has_parent env) && not (phys_equal level 0) then 227 | walk_previous () 228 | else 229 | stop_here () 230 | else (* env is empty *) 231 | if (env_has_parent env) && not (phys_equal level 0) then 232 | walk_previous () (* May be there are non-empty parents. *) 233 | else 234 | stop_here () 235 | in 236 | let select_level () = 237 | (* We prefer the most near scopes: 238 | 30% - 2 levels 239 | 30% - 3 levels 240 | 25% - 4 levels 241 | 15% - 5 levels *) 242 | let r = Random.int_incl 0 100 in 243 | if r < 30 then 2 244 | else if r < 60 then 3 245 | else if r < 85 then 4 246 | else 5 247 | in 248 | aux env (select_level ()) [] 249 | 250 | (** Generates unary operator which can take expression of type [ty] and returns 251 | an expression of the same [ty]. *) 252 | let gen_combine_unop ty = 253 | let open Ast in 254 | match ty with 255 | | TyNil -> None 256 | | TyBoolean -> Some(OpNot) 257 | | TyInt -> begin 258 | match Random.bool () with 259 | | true -> Some(OpSub) (* - *) 260 | | _ -> Some(OpBNot) (* ~ *) 261 | end 262 | | TyFloat -> Some(OpSub) 263 | | TyString -> None 264 | | TyIntString -> None 265 | | TyFunction -> None 266 | | TyTable -> None 267 | | TyAny -> None 268 | | TyThread | TyUserdata -> None 269 | 270 | (** Generates name of the function which takes a single argument of type [ty] 271 | and returns an expression of the same [ty]. *) 272 | let gen_combine_un_funcall ctx ty = 273 | let open Ast in 274 | let open Context in 275 | let open Config in 276 | match ty with 277 | | TyNil -> None 278 | | TyBoolean -> None 279 | | TyInt -> 280 | Util.choose [(ctx.ctx_config.c_use_math_log, 281 | lazy (Some("math.log"))); 282 | (true), 283 | lazy (None)] 284 | @@ lazy (None) 285 | | TyFloat -> 286 | Util.choose [(ctx.ctx_config.c_use_math_log, 287 | lazy (Some("math.log"))); 288 | (true), 289 | lazy (None)] 290 | @@ lazy (None) 291 | | TyString -> 292 | Util.choose [(ctx.ctx_config.c_use_string_lower, 293 | lazy (Some("string.upper"))); 294 | (ctx.ctx_config.c_use_string_upper, 295 | lazy (Some("string.lower"))); 296 | (true), 297 | lazy (None)] 298 | @@ lazy (None) 299 | | TyIntString -> None 300 | | TyFunction -> None 301 | | TyTable -> None 302 | | TyAny -> None 303 | | TyThread | TyUserdata -> None 304 | 305 | (** Generates binary operator which can take two expressions of type [ty] and 306 | returns an expression of the same [ty]. The type of the generated operator 307 | depends of values of [lhs] and [rhs], because we must avoid result 308 | expresssions which exceeds Lua's number limits. *) 309 | let gen_combine_binop ty lhs rhs = 310 | let open Ast in 311 | match ty with 312 | | TyNil -> None 313 | | TyBoolean -> begin 314 | match Random.bool () with 315 | | true -> Some(OpEq) (* == *) 316 | | _ -> Some(OpNeq) (* ~= *) 317 | end 318 | | TyInt -> begin 319 | (* Chooses "safe" operand that won't cause number overflow. *) 320 | let select_safe () = 321 | match Random.int_incl 0 4 with 322 | | 0 -> Some(OpAdd) (* + *) 323 | | 1 -> Some(OpSub) (* - *) 324 | | 3 -> Some(OpBAnd) (* & *) 325 | | _ -> Some(OpBOr) (* ~ *) 326 | in 327 | match lhs, rhs with 328 | | IntExpr(l), IntExpr(r) -> begin 329 | (* If we have both even integers, we could safely divide them. *) 330 | if (phys_equal (l % 2) 0) && (phys_equal (r % 2) 0) && 331 | (not @@ phys_equal r 0) then 332 | if Random.bool() then 333 | Some(OpDiv) (* / *) 334 | else 335 | Some(OpMod) (* % *) 336 | else if (l < 10) && (r < 3) then 337 | (* We could safely perform some operations on small integers. *) 338 | match Random.int_incl 0 4 with 339 | | 0 -> Some(OpPow) (* ^ *) 340 | | 1 -> Some(OpBRs) (* >> *) 341 | | 2 -> Some(OpMul) (* * *) 342 | | _ -> Some(OpBLs) (* << *) 343 | else 344 | select_safe () 345 | end 346 | | _ -> select_safe () 347 | end 348 | | TyFloat -> begin 349 | (* Select operator that can't cause number overflow or zero division. *) 350 | let select_safe () = 351 | if Random.bool () then 352 | Some(OpAdd) (* + *) 353 | else 354 | Some(OpSub) (* - *) 355 | in 356 | match lhs, rhs with 357 | | FloatExpr(l), FloatExpr(r) -> begin 358 | if ((int_of_float l) < 10) && 359 | ((int_of_float r) < 3) && 360 | (not @@ phys_equal r 0.0) then 361 | match Random.int_incl 0 3 with 362 | | 0 -> Some(OpMul) (* * *) 363 | | 1 -> Some(OpDiv) (* / *) 364 | | 2 -> Some(OpPow) (* ^ *) 365 | | _ -> Some(OpMod) (* % *) 366 | else select_safe () 367 | end 368 | | _ -> select_safe () 369 | end 370 | | TyString -> None (* skip, because concat ('..') operators are horrible slow *) 371 | | TyIntString -> None (* skip, because concat ('..') operators are horrible slow *) 372 | | TyFunction -> None 373 | | TyTable -> None 374 | | TyAny -> None 375 | | TyThread | TyUserdata -> None 376 | 377 | (** Combines [exprs] of the same type [ty] to a single expression. *) 378 | let rec combine ctx ty exprs = 379 | let open Ast in 380 | match exprs with 381 | | [] -> begin 382 | None 383 | end 384 | | [expr] -> begin 385 | match gen_combine_unop ty with 386 | | Some un_op -> begin 387 | (* Create an unary expression. *) 388 | Some(UnExpr{ un_op; 389 | un_expr = expr }) 390 | end 391 | | None -> begin 392 | (* Create a function call with a single argument. *) 393 | match gen_combine_un_funcall ctx ty with 394 | | Some func_name -> begin 395 | let fcf_func = IdentExpr{ id_id = Context.get_free_idx (); 396 | id_name = func_name; 397 | id_ty = TyFunction } in 398 | let fc_ty = FCFunc{ fcf_func } in 399 | Some(FuncCallExpr{ fc_id = -1; 400 | fc_ty; 401 | fc_args = [expr] }) 402 | end 403 | | None -> begin 404 | (* Just use this value "as is". *) 405 | Some(expr) 406 | end 407 | end 408 | end 409 | | lhs::head -> begin 410 | let rhs_opt = combine ctx ty head in 411 | match rhs_opt with 412 | | Some rhs -> begin 413 | match gen_combine_binop ty lhs rhs with 414 | | Some bin_op -> begin 415 | (* Create a binary operator. *) 416 | Some(BinExpr{ bin_lhs = lhs; 417 | bin_op; 418 | bin_rhs = rhs; }) 419 | end 420 | (* TODO: Add standard functions with two arguments. *) 421 | | None -> begin 422 | (* Well, we can't create such expressions yet. *) 423 | Some(lhs) 424 | end 425 | end 426 | | None -> None 427 | end 428 | 429 | let combine_to_typed_expr ctx ty exprs = 430 | let open Ast in 431 | let type_conv = match ty with 432 | | TyNil -> Transform.to_nil 433 | | TyBoolean -> Transform.to_boolean 434 | | TyInt -> Transform.to_int 435 | | TyFloat -> Transform.to_float 436 | | TyString -> Transform.to_string 437 | | TyIntString -> Transform.to_int_string 438 | | TyFunction -> Transform.to_function 439 | | TyTable -> Transform.to_table 440 | | TyThread | TyUserdata | TyAny -> Transform.to_nil 441 | in 442 | List.fold_left 443 | exprs 444 | ~init:[] 445 | ~f:(fun acc expr -> begin 446 | let e_ty = Option.value (get_essential_ty expr) ~default:TyNil in 447 | acc @ [type_conv ctx e_ty expr] 448 | end) 449 | |> combine ctx ty 450 | 451 | (** Extends the BlockStmt [block] adding given [stmt] to the end of the block. *) 452 | let extend_block_stmt block stmts = 453 | match block with 454 | | Ast.BlockStmt block -> begin 455 | let block_stmts = block.block_stmts @ stmts in 456 | Ast.BlockStmt { block with block_stmts } 457 | end 458 | | _ -> block 459 | 460 | let gen_empty_block ?(is_loop = false) parent_env = 461 | let block_env = Ast.env_mk () in 462 | let block_env = { block_env with env_parent = Some(ref parent_env) } in 463 | let block_stmts = [] in 464 | Ast.env_add_child parent_env block_env; 465 | Ast.BlockStmt{ block_stmts; 466 | block_is_loop = is_loop; 467 | block_env } 468 | -------------------------------------------------------------------------------- /src/ast.ml: -------------------------------------------------------------------------------- 1 | open Core_kernel 2 | 3 | (** Essential types present in Lua. *) 4 | type ty = 5 | | TyNil 6 | | TyBoolean 7 | | TyInt 8 | | TyFloat 9 | | TyIntString 10 | | TyString 11 | | TyUserdata 12 | | TyFunction 13 | | TyThread 14 | | TyTable 15 | | TyAny 16 | [@@deriving eq, show] 17 | 18 | (** See: https://www.lua.org/manual/5.3/manual.html#3.4.1 *) 19 | type operator = 20 | | OpAdd (* + *) 21 | | OpSub (* - *) 22 | | OpMul (* * *) 23 | | OpDiv (* / *) 24 | | OpFloor (* // *) 25 | | OpPow (* ^ *) 26 | | OpMod (* % *) 27 | | OpConcat (* .. *) 28 | | OpLt (* < *) 29 | | OpLte (* <= *) 30 | | OpGt (* > *) 31 | | OpGte (* >= *) 32 | | OpEq (* == *) 33 | | OpNeq (* ~= *) 34 | | OpBAnd (* & *) 35 | | OpBOr (* ~ *) 36 | | OpBRs (* >> *) 37 | | OpBLs (* << *) 38 | | OpBNot (* ~ *) 39 | | OpAnd (* and *) 40 | | OpOr (* or *) 41 | | OpNot (* not *) 42 | | OpLen (* # *) 43 | 44 | type env = { mutable env_bindings: expr ref list; 45 | mutable env_pending_bindings: expr ref list; 46 | mutable env_parent: env ref option; 47 | mutable env_children: env ref list; } 48 | 49 | and expr = 50 | | TrueExpr 51 | | FalseExpr 52 | | NilExpr 53 | | IntExpr of int 54 | | FloatExpr of float 55 | | StringExpr of string 56 | | IdentExpr of { id_id: int; 57 | id_name: string; 58 | id_ty: ty } 59 | | AttrGetExpr of { ag_obj: expr; 60 | ag_key: expr } 61 | | TableExpr of table_ty 62 | | LambdaExpr of { lambda_args: expr list; 63 | lambda_body: stmt; } 64 | | FuncCallExpr of { fc_id: int; 65 | fc_ty: func_call; 66 | fc_args: expr list } 67 | | UnExpr of { un_op: operator; 68 | un_expr: expr } 69 | | BinExpr of { bin_lhs: expr; 70 | bin_op: operator; 71 | bin_rhs: expr } 72 | and table_ty = 73 | | TArray of { table_elements: expr list } 74 | | THashMap of { table_fields: table_field list } 75 | and table_field = 76 | { tf_key: expr; 77 | tf_value: expr } 78 | and func_call = 79 | | FCMethod of { fcm_receiver: string; 80 | fcm_method: string; } 81 | | FCFunc of { fcf_func: expr } 82 | 83 | and stmt = 84 | | AssignStmt of { assign_local: bool; 85 | assign_lhs: expr list; 86 | assign_rhs: expr list } 87 | | FuncCallStmt of { fc_expr: expr } 88 | | DoBlockStmt of { do_block: stmt } 89 | | LoopStmt of { loop_cond: expr; 90 | loop_block: stmt; 91 | loop_ty: loop_type } 92 | | IfStmt of { if_cond: expr; 93 | if_body: stmt; 94 | if_else: stmt option } 95 | | NumForStmt of { nfor_name: string; 96 | nfor_init: expr; 97 | nfor_limit: expr; 98 | nfor_step: expr; 99 | nfor_body: stmt } 100 | | ForStmt of { for_names: expr list; 101 | for_exprs: expr list; 102 | for_body: stmt } 103 | | FuncDefStmt of { fd_id: int; 104 | (** Receiver (object) which this function belongs to. 105 | If not None, the function is a method. *) 106 | fd_local: bool; 107 | fd_receiver: string option; 108 | fd_name: string; 109 | fd_args: expr list; 110 | fd_has_varags: bool; 111 | fd_body: stmt; 112 | fd_ty: ty list } 113 | | ReturnStmt of { return_exprs: expr list } 114 | | BreakStmt 115 | | BlockStmt of { block_stmts: stmt list; 116 | block_is_loop: bool; 117 | block_env: env } 118 | and loop_type = 119 | | While 120 | | Repeat 121 | 122 | let mki = 123 | let r = ref 0 in 124 | fun () -> incr r; 125 | !r 126 | 127 | let ty_to_s = function 128 | | TyNil -> "nil" 129 | | TyBoolean -> "boolean" 130 | | TyInt -> "int" 131 | | TyFloat -> "float" 132 | | TyString -> "string" 133 | | TyIntString -> "string" 134 | | TyUserdata -> "userdata" 135 | | TyFunction -> "function" 136 | | TyThread -> "thread" 137 | | TyTable -> "table" 138 | | TyAny -> "any" 139 | 140 | let get_essential_ty expr = 141 | match expr with 142 | | TrueExpr | FalseExpr -> Some(TyBoolean) 143 | | NilExpr -> Some(TyNil) 144 | | IntExpr _ -> Some(TyInt) 145 | | FloatExpr _ -> Some(TyFloat) 146 | | StringExpr _ -> Some(TyString) 147 | | IdentExpr id -> Some(id.id_ty) 148 | | TableExpr _ -> Some(TyTable) 149 | | LambdaExpr _ -> Some(TyFunction) 150 | | _ -> None 151 | 152 | let get_table_key_ids table_expr = 153 | let get_id = function 154 | | IdentExpr id -> id.id_id 155 | | _ -> failwith "Impossible: Table key is not IdentExpr" 156 | in 157 | match table_expr with 158 | | TableExpr table -> begin 159 | match table with 160 | | THashMap hm -> begin 161 | List.fold_left 162 | hm.table_fields 163 | ~init:[] 164 | ~f:(fun acc f -> acc @ [get_id f.tf_key]) 165 | end 166 | | TArray _ -> [] 167 | end 168 | | _ -> [] 169 | 170 | let types_are_comparable ty_lhs ty_rhs = 171 | match (ty_lhs, ty_rhs) with 172 | | (TyNil, TyNil) -> false 173 | | (TyBoolean, TyBoolean) -> false 174 | | (TyInt, TyInt) -> true 175 | | (TyInt, TyFloat) -> true 176 | | (TyFloat, TyInt) -> true 177 | | (TyFloat, TyFloat) -> true 178 | | (TyString, TyString) -> true 179 | | (TyUserdata, TyUserdata) -> false 180 | | (TyFunction, TyFunction) -> false 181 | | (TyThread, TyThread) -> false 182 | | (TyTable, TyTable) -> false 183 | | _ -> false 184 | 185 | let get_block_env_exn = function 186 | | BlockStmt s -> s.block_env 187 | | _ -> Errors.InternalError "Expected BlockStmt" |> raise 188 | 189 | let op_to_s = function 190 | | OpAdd -> "+" 191 | | OpSub -> "-" 192 | | OpMul -> "*" 193 | | OpDiv -> "/" 194 | | OpFloor -> "//" 195 | | OpPow -> "^" 196 | | OpMod -> "%" 197 | | OpConcat -> ".." 198 | | OpLt -> "<" 199 | | OpLte -> "<=" 200 | | OpGt -> ">" 201 | | OpGte -> ">=" 202 | | OpEq -> "==" 203 | | OpNeq -> "~=" 204 | | OpNot -> "not" 205 | | OpBAnd -> "&" 206 | | OpBOr -> "~" 207 | | OpBRs -> ">>" 208 | | OpBLs -> "<<" 209 | | OpBNot -> "~" 210 | | OpAnd -> "and" 211 | | OpOr -> "or" 212 | | OpLen -> "#" 213 | 214 | let env_mk () = 215 | { env_bindings = []; 216 | env_pending_bindings = []; 217 | env_parent = None; 218 | env_children = [] } 219 | 220 | let env_empty env = 221 | List.is_empty env.env_bindings 222 | 223 | let env_has_parent env = 224 | Option.is_some env.env_parent 225 | 226 | let env_get_parent_exn env = 227 | match env.env_parent with 228 | | Some parent -> !parent 229 | | None -> Errors.InternalError "Env has no parent!" |> raise 230 | 231 | (* TODO: Add nested level *) 232 | let env_peek_random_exn env = 233 | Random.int_incl 0 @@ (List.length env.env_bindings) - 1 234 | |> List.nth_exn env.env_bindings 235 | 236 | let env_shuffle_local_bindings env = 237 | if env_empty env then [] 238 | else begin 239 | let bindings_len = List.length env.env_bindings in 240 | let get_shuffled_idxes () = 241 | let nums = List.range 0 bindings_len in 242 | let ns = List.map nums ~f:(fun num -> (Random.bits (), num)) in 243 | let sorted = Caml.List.sort Caml.compare ns in 244 | List.map sorted ~f:snd 245 | in 246 | let rec aux acc n idxes = 247 | if bindings_len <= List.length acc then 248 | acc 249 | else 250 | aux (acc @ [! (List.nth_exn env.env_bindings n)]) (n + 1) idxes 251 | in 252 | get_shuffled_idxes () |> aux [] 0 253 | end 254 | 255 | let env_find_binding_with_ty ?(depth = 1000) env ty = 256 | let find env = 257 | let filter e = 258 | match !e with 259 | | IdentExpr id -> if equal_ty id.id_ty ty then true else false 260 | | _ -> false 261 | in 262 | match List.filter env.env_bindings ~f:filter with 263 | | [x] -> Some(!x) 264 | | [] -> None 265 | | l -> Some(!(Util.choose_one_exn l)) 266 | in 267 | let rec aux env current_depth = 268 | if current_depth < depth then 269 | match find env with 270 | | Some v -> Some v 271 | | None -> 272 | if env_has_parent env then begin 273 | aux (env_get_parent_exn env) (current_depth + 1) 274 | end 275 | else 276 | None 277 | else 278 | None 279 | in 280 | aux env 0 281 | 282 | let env_add_binding env ident = 283 | env.env_bindings <- env.env_bindings @ [ref ident]; 284 | () 285 | 286 | let env_add_pending_binding env ident = 287 | env.env_pending_bindings <- env.env_pending_bindings @ [ref ident]; 288 | () 289 | 290 | let env_flush_pending_bindings env = 291 | let aux () = 292 | match env.env_pending_bindings with 293 | | [x] -> begin 294 | env.env_pending_bindings <- []; 295 | env.env_bindings <- env.env_bindings @ [x]; 296 | () 297 | end 298 | | x :: xs -> begin 299 | env.env_pending_bindings <- xs; 300 | env.env_bindings <- env.env_bindings @ [x]; 301 | () 302 | end 303 | | _ -> () 304 | in 305 | aux () 306 | 307 | let env_add_child env_parent block_env = 308 | env_parent.env_children <- env_parent.env_children @ [ref block_env] 309 | 310 | let get_ident_name = function 311 | | IdentExpr id -> id.id_name 312 | | _ -> failwith "Expected IdentExpr" 313 | 314 | (** Generates indentation string of depth [n]. *) 315 | let rec mk_indent is n = 316 | if phys_equal n 0 then "" 317 | else is ^ mk_indent is (n - 1) 318 | 319 | let rec expr_to_s stmt_to_s c expr = 320 | let expr_to_s' = expr_to_s stmt_to_s c in 321 | match expr with 322 | | TrueExpr -> "true" 323 | | FalseExpr -> "false" 324 | | NilExpr -> "nil" 325 | | StringExpr s -> 326 | if phys_equal 0 @@ Random.int_incl 0 10 then 327 | (* Generate multiline string literal. *) 328 | String.split ~on:' ' s 329 | |> String.concat ~sep:"\n" 330 | |> Printf.sprintf "[[%s]]" 331 | else 332 | Printf.sprintf "\"%s\"" s 333 | | IdentExpr id -> id.id_name 334 | | TableExpr t -> begin 335 | match t with 336 | | TArray t -> begin 337 | List.fold_left 338 | t.table_elements 339 | ~init:[] 340 | ~f:(fun acc el -> begin 341 | acc @ [Printf.sprintf "%s" 342 | (expr_to_s' el)] 343 | end) 344 | |> String.concat ~sep:", " 345 | |> Printf.sprintf "{%s}" 346 | end 347 | | THashMap t -> begin 348 | List.fold_left 349 | t.table_fields 350 | ~init:[] 351 | ~f:(fun acc kv -> begin 352 | acc @ [Printf.sprintf "%s = %s" 353 | (expr_to_s' kv.tf_key) 354 | (expr_to_s' kv.tf_value)] 355 | end) 356 | |> String.concat ~sep:", " 357 | |> Printf.sprintf "{%s}" 358 | end 359 | end 360 | | LambdaExpr e -> begin 361 | let args_s = exprs_to_cs stmt_to_s c e.lambda_args 362 | and body_s = stmt_to_s c e.lambda_body 363 | in 364 | Printf.sprintf "function (%s) %s end" args_s body_s 365 | end 366 | | IntExpr n -> 367 | Util.choose [ (n > 0 && phys_equal 0 @@ Random.int_incl 0 10), 368 | lazy (Printf.sprintf "0x%x" n); 369 | (n > 0 && phys_equal 0 @@ Random.int_incl 0 10), 370 | lazy (Printf.sprintf "0x%X" n) ] 371 | @@ lazy (Printf.sprintf "%d" n) 372 | | FloatExpr n -> 373 | Util.choose [ (phys_equal 0 @@ Random.int_incl 0 10), 374 | lazy (Printf.sprintf "%e" n); 375 | (c.Config.c_use_hex_floats && 376 | phys_equal 0 @@ Random.int_incl 0 10), 377 | lazy (Printf.sprintf "%h" n) ] 378 | @@ lazy (Printf.sprintf "%f" n) 379 | | UnExpr e -> begin 380 | let (sl, sr) = match e.un_op with 381 | | OpSub | OpLen -> "(", ")" 382 | | _ -> " ", "" 383 | in 384 | Printf.sprintf "%s%s%s%s" 385 | (op_to_s e.un_op) 386 | sl 387 | (expr_to_s' e.un_expr) 388 | sr 389 | end 390 | | BinExpr e -> begin 391 | Printf.sprintf "%s %s %s" 392 | (expr_to_s' e.bin_lhs) 393 | (op_to_s e.bin_op) 394 | (expr_to_s' e.bin_rhs) 395 | end 396 | | AttrGetExpr e -> begin 397 | Printf.sprintf "%s[\"%s\"]" 398 | (expr_to_s' e.ag_obj) 399 | (expr_to_s' e.ag_key) 400 | end 401 | | FuncCallExpr e -> begin 402 | match e.fc_ty with 403 | | FCFunc f -> begin 404 | Printf.sprintf "%s(%s)" 405 | (expr_to_s' f.fcf_func) 406 | (exprs_to_cs stmt_to_s c e.fc_args) 407 | end 408 | | FCMethod m -> begin 409 | Printf.sprintf "%s:%s(%s)" 410 | (m.fcm_receiver) 411 | m.fcm_method 412 | (exprs_to_cs stmt_to_s c e.fc_args) 413 | end 414 | end 415 | 416 | (** Helper function that converts list of arguments to a string. *) 417 | and exprs_to_s ?(sep = " ") stmt_to_s c exprs = 418 | List.fold_left 419 | exprs 420 | ~init:[] 421 | ~f:(fun acc expr -> acc @ [expr_to_s stmt_to_s c expr]) 422 | |> String.concat ~sep:sep 423 | 424 | (** Converts list of arguments to comma-separated string. *) 425 | and exprs_to_cs stmt_to_s c exprs = 426 | exprs_to_s stmt_to_s c exprs ~sep:", " 427 | 428 | let to_block_stmts_exn = function 429 | | BlockStmt bs -> bs.block_stmts 430 | | _ -> Errors.InternalError "Expected BlockStmt" |> raise 431 | 432 | let rec stmt_to_s ?(cr = false) ?(depth = 0) c stmt = 433 | let mk_i () = mk_indent c.Config.c_indent depth in 434 | let cr_s = if cr then "\n" else "" in 435 | match stmt with 436 | | FuncDefStmt fd -> begin 437 | let docstring = 438 | [ let s = List.fold_left 439 | fd.fd_ty 440 | ~init:[] 441 | ~f:(fun acc ty -> acc @ [ty_to_s ty]) 442 | |> String.concat ~sep:", " 443 | in let s = if String.is_empty s then "nil" else s in 444 | Printf.sprintf "%s-- @return %s" (mk_i ()) s] 445 | |> List.append @@ 446 | List.fold_left 447 | fd.fd_args 448 | ~init:[] 449 | ~f:(fun acc expr -> begin 450 | match expr with 451 | | IdentExpr id -> 452 | acc @ [Printf.sprintf "%s-- @param %s %s" 453 | (mk_i ()) id.id_name (ty_to_s id.id_ty)] 454 | | _ -> failwith "Expected IdentExpr" 455 | end) 456 | |> String.concat ~sep:"\n" 457 | and args_code = exprs_to_cs stmt_to_s c fd.fd_args 458 | and varargs_s = if fd.fd_has_varags then 459 | Printf.sprintf "%s..." (if List.is_empty fd.fd_args then "" else ", ") 460 | else "" 461 | and body_code = List.fold_left 462 | (to_block_stmts_exn fd.fd_body) 463 | ~init:[] 464 | ~f:(fun acc stmt -> 465 | acc @ [stmt_to_s c stmt ~depth:(depth + 1)]) 466 | |> String.concat ~sep:"\n" 467 | and name = match fd.fd_receiver with 468 | | Some r -> Printf.sprintf "%s:%s" r fd.fd_name 469 | | None -> fd.fd_name 470 | in if fd.fd_local then 471 | Printf.sprintf "%s\n%slocal %s = function (%s%s)\n%s\n%send%s" 472 | docstring 473 | (mk_i ()) 474 | name 475 | args_code 476 | varargs_s 477 | body_code 478 | (mk_i ()) 479 | cr_s 480 | else 481 | Printf.sprintf "%s\n%sfunction %s(%s%s)\n%s\n%send%s" 482 | docstring 483 | (mk_i ()) 484 | name 485 | args_code 486 | varargs_s 487 | body_code 488 | (mk_i ()) 489 | cr_s 490 | end 491 | | FuncCallStmt s -> begin 492 | let (name, args_s) = match s.fc_expr with 493 | | FuncCallExpr fce -> begin 494 | match fce.fc_ty with 495 | | FCFunc f -> (get_ident_name f.fcf_func, 496 | exprs_to_cs stmt_to_s c fce.fc_args) 497 | | FCMethod m -> begin 498 | let name = Printf.sprintf "%s:%s" m.fcm_receiver m.fcm_method 499 | in 500 | (name, exprs_to_cs stmt_to_s c fce.fc_args) 501 | end 502 | end 503 | | _ -> failwith "Expected FuncCallExpr" 504 | in 505 | Printf.sprintf "%s%s(%s)" (mk_i ()) name args_s 506 | end 507 | | AssignStmt s -> begin 508 | let l_s = if s.assign_local then "local " else "" 509 | and lhs_s = exprs_to_cs stmt_to_s c s.assign_lhs 510 | and rhs_s = exprs_to_cs stmt_to_s c s.assign_rhs in 511 | Printf.sprintf "%s%s%s = %s%s" (mk_i ()) l_s lhs_s rhs_s cr_s 512 | end 513 | | IfStmt s -> begin 514 | let cond_s = expr_to_s stmt_to_s c s.if_cond 515 | and body_s = stmt_to_s c s.if_body ~depth:(depth + 1) 516 | and else_s = match s.if_else with 517 | | Some es -> begin 518 | let es_s = stmt_to_s c es ~depth:(depth + 1) in 519 | Printf.sprintf "\n%selse\n%s" (mk_i ()) es_s 520 | end 521 | | None -> "" 522 | in 523 | Printf.sprintf "%sif %s then\n%s%s\n%send" 524 | (mk_i ()) cond_s body_s else_s (mk_i ()) 525 | end 526 | | BlockStmt s -> begin 527 | List.fold_left 528 | s.block_stmts 529 | ~init:[] 530 | ~f:(fun acc s -> acc @ [stmt_to_s c s ~depth:(depth + 1)]) 531 | |> String.concat ~sep:"\n" 532 | end 533 | | LoopStmt s -> begin 534 | let cond_s = expr_to_s stmt_to_s c s.loop_cond 535 | and body_s = stmt_to_s c s.loop_block ~depth:(depth + 1) 536 | in 537 | match s.loop_ty with 538 | | While -> 539 | Printf.sprintf "%swhile %s do\n%s\n%send" 540 | (mk_i ()) cond_s body_s (mk_i ()) 541 | | Repeat -> 542 | Printf.sprintf "%srepeat\n%s\n%suntil %s" 543 | (mk_i ()) body_s (mk_i ()) cond_s 544 | end 545 | | DoBlockStmt s -> begin 546 | let body_s = stmt_to_s c s.do_block ~depth:(depth + 1) in 547 | Printf.sprintf "%sdo\n%s\n%send" (mk_i ()) body_s (mk_i ()) 548 | end 549 | | BreakStmt -> Printf.sprintf "%sbreak" (mk_i ()) 550 | | ReturnStmt s -> begin 551 | let exprs_s = exprs_to_cs stmt_to_s c s.return_exprs in 552 | Printf.sprintf "%sreturn %s" (mk_i ()) exprs_s 553 | end 554 | | NumForStmt s -> begin 555 | let for_init = match s.nfor_step with 556 | | IntExpr 1 -> 557 | Printf.sprintf "%s=%s,%s" 558 | (s.nfor_name) 559 | (expr_to_s stmt_to_s c s.nfor_init) 560 | (expr_to_s stmt_to_s c s.nfor_limit) 561 | | _ -> 562 | Printf.sprintf "%s=%s,%s,%s" 563 | (s.nfor_name) 564 | (expr_to_s stmt_to_s c s.nfor_init) 565 | (expr_to_s stmt_to_s c s.nfor_limit) 566 | (expr_to_s stmt_to_s c s.nfor_step) 567 | in 568 | let for_body = stmt_to_s c s.nfor_body ~depth:(depth + 1) in 569 | Printf.sprintf "%sfor %s do\n%s\n%send" (mk_i ()) for_init for_body (mk_i ()) 570 | end 571 | | ForStmt s -> begin 572 | let for_names = exprs_to_cs stmt_to_s c s.for_names 573 | and for_exprs = exprs_to_cs stmt_to_s c s.for_exprs 574 | and for_body = stmt_to_s c s.for_body ~depth:(depth + 1) in 575 | Printf.sprintf "%sfor %s in %s do\n%s\n%send" 576 | (mk_i ()) for_names for_exprs for_body (mk_i ()) 577 | end 578 | --------------------------------------------------------------------------------