diff --git a/config/stub_templates/c/loopline.c.jinja b/config/stub_templates/c/loopline.c.jinja index ce5e9caf..d466218a 100644 --- a/config/stub_templates/c/loopline.c.jinja +++ b/config/stub_templates/c/loopline.c.jinja @@ -11,7 +11,7 @@ for (int i = 0; i < {{ count_var }}; i++) { {%- else -%} {% set len = "" %} {%- endif %} - {{ type_kw }} {{ var.name }}{{ len }}; - scanf("{{ sym }}", {{ var.name }}); + {{ type_kw }} {{ var.ident }}{{ len }}; + scanf("{{ sym }}", {{ var.ident }}); {%- endfor %} } diff --git a/config/stub_templates/c/read_many.c.jinja b/config/stub_templates/c/read_many.c.jinja index 9d83a89c..768b77ad 100644 --- a/config/stub_templates/c/read_many.c.jinja +++ b/config/stub_templates/c/read_many.c.jinja @@ -7,8 +7,9 @@ {% endif -%} {% endif -%} -{% for cmt in comments -%} -// {{ cmt.variable }}: {{cmt.description}} +{%- for var in vars %} +{%- if var.input_comment -%}# {{ var.ident }}: {{ var.input_comment }} +{% endif -%} {% endfor -%} {# Setup of types (%d, %s etc.) and variable references (& or "") inside the scanf function #} @@ -20,7 +21,7 @@ {%- else -%} {%- set ref = "&" -%} {%- endif -%} - {%- set var_ref = ref ~ var.name -%} + {%- set var_ref = ref ~ var.ident -%} {%- set_global var_refs = var_refs | concat(with=var_ref) -%} {%- set_global types = types ~ format_symbols[var.var_type] -%} {%- endfor -%} @@ -28,7 +29,7 @@ {%- if single_type -%} {%- set_global type = vars[0].var_type -%} -{{ type_tokens[type] }} {{ vars | map(attribute="name") | join(sep=", ") }}; +{{ type_tokens[type] }} {{ vars | map(attribute="ident") | join(sep=", ") }}; scanf("{{ types }}", {{ var_refs | join(sep=", ") }}); {%- else %} @@ -39,7 +40,7 @@ scanf("{{ types }}", {{ var_refs | join(sep=", ") }}); {%- else -%} {%- set len = "" -%} {%- endif -%} -{{ type_tokens[var.var_type] }} {{ var.name }}{{ len }}; +{{ type_tokens[var.var_type] }} {{ var.ident }}{{ len }}; {% endfor -%} scanf("{{ types }}", {{ var_refs | join(sep=", ") }}); diff --git a/config/stub_templates/c/read_one.c.jinja b/config/stub_templates/c/read_one.c.jinja index b30c4b4d..c7784e3f 100644 --- a/config/stub_templates/c/read_one.c.jinja +++ b/config/stub_templates/c/read_one.c.jinja @@ -16,5 +16,5 @@ {%- set ref = "&" -%} {%- endif -%} -{{ type_kw }} {{ var.name }}{{ len }}; -scanf("{{ sym }}", {{ ref }}{{ var.name }}); +{{ type_kw }} {{ var.ident }}{{ len }}; +scanf("{{ sym }}", {{ ref }}{{ var.ident }}); diff --git a/config/stub_templates/python/loop.py.jinja b/config/stub_templates/python/loop.py.jinja new file mode 100644 index 00000000..c22706d7 --- /dev/null +++ b/config/stub_templates/python/loop.py.jinja @@ -0,0 +1,8 @@ +{%- if debug_mode -%} +---- 🇱 OOP +{% endif -%} + +for {{ index_ident }} in range({{ count_var }}): +{%- for line in inner %} + {{line}} +{%- endfor %} diff --git a/config/stub_templates/python/loopline.py.jinja b/config/stub_templates/python/loopline.py.jinja new file mode 100644 index 00000000..e2bb1ceb --- /dev/null +++ b/config/stub_templates/python/loopline.py.jinja @@ -0,0 +1,48 @@ +{%- if debug_mode -%} +---- 🇱 OOPLINE +{%- if vars | length == 1 -%} + (SINGLE_TYPE) +{% else -%} + (MULTIPLE_TYPE) +{% endif -%} +{% endif -%} + +{%- if vars | length == 1 -%} +{# SINGLE_TYPE #} +{%- set var = vars[0] -%} +{# Special CG case, doesn't make much sense otherwise #} +{%- if var.var_type == "Word" -%} +for {{ var.ident }} in input().split(): + pass + +{%- else -%} + +for {{ index_ident }} in input().split(): + {%- if var.var_type == "String" -%} + {%- set fn = index_ident -%} + {%- else -%} + {%- set fn = type_tokens[var.var_type] ~ "(" ~ index_ident ~ ")" -%} + {%- endif %} + {{ var.ident }} = {{ fn }} + +{%- endif -%} +{# MULTIPLE_TYPE #} +{%- else -%} + +inputs = input().split() +for {{ index_ident }} in range({{ count_var }}): +{%- for var in vars %} + {%- if loop.index0 == 0 -%} + {%- set idx = "" -%} + {%- else -%} + {%- set idx = "+" ~ loop.index0 -%} + {%- endif -%} + {%- if var.var_type == "String" or var.var_type == "Word" -%} + {%- set fn = "inputs[2*" ~ index_ident ~ idx ~ "]" -%} + {%- else -%} + {%- set fn = type_tokens[var.var_type] ~ "(inputs[2*" ~ index_ident ~ idx ~ "])" -%} + {%- endif %} + {{ var.ident }} = {{ fn }} +{%- endfor %} + +{%- endif %} diff --git a/config/stub_templates/python/main.py.jinja b/config/stub_templates/python/main.py.jinja new file mode 100644 index 00000000..49f52f2d --- /dev/null +++ b/config/stub_templates/python/main.py.jinja @@ -0,0 +1,13 @@ +{% if debug_mode %} +---- 🇲 AIN +{% endif -%} +{% if debug_mode -%} +---- 🇸 TATEMENT +{% endif -%} + +{%- for line in statement -%} +# {{ line }} +{% endfor %} +{%- for line in code_lines %} +{{ line }} +{%- endfor %} \ No newline at end of file diff --git a/config/stub_templates/python/read_many.py.jinja b/config/stub_templates/python/read_many.py.jinja new file mode 100644 index 00000000..3765e08c --- /dev/null +++ b/config/stub_templates/python/read_many.py.jinja @@ -0,0 +1,39 @@ +{%- if debug_mode -%} +---- 🇷 EAD_MANY +{%- if single_type -%} + (SINGLE_TYPE) +{% else -%} + (MULTIPLE_TYPE) +{% endif -%} +{% endif -%} + +{%- set_global names = vars | map(attribute="ident") | join(sep=", ") -%} + +{%- if single_type -%} + +{%- for var in vars %} +{%- if var.input_comment -%}# {{ var.ident }}: {{ var.input_comment }} +{% endif -%} +{% endfor -%} + +{%- set_global type = vars[0].var_type -%} +{%- if type == "String" or type == "Word" -%} + {%- set assign = "input().split()" -%} +{%- else -%} + {%- set assign = "[" ~ type_tokens[type] ~ "(i) for i in input().split()]" -%} +{%- endif -%} +{{ names }} = {{ assign }} + +{%- else -%} + +inputs = input().split() +{%- for var in vars %} +{% if var.var_type == "String" or var.var_type == "Word" -%} + {%- set assign = "inputs[" ~ loop.index0 ~ "]" -%} +{% else -%} + {%- set assign = type_tokens[var.var_type] ~ "(inputs[" ~ loop.index0 ~ "])" -%} +{% endif -%} +{{ var.ident }} = {{ assign }}{%- if var.input_comment %} # {{ var.input_comment }}{% endif %} +{%- endfor -%} + +{%- endif %} diff --git a/config/stub_templates/python/read_one.py.jinja b/config/stub_templates/python/read_one.py.jinja new file mode 100644 index 00000000..87e02f7a --- /dev/null +++ b/config/stub_templates/python/read_one.py.jinja @@ -0,0 +1,13 @@ +{% if debug_mode -%} +---- 🇷 EAD_ONE: {{ var.var_type }} +{% endif %} + +{%- if var.var_type == "String" or var.var_type == "Word" -%} + {%- set fn = "input()" -%} +{%- elif var.var_type == "Bool" -%} + {%- set fn = "input()" ~ type_tokens[var.var_type] -%} +{%- else -%} + {%- set fn = type_tokens[var.var_type] ~ "(input())" -%} +{%- endif -%} + +{{ var.ident }} = {{ fn }}{%- if var.input_comment %} # {{ var.input_comment }}{% endif %} diff --git a/config/stub_templates/python/stub_config.toml b/config/stub_templates/python/stub_config.toml new file mode 100644 index 00000000..3d5bcc6e --- /dev/null +++ b/config/stub_templates/python/stub_config.toml @@ -0,0 +1,16 @@ +name = "python" +variable_format = "snake_case" +source_file_ext = "py" +allow_uppercase_vars = false +keywords = [ + "for" +] +aliases = ["py"] + +[type_tokens] +Int = "int" +Long = "int" +Float = "float" +Bool = " != \"0\"" +String = "UNUSED" +Word = "UNUSED" diff --git a/config/stub_templates/python/write.py.jinja b/config/stub_templates/python/write.py.jinja new file mode 100644 index 00000000..f9fddc35 --- /dev/null +++ b/config/stub_templates/python/write.py.jinja @@ -0,0 +1,10 @@ +{%- if debug_mode -%} +---- 🇼 RITE +{% endif -%} + +{%- for line in output_comments -%} +# {{ line }} +{% endfor %} +{%- for line in messages -%} +print("{{line}}") +{% endfor -%} \ No newline at end of file diff --git a/config/stub_templates/python/write_join.py.jinja b/config/stub_templates/python/write_join.py.jinja new file mode 100644 index 00000000..244a88cf --- /dev/null +++ b/config/stub_templates/python/write_join.py.jinja @@ -0,0 +1,20 @@ +{%- if debug_mode -%} +---- 🇼 RITE_JOIN +{% endif -%} + +{%- set_global out = "" -%} +{%- for term in terms -%} + {%- if term.term_type == "Variable" -%} + {%- set_global out = out ~ "str(" ~ term.name ~ ")" -%} + {%- else -%} + {%- set_global out = out ~ '"' ~ term.name ~ '"' -%} + {%- endif -%} + {%- if loop.last == false %} + {%- set_global out = out ~ ' + " " + ' -%} + {% endif %} +{%- endfor -%} + +{%- for line in output_comments %} +# {{ line }} +{%- endfor -%} +print({{ out | replace(from='" + "', to="") }}) diff --git a/config/stub_templates/ruby/loopline.rb.jinja b/config/stub_templates/ruby/loopline.rb.jinja index 6a7bc402..3cab9b55 100644 --- a/config/stub_templates/ruby/loopline.rb.jinja +++ b/config/stub_templates/ruby/loopline.rb.jinja @@ -2,13 +2,14 @@ ---- 🇱 OOPLINE {% endif -%} -{% for cmt in comments -%} -# {{ cmt.variable }}: {{cmt.description}} +{%- for var in vars %} +{%- if var.input_comment -%}# {{ var.ident }}: {{ var.input_comment }} +{% endif -%} {% endfor -%} -gets.split.each{% if vars | length > 1 %}_slice({{ vars | length }}){% endif %} do |{{ vars | map(attribute="name") | join(sep=", ") }}| +gets.split.each{% if vars | length > 1 %}_slice({{ vars | length }}){% endif %} do |{{ vars | map(attribute="ident") | join(sep=", ") }}| {%- for var in vars -%} {%- if var.var_type != "Word" %} - {{var.name}} = {{var.name}}.{{type_tokens[var.var_type]-}} + {{var.ident}} = {{var.ident}}.{{type_tokens[var.var_type]-}} {% endif -%} {%- endfor %} end diff --git a/config/stub_templates/ruby/read_many.rb.jinja b/config/stub_templates/ruby/read_many.rb.jinja index ee3e949c..9101715d 100644 --- a/config/stub_templates/ruby/read_many.rb.jinja +++ b/config/stub_templates/ruby/read_many.rb.jinja @@ -1,12 +1,18 @@ {%- if debug_mode -%} ---- 🇷 EAD_MANY +{%- if single_type -%} + (SINGLE_TYPE) +{% else -%} + (MULTIPLE_TYPE) +{% endif -%} {% endif -%} -{% for cmt in comments -%} -# {{ cmt.variable }}: {{cmt.description}} +{%- for var in vars %} +{%- if var.input_comment -%}# {{ var.ident }}: {{ var.input_comment }} +{% endif -%} {% endfor -%} -{{- vars | map(attribute="name") | join(sep=", ") }} = gets.split +{{- vars | map(attribute="ident") | join(sep=", ") }} = gets.split {%- if single_type -%} @@ -18,9 +24,9 @@ {%- for var in vars -%} {%- if var.var_type == "String" or var.var_type == "Word" %} -{{var.name}} = {{var.name}}.chomp +{{var.ident}} = {{var.ident}}.chomp {%- else %} -{{var.name}} = {{var.name}}.{{ type_tokens[var.var_type] }} +{{var.ident}} = {{var.ident}}.{{ type_tokens[var.var_type] }} {%- endif -%} {%- endfor -%} diff --git a/config/stub_templates/ruby/read_one.rb.jinja b/config/stub_templates/ruby/read_one.rb.jinja index 120376ec..c78d0167 100644 --- a/config/stub_templates/ruby/read_one.rb.jinja +++ b/config/stub_templates/ruby/read_one.rb.jinja @@ -10,4 +10,4 @@ {% set method = "." ~ type_tokens[var.var_type] %} {%- endif -%} -{{ var.name }} = gets{{ method }}{% if comment %} # {{ comment.description }}{% endif %} +{{ var.ident }} = gets{{ method }}{% if var.input_comment %} # {{ var.input_comment }}{% endif %} diff --git a/config/stub_templates/ruby/write_join.rb.jinja b/config/stub_templates/ruby/write_join.rb.jinja index 896974c5..9cfb8654 100644 --- a/config/stub_templates/ruby/write_join.rb.jinja +++ b/config/stub_templates/ruby/write_join.rb.jinja @@ -4,7 +4,7 @@ puts " {%- for term in terms -%} - {%- if term.term_type=="Variable" -%} + {%- if term.term_type == "Variable" -%} {{- '#{' }}{{ term.name }}{{ '}' -}} {%- else -%} {{- term.name -}} diff --git a/config/stub_templates/rust/loopline.rs.jinja b/config/stub_templates/rust/loopline.rs.jinja index 64ff5594..8a91cce1 100644 --- a/config/stub_templates/rust/loopline.rs.jinja +++ b/config/stub_templates/rust/loopline.rs.jinja @@ -2,10 +2,10 @@ # LOOPLINE {% endif -%} -gets.split.each{% if vars | length > 1 %}_slice({{ vars | length }}){% endif %} do |{{ vars | map(attribute="name") | join(sep=", ") }}| +gets.split.each{% if vars | length > 1 %}_slice({{ vars | length }}){% endif %} do |{{ vars | map(attribute="ident") | join(sep=", ") }}| {%- for var in vars -%} {%- if var.var_type != "Word" %} - {{var.name}} = {{var.name}}.{{type_tokens[var.var_type]-}} + {{var.ident}} = {{var.ident}}.{{type_tokens[var.var_type]-}} {% endif -%} {%- endfor %} end diff --git a/config/stub_templates/rust/read_many.rs.jinja b/config/stub_templates/rust/read_many.rs.jinja index b1702697..4298b6a3 100644 --- a/config/stub_templates/rust/read_many.rs.jinja +++ b/config/stub_templates/rust/read_many.rs.jinja @@ -7,8 +7,9 @@ {% endif -%} {% endif -%} -{% for cmt in comments -%} -// {{ cmt.variable }}: {{cmt.description}} +{%- for var in vars %} +{%- if var.input_comment -%}// {{ var.ident }}: {{ var.input_comment }} +{% endif -%} {% endfor -%} let mut input_line = String::new(); @@ -19,18 +20,18 @@ let inputs = input_line.split(" ").collect::>(); {%- for var in vars -%} {%- if var.var_type == "String" or var.var_type == "Word" %} -let {{var.name}} = inputs[{{ loop.index - 1 }}].trim().to_string(); +let {{var.ident}} = inputs[{{ loop.index - 1 }}].trim().to_string(); {%- else %} -let {{var.name}} = parse_input!(inputs[{{ loop.index - 1 }}], {{ type_tokens[var.var_type] }}); +let {{var.ident}} = parse_input!(inputs[{{ loop.index - 1 }}], {{ type_tokens[var.var_type] }}); {%- endif %} {%- endfor -%} {%- else %} {%- for var in vars -%} {%- if var.var_type == "String" or var.var_type == "Word" %} -let {{var.name}} = inputs[{{ loop.index - 1 }}].trim().to_string(); +let {{var.ident}} = inputs[{{ loop.index - 1 }}].trim().to_string(); {%- else %} -let {{var.name}} = parse_input!(inputs[{{ loop.index - 1 }}], {{ type_tokens[var.var_type] }}); +let {{var.ident}} = parse_input!(inputs[{{ loop.index - 1 }}], {{ type_tokens[var.var_type] }}); {%- endif -%} {%- endfor -%} diff --git a/config/stub_templates/rust/read_one.rs.jinja b/config/stub_templates/rust/read_one.rs.jinja index 5dab7046..d5ad60c5 100644 --- a/config/stub_templates/rust/read_one.rs.jinja +++ b/config/stub_templates/rust/read_one.rs.jinja @@ -6,9 +6,9 @@ let mut input_line = String::new(); io::stdin().read_line(&mut input_line).unwrap(); {%- if var.var_type == "String" or var.var_type == "Word" %} -let {{ var.name }} = input_line.trim_matches('\n').to_string(); +let {{ var.ident }} = input_line.trim_matches('\n').to_string(); {% else %} -let {{ var.name }} = parse_input!(input_line, {{ type_tokens[var.var_type] }}); +let {{ var.ident }} = parse_input!(input_line, {{ type_tokens[var.var_type] }}); {% endif %} {%- if comment -%} diff --git a/src/stub.rs b/src/stub.rs index 7638e703..4f2ba626 100644 --- a/src/stub.rs +++ b/src/stub.rs @@ -100,7 +100,6 @@ puts "many spaces here" try = gets.to_bool _nil = gets.chomp l = gets.chomp -# a: does stuff a, b = gets.split x_tra, y = gets.split.map(&:to_i) annoying = gets @@ -151,57 +150,16 @@ puts "hello #{a} planet""##; } } + // Just test that it compiles #[test] fn test_reference_stub_rust() { - // TODO let lang = Language::try_from("rust").unwrap(); - let received = generate(lang, REFERENCE_STUB).unwrap(); - let expected = r##"[Live long , and prosper] -puts "many spaces here" -try = gets.to_bool -_nil = gets.chomp -l = gets.chomp -# a: does stuff -a, b = gets.split -x_tra, y = gets.split.map(&:to_i) -annoying = gets.chomp -another_annoying = gets.chomp -a_bc = gets.chomp -row = gets.chomp -n.times do - ext, mt = gets.split -end -n.times do - count, name = gets.split - count = count.to_i - name = name.chomp -end -q.times do - fname = gets.chomp -end -4.times do - number = gets.to_i -end -4.times do - puts "0 0" -end -x_count = gets.to_i -gets.split.each do |x| - x = x.to_i -end -gets.split.each_slice(3) do |x, y, z| - x = x.to_i - y = y.to_i -end -puts "#{a} #{b}" -puts "#{a} #{b}" -puts "#{a} b #{aBc}" -puts "hello world" -puts "hello #{a} planet""##; - println!("{}", received); + generate(lang, REFERENCE_STUB).unwrap(); + } - // for (r, e) in received.lines().zip(expected.lines()) { - // assert_eq!(r, e) - // } + #[test] + fn test_reference_stub_c() { + let lang = Language::try_from("C").unwrap(); + generate(lang, REFERENCE_STUB).unwrap(); } } diff --git a/src/stub/parser.rs b/src/stub/parser.rs index e8133e04..06986408 100644 --- a/src/stub/parser.rs +++ b/src/stub/parser.rs @@ -3,10 +3,10 @@ use regex::Regex; pub mod types; -pub use types::{Cmd, InputComment, JoinTerm, JoinTermType, LengthType, Stub, VariableCommand}; +pub use types::{Cmd, JoinTerm, JoinTermType, Stub, VariableCommand}; pub fn parse_generator_stub(generator: String) -> Stub { - let generator = generator.replace('\n', " \n ").replace("\n \n", "\n \n"); + let generator = generator.replace('\n', " \n "); let stream = generator.split(' '); Parser::new(stream).parse() } @@ -22,7 +22,7 @@ impl<'a, I: Iterator> Parser { #[rustfmt::skip] fn parse(&mut self) -> Stub { - let mut stub = Stub::new(); + let mut stub = Stub::default(); while let Some(token) = self.stream.next() { match token { @@ -31,10 +31,10 @@ impl<'a, I: Iterator> Parser { "loop" => stub.commands.push(self.parse_loop()), "loopline" => stub.commands.push(self.parse_loopline()), "OUTPUT" => self.parse_output_comment(&mut stub.commands), - "INPUT" => stub.input_comments.append(&mut self.parse_input_comment()), - "STATEMENT" => stub.statement = self.parse_statement(), + "INPUT" => self.parse_input_comment(&mut stub.commands), + "STATEMENT" => stub.statement = self.parse_text_block(), "\n" | "" => continue, - thing => panic!("Error parsing stub generator: {}", thing), + thing => panic!("Unknown token stub generator: '{}'", thing), }; } @@ -42,63 +42,76 @@ impl<'a, I: Iterator> Parser { } fn parse_read(&mut self) -> Cmd { - Cmd::Read(self.parse_variable_list()) + Cmd::Read(self.parse_variables()) } fn parse_write(&mut self) -> Cmd { - let mut output: Vec = Vec::new(); - - while let Some(token) = self.stream.next() { - let next_token = match token { - "\n" => match self.stream.next() { - Some("\n") | None => break, - Some(str) => format!("\n{}", str), - }, - join if join.starts_with("join(") => return self.parse_write_join(join), - other => String::from(other), - }; - - output.push(next_token); + let mut write_text: Vec = Vec::new(); + let mut first_line = true; + + while let Some(line) = self.upto_newline() { + // NOTE: write•join()•rest⏎, with NOTHING inside the parens, + // gets parsed as a write and not as a write_join + // NOTE: write•join("a")⏎ is a valid join + // NOTE: write•join(⏎ gets parsed as a raw_string + if let Some(position) = line + .iter() + .position(|&token| token.starts_with("join(") && !token.starts_with("join()") && first_line) + { + let result_slice = &line[position..]; + return self.parse_write_join(result_slice.to_vec()) + } + first_line = false; + write_text.push(line.join(" ").trim().to_string()) } Cmd::Write { - text: output.join(" "), + text: write_text.join("\n"), output_comment: String::new(), } } - fn parse_write_join(&mut self, start: &str) -> Cmd { - let mut raw_string = String::from(start); - - while let Some(token) = self.stream.next() { - match token { - "\n" => panic!("'join(' never closed"), - last_term if last_term.contains(')') => { - raw_string.push_str(last_term); - break; + fn parse_write_join(&self, line_stream: Vec<&str>) -> Cmd { + let inner = line_stream.join(" "); + let terms_finder = Regex::new(r"join\(([^)]*)\)").unwrap(); + let terms_string_captures = terms_finder.captures(&inner); + let terms_string = match terms_string_captures { + None => { + // in case write join(⏎ + return Cmd::Write { + text: inner, + output_comment: String::new(), } - regular_term => raw_string.push_str(regular_term), } - } - - self.skip_to_next_line(); + Some(str) => str.get(1).unwrap().as_str(), + }; + let term_splitter = Regex::new(r",\s*").unwrap(); + let literal_matcher = Regex::new("\\\"([^)]+)\\\"").unwrap(); - let terms_finder = Regex::new(r"join\((.+)\)").unwrap(); - let terms_string = terms_finder.captures(&raw_string).unwrap().get(1).unwrap().as_str(); - let term_splitter = Regex::new(r"\s*,\s*").unwrap(); - let terms: Vec = term_splitter + let join_terms = term_splitter .split(terms_string) .map(|term_str| { - let literal_matcher = Regex::new("^\\\"(.+)\\\"$").unwrap(); if let Some(mtch) = literal_matcher.captures(term_str) { JoinTerm::new(mtch.get(1).unwrap().as_str().to_owned(), JoinTermType::Literal) } else { JoinTerm::new(term_str.to_owned(), JoinTermType::Variable) } }) - .collect(); + .collect::>(); + + // write•join("hi",,,•"Jim")⏎ should be rendered as a Write Cmd + // (I guess the original parser fails a previous command due to consecutive commas) + if join_terms.iter().any(|jt| jt.name.is_empty()) { + return Cmd::Write { + text: inner, + output_comment: String::new(), + } + } - Cmd::WriteJoin(terms) + Cmd::WriteJoin { + join_terms, + output_comment: String::new(), + } } fn parse_loop(&mut self) -> Cmd { @@ -130,7 +143,7 @@ impl<'a, I: Iterator> Parser { None => panic!("Unexpected end of input: Loopline stub not provided with count identifier"), Some(other) => Cmd::LoopLine { count_var: other.to_string(), - variables: self.parse_variable_list(), + variables: self.parse_variables(), }, } } @@ -142,10 +155,10 @@ impl<'a, I: Iterator> Parser { // Trim because the stub generator may contain sneaky newlines match var_type.trim_end() { - "int" => VariableCommand::Int { name: identifier }, - "float" => VariableCommand::Float { name: identifier }, - "long" => VariableCommand::Long { name: identifier }, - "bool" => VariableCommand::Bool { name: identifier }, + "int" => VariableCommand::new(identifier, types::VarType::Int, None), + "float" => VariableCommand::new(identifier, types::VarType::Float, None), + "long" => VariableCommand::new(identifier, types::VarType::Long, None), + "bool" => VariableCommand::new(identifier, types::VarType::Bool, None), _ => { let length_regex = Regex::new(r"(word|string)\((\w+)\)").unwrap(); let length_captures = length_regex.captures(var_type); @@ -154,42 +167,32 @@ impl<'a, I: Iterator> Parser { let new_type = caps.get(1).unwrap().as_str(); let length = caps.get(2).unwrap().as_str(); let max_length = String::from(length); - let length_type = LengthType::from(length); match new_type { - "word" => VariableCommand::Word { - name: identifier, - max_length, - length_type, - }, - "string" => VariableCommand::String { - name: identifier, - max_length, - length_type, - }, + "word" => VariableCommand::new(identifier, types::VarType::Word, Some(max_length)), + "string" => VariableCommand::new(identifier, types::VarType::String, Some(max_length)), _ => panic!("Unexpected error"), } } } } - fn parse_variable_list(&mut self) -> Vec { + fn parse_variables(&mut self) -> Vec { let mut vars = Vec::new(); + let Some(line) = self.upto_newline() else { + panic!("Empty line after read keyword") + }; - while let Some(token) = self.stream.next() { - let var: VariableCommand = match token { - _ if String::from(token).contains(':') => Self::parse_variable(token), - "\n" => break, - unexp => panic!("Error in stub generator, found {unexp} while searching for stub variables"), - }; - - vars.push(var); + for token in line { + if token != "" { + vars.push(Self::parse_variable(token)) + } } vars } fn parse_output_comment(&mut self, previous_commands: &mut [Cmd]) { - let output_comment = self.parse_statement(); + let output_comment = self.parse_text_block(); for cmd in previous_commands { Self::update_cmd_with_output_comment(cmd, &output_comment) } @@ -200,6 +203,10 @@ impl<'a, I: Iterator> Parser { Cmd::Write { text: _, ref mut output_comment, + } + | Cmd::WriteJoin { + join_terms: _, + ref mut output_comment, } if output_comment.is_empty() => *output_comment = new_comment.to_string(), Cmd::Loop { count_var: _, @@ -211,44 +218,50 @@ impl<'a, I: Iterator> Parser { } } - fn parse_input_comment(&mut self) -> Vec { - self.skip_to_next_line(); - let mut comments = Vec::new(); - - while let Some(token) = self.stream.next() { - let comment = match token { - "\n" => break, - _ => match token.strip_suffix(':') { - Some(variable) => InputComment::new(String::from(variable), self.read_to_end_of_line()), - None => { - self.skip_to_next_line(); - continue - } - }, - }; + // Doesn't deal with InputComments to unassigned variables + // nor InputComments to variables with the same identifier + fn parse_input_comment(&mut self, previous_commands: &mut [Cmd]) { + let input_statement = self.parse_text_block(); + let input_comments = input_statement + .lines() + .filter(|line| line.contains(':')) + .map(|line| { + if let Some((var, rest)) = line.split_once(':') { + (String::from(var.trim()), String::from(rest.trim())) + } else { + panic!("Impossible since the list was filtered??"); + } + }) + .collect::>(); - comments.push(comment) + for (ic_ident, ic_comment) in input_comments { + for cmd in previous_commands.iter_mut() { + Self::update_cmd_with_input_comment(cmd, &ic_ident, &ic_comment); + } } - - comments } - fn parse_statement(&mut self) -> String { - self.skip_to_next_line(); - self.parse_text_block() - } - - fn read_to_end_of_line(&mut self) -> String { - let mut upto_end_of_line = Vec::new(); - - while let Some(token) = self.stream.next() { - match token { - "\n" => break, - other => upto_end_of_line.push(other), + fn update_cmd_with_input_comment(cmd: &mut Cmd, ic_ident: &String, ic_comment: &String) { + match cmd { + Cmd::Read(variables) + | Cmd::LoopLine { + count_var: _, + variables, + } => { + for var in variables.iter_mut() { + if var.ident == *ic_ident { + var.input_comment = ic_comment.clone(); + } + } + } + Cmd::Loop { + count_var: _, + ref mut command, + } => { + return Self::update_cmd_with_input_comment(command, &ic_ident, &ic_comment); } + _ => (), } - - upto_end_of_line.join(" ") } fn skip_to_next_line(&mut self) { @@ -260,29 +273,14 @@ impl<'a, I: Iterator> Parser { } fn parse_text_block(&mut self) -> String { - let mut text_block: Vec = Vec::new(); + self.skip_to_next_line(); - while let Some(token) = self.stream.next() { - let next_token = match token { - "\n" => match self.stream.next() { - Some("\n") | None => break, - Some(str) => format!("\n{}", str), - }, - other => String::from(other), - }; - text_block.push(next_token); + let mut text_block: Vec = Vec::new(); + while let Some(line) = self.upto_newline() { + text_block.push(line.join(" ").trim().to_string()) } - // Hacky - The replace is due to the "replace hacks" at generator - // so that ["you", "\ndown"] -> you\ndown ... - // while trimming spaces both sides of each line - text_block - .join(" ") - .replace(" \n", "\n") - .split('\n') - .map(|line| line.trim()) - .collect::>() - .join("\n") + text_block.join("\n") } fn next_past_newline(&mut self) -> Option<&'a str> { @@ -292,4 +290,21 @@ impl<'a, I: Iterator> Parser { token => token, } } + + // Consumes the newline + fn upto_newline(&mut self) -> Option> { + let mut buf = Vec::new(); + while let Some(token) = self.stream.next() { + if token == "\n" { + break + } + buf.push(token) + } + + if buf.join("").is_empty() { + None + } else { + Some(buf) + } + } } diff --git a/src/stub/parser/types.rs b/src/stub/parser/types.rs index ad498a34..e36893cd 100644 --- a/src/stub/parser/types.rs +++ b/src/stub/parser/types.rs @@ -1,10 +1,8 @@ use serde::Serialize; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Stub { pub commands: Vec, - pub input_comments: Vec, - pub output_comment: String, pub statement: String, } @@ -12,93 +10,42 @@ pub struct Stub { impl std::fmt::Debug for Stub { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Stub {{\n commands: [")?; - - // Print commands recursively for command in &self.commands { write!(f, "\n {:?}", command)?; } - - write!( - f, - "\n ],\n input_comments: {:?},\n output_comment: {:?},\n statement: {:?}\n}}", - self.input_comments, self.output_comment, self.statement - ) + write!(f, "\n ],\n statement: {:?}\n}}", self.statement) } } -impl Stub { - pub fn new() -> Self { - Self { - commands: Vec::new(), - input_comments: Vec::new(), - output_comment: String::new(), - statement: String::new(), - } - } -} - -impl Default for Stub { - fn default() -> Self { - Self::new() - } +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +pub enum VarType { + Int, + Float, + Long, + Bool, + Word, + String, } #[derive(Debug, Clone, Serialize)] -pub struct InputComment { - pub variable: String, - pub description: String, -} - -impl InputComment { - pub fn new(variable: String, description: String) -> Self { - Self { - variable, - description, - } - } -} - -#[derive(Serialize, Clone, Debug)] -pub enum LengthType { - Number, - Variable, +pub struct VariableCommand { + pub ident: String, + pub var_type: VarType, + pub max_length: Option, + pub input_comment: String, } -impl<'a> From<&'a str> for LengthType { - fn from(value: &'a str) -> Self { - match value.parse::() { - Ok(_) => Self::Number, - Err(_) => Self::Variable, +impl VariableCommand { + pub fn new(ident: String, var_type: VarType, max_length: Option) -> VariableCommand { + VariableCommand { + ident, + var_type, + max_length, + input_comment: String::new(), } } } -#[derive(Debug, Clone, Serialize)] -pub enum VariableCommand { - Int { - name: String, - }, - Float { - name: String, - }, - Long { - name: String, - }, - Bool { - name: String, - }, - Word { - name: String, - max_length: String, - length_type: LengthType, - }, - String { - name: String, - max_length: String, - length_type: LengthType, - }, -} - #[derive(Serialize, Clone, Debug)] pub enum JoinTermType { Literal, @@ -132,5 +79,8 @@ pub enum Cmd { text: String, output_comment: String, }, - WriteJoin(Vec), + WriteJoin { + join_terms: Vec, + output_comment: String, + }, } diff --git a/src/stub/renderer.rs b/src/stub/renderer.rs index f77b79b2..0bff3d7d 100644 --- a/src/stub/renderer.rs +++ b/src/stub/renderer.rs @@ -1,15 +1,12 @@ pub mod language; -mod types; use anyhow::{Context as _, Result}; // To distinguish it from tera::Context use itertools::Itertools; use language::Language; use serde_json::json; use tera::{Context, Tera}; -use types::ReadData; -use self::types::VariableType; -use super::parser::{Cmd, InputComment, JoinTerm, JoinTermType, Stub, VariableCommand}; +use super::parser::{Cmd, JoinTerm, JoinTermType, Stub, VariableCommand}; const ALPHABET: [char; 18] = [ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', @@ -28,13 +25,9 @@ struct Renderer { } impl Renderer { - fn new(lang: Language, mut stub: Stub, debug_mode: bool) -> Result { + fn new(lang: Language, stub: Stub, debug_mode: bool) -> Result { let tera = Tera::new(&lang.template_glob())?; - for comment in &mut stub.input_comments { - comment.variable = lang.transform_variable_name(&comment.variable); - } - Ok(Self { lang, tera, @@ -84,25 +77,31 @@ impl Renderer { match cmd { Cmd::Read(vars) => self.render_read(vars), Cmd::Write { text, output_comment } => self.render_write(text, output_comment), - Cmd::WriteJoin(join_terms) => self.render_write_join(join_terms), + Cmd::WriteJoin { + join_terms, + output_comment, + } => self.render_write_join(join_terms, output_comment), Cmd::Loop { count_var, command } => self.render_loop(count_var, command, nesting_depth), - Cmd::LoopLine { count_var, variables } => self.render_loopline(count_var, variables), + Cmd::LoopLine { count_var, variables } => { + self.render_loopline(count_var, variables, nesting_depth) + } } } fn render_write(&self, text: &str, output_comment: &str) -> String { let mut context = Context::new(); - let messages: Vec<&str> = text.lines().map(|msg| msg.trim_end()).collect(); - let output_comments: Vec<&str> = output_comment.lines().map(|msg| msg.trim_end()).collect(); + let output_comments: Vec<&str> = output_comment.lines().collect(); + let messages: Vec<&str> = text.lines().collect(); + context.insert("messages", &messages); context.insert("output_comments", &output_comments); self.tera_render("write", &mut context) } - fn render_write_join(&self, terms: &[JoinTerm]) -> String { + fn render_write_join(&self, terms: &[JoinTerm], output_comment: &str) -> String { let mut context = Context::new(); - + let output_comments: Vec<&str> = output_comment.lines().collect(); let terms: Vec = terms .iter() .cloned() @@ -115,6 +114,8 @@ impl Renderer { .collect(); context.insert("terms", &terms); + context.insert("output_comments", &output_comments); + self.tera_render("write_join", &mut context) } @@ -127,11 +128,9 @@ impl Renderer { fn render_read_one(&self, var: &VariableCommand) -> String { let mut context = Context::new(); - let var_data = &ReadData::new(var, &self.lang); - let comment = self.stub.input_comments.iter().find(|comment| var_data.name == comment.variable); + let var = self.lang.transform_variable_command(var); - context.insert("comment", &comment); - context.insert("var", var_data); + context.insert("var", &var); context.insert("type_tokens", &self.lang.type_tokens); self.tera_render("read_one", &mut context) @@ -139,26 +138,16 @@ impl Renderer { fn render_read_many(&self, vars: &[VariableCommand]) -> String { let mut context = Context::new(); + let vars = vars.iter().map(|var| self.lang.transform_variable_command(var)).collect::>(); - let read_data: Vec = - vars.iter().map(|var_cmd| ReadData::new(var_cmd, &self.lang)).collect(); - - let comments: Vec<&InputComment> = self - .stub - .input_comments - .iter() - .filter(|comment| read_data.iter().any(|var_data| var_data.name == comment.variable)) - .collect(); - - let types: Vec<&VariableType> = read_data.iter().map(|r| &r.var_type).unique().collect(); + let types: Vec<_> = vars.iter().map(|r| &r.var_type).unique().collect(); match types.as_slice() { [single_type] => context.insert("single_type", single_type), _ => context.insert("single_type", &false), } - context.insert("comments", &comments); - context.insert("vars", &read_data); + context.insert("vars", &vars); context.insert("type_tokens", &self.lang.type_tokens); self.tera_render("read_many", &mut context) @@ -176,25 +165,18 @@ impl Renderer { self.tera_render("loop", &mut context) } - fn render_loopline(&self, count_var: &str, vars: &[VariableCommand]) -> String { - let read_data: Vec = - vars.iter().map(|var_cmd| ReadData::new(var_cmd, &self.lang)).collect(); + fn render_loopline(&self, count_var: &str, vars: &[VariableCommand], nesting_depth: usize) -> String { + let vars = vars.iter().map(|var| self.lang.transform_variable_command(var)).collect::>(); let mut context = Context::new(); let cased_count_var = self.lang.transform_variable_name(count_var); - - let comments: Vec<&InputComment> = self - .stub - .input_comments - .iter() - .filter(|comment| read_data.iter().any(|var_data| var_data.name == comment.variable)) - .collect(); + let index_ident = ALPHABET[nesting_depth]; context.insert("count_var", &cased_count_var); - context.insert("vars", &read_data); - context.insert("comments", &comments); + context.insert("vars", &vars); context.insert("type_tokens", &self.lang.type_tokens); + context.insert("index_ident", &index_ident); self.tera_render("loopline", &mut context) } diff --git a/src/stub/renderer/language.rs b/src/stub/renderer/language.rs index 93d288a5..f08c6af2 100644 --- a/src/stub/renderer/language.rs +++ b/src/stub/renderer/language.rs @@ -5,6 +5,8 @@ use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; +use crate::stub::parser::VariableCommand; + lazy_static! { static ref SC_WORD_BREAK: Regex = Regex::new(r"([a-z])([A-Z])").unwrap(); static ref PC_WORD_BREAK: Regex = Regex::new(r"([A-Z]*)([A-Z][a-z])").unwrap(); @@ -88,6 +90,15 @@ impl Language { self.escape_keywords(converted_variable_name) } + pub fn transform_variable_command(&self, var: &VariableCommand) -> VariableCommand { + VariableCommand { + ident: self.transform_variable_name(&var.ident), + var_type: var.var_type.clone(), + input_comment: var.input_comment.clone(), + max_length: var.max_length.clone(), + } + } + pub fn escape_keywords(&self, variable_name: String) -> String { if self.keywords.contains(&variable_name) { format!("_{variable_name}") diff --git a/src/stub/renderer/types.rs b/src/stub/renderer/types.rs deleted file mode 100644 index 69aa9ecc..00000000 --- a/src/stub/renderer/types.rs +++ /dev/null @@ -1,67 +0,0 @@ -use serde::Serialize; - -use super::language::Language; -use crate::stub::parser::types::VariableCommand; -use crate::stub::parser::LengthType; - -#[derive(Debug, Clone, Serialize, Hash, PartialEq, Eq)] -pub enum VariableType { - Int, - Float, - Long, - Bool, - Word, - String, -} - -#[derive(Debug, Clone, Serialize)] -pub struct ReadData { - pub name: String, - pub var_type: VariableType, - pub max_length: Option, - pub length_type: Option, -} - -impl ReadData { - // VariableNameFormat is just the case (snake_case, pascal_case etc.) - pub fn new(value: &VariableCommand, lang: &Language) -> Self { - use {VariableCommand as VC, VariableType as VT}; - - let (name, var_type, max_length, length_type) = match value { - VC::Int { name } => (name, VT::Int, None, None), - VC::Float { name } => (name, VT::Float, None, None), - VC::Long { name } => (name, VT::Long, None, None), - VC::Bool { name } => (name, VT::Bool, None, None), - VC::Word { - name, - max_length, - length_type, - } - | VC::String { - name, - max_length, - length_type, - } => { - let length = match length_type { - LengthType::Variable => lang.transform_variable_name(max_length), - LengthType::Number => max_length.clone(), - }; - - let var_type = if let VC::Word { .. } = value { - VT::Word - } else { - VT::String - }; - - (name, var_type, Some(length), Some(length_type.clone())) - } - }; - - Self { - name: lang.transform_variable_name(name), - var_type, - max_length, - length_type, - } - } -} diff --git a/tests/render_py_tests.rs b/tests/render_py_tests.rs new file mode 100644 index 00000000..dd307839 --- /dev/null +++ b/tests/render_py_tests.rs @@ -0,0 +1,462 @@ +use clashlib::stub::generate; +use clashlib::stub::renderer::language::Language; + +fn test_stub_builder(generator: &str, expected: &str) { + let lang = Language::try_from("python").unwrap(); + let received = generate(lang, generator).unwrap().as_str().trim().to_string(); + let expected = expected.trim(); + + assert_eq!(expected.lines().count(), received.lines().count()); + for (r, e) in expected.lines().zip(received.lines()) { + assert_eq!(r, e) + } +} + +#[test] +fn test_stub_read_1() { + let generator = r##"read anInt:int +read I:int +read aFloat:float +read aLong:long +read aString:string(256) +read aWord:word(256) +read Spaces:string(10) +read aBOOL:bool +"##; + let expected = r##"an_int = int(input()) +i = int(input()) +a_float = float(input()) +a_long = int(input()) +a_string = input() +a_word = input() +spaces = input() +a_bool = input() != "0" +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_read_2() { + let generator = r##"read x:int y:int +read x:int y:float +read two:word(50) words:word(50) +read aWord:word(50) x:int +read inputs:string(256) +"##; + let expected = r##"x, y = [int(i) for i in input().split()] +inputs = input().split() +x = int(inputs[0]) +y = float(inputs[1]) +two, words = input().split() +inputs = input().split() +a_word = inputs[0] +x = int(inputs[1]) +inputs = input() +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_loop() { + let generator = r##"read nLoop:int +loop nLoop read anInt:int +loop nLoop read anInt:int aFloat:float aWord:word(256) +read nLoopLines:int +loopline nLoopLines anInt:int +loopline nLoopLines aFloat:float aLong:long +loop nLoop loopline 5 word:word(1) +loop nLoop loopline 5 string:string(1) +"##; + let expected = r##"n_loop = int(input()) +for i in range(n_loop): + an_int = int(input()) +for i in range(n_loop): + inputs = input().split() + an_int = int(inputs[0]) + a_float = float(inputs[1]) + a_word = inputs[2] +n_loop_lines = int(input()) +for i in input().split(): + an_int = int(i) +inputs = input().split() +for i in range(n_loop_lines): + a_float = float(inputs[2*i]) + a_long = int(inputs[2*i+1]) +for i in range(n_loop): + for word in input().split(): + pass +for i in range(n_loop): + for j in input().split(): + string = j +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_write_1() { + let generator = r##"write Never + +read n:int +loop n write gonna + +loop n loop n write let + +write you +down + +write write + +write care, here spaces everywhere + and some more + +write and dont do this, this breaks paitong + +write " +' +" +"##; + let expected = r##"print("Never") +n = int(input()) +for i in range(n): + print("gonna") +for i in range(n): + for j in range(n): + print("let") +print("you") +print("down") +print("write") +print("care, here spaces everywhere") +print("and some more") +print("and dont do this, this breaks paitong") +print(""") +print("'") +print(""") +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_write_2() { + let generator = r##"read n:int +read aBc:int + +write join("a", "b") +write join("a", n) +write join(n, "a") +write join(n, aBc) + +write THIS IS IGNORED join(n, aBc) +write something join(n, aBc, n) something + +write join(n, "potato", aBc, n) +"##; + let expected = r##"n = int(input()) +a_bc = int(input()) +print("a b") +print("a " + str(n)) +print(str(n) + " a") +print(str(n) + " " + str(a_bc)) +print(str(n) + " " + str(a_bc)) +print(str(n) + " " + str(a_bc) + " " + str(n)) +print(str(n) + " potato " + str(a_bc) + " " + str(n)) + +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_write_3() { + let generator = r##"read n:int +read aBc:int +write join( " le " , " spaces " ) + +write join( + +write join (baited) +This only works because the next join gets parsed as raw text +write join( ) + +write join("a") +write join("b") join("IGNORED") +write join("c") join( + +write join(join("a"), n, join("b"))))) + +write join("d", write("writeception")) +write join("d", write(join("a", aBc))) +write join("d", write join("a", aBc) ) +"##; + let expected = r##"n = int(input()) +a_bc = int(input()) +print(" le spaces ") +print("join(") +print("join (baited)") +print("This only works because the next join gets parsed as raw text") +print("write join( )") +print("a") +print("b") +print("c") +print("a") +print("d writeception") +print("d a " + str(a_bc)) +print("d a " + str(a_bc)) + +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_write_4() { + let generator = r##"read NONSENSE:int +write join("hi", NONSENSE INJECTED "it's me Jim" WHAT THE F$) +write join("hi", NONSENSE, INJECTED "it's me Jim" WHAT THE F$) +write join("hi",,, "Jim") + +write join(join("hi" , (((( "Jim") ) + +write join("NEVER") IGNORED join("GONNA") +write join() IGNORED join("GONNA") +write join() LET + +write join("YOU", join("JOIN")) +"##; + let expected = r##"nonsense = int(input()) +print("hi it's me Jim") +print("hi " + str(nonsense) + " it's me Jim") +print("join("hi",,, "Jim")") +print("hi Jim") +print("NEVER") +print("GONNA") +print("join() LET") +print("YOU JOIN") +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_statement() { + let generator = r##"STATEMENT +There can only be one per stub + +STATEMENT this gets ignored +This should start here +STATEMENT +override the previous statement + and end here (no spaces both sides) + + +read n:int +loop n loop n loop n write crazy +right? +"##; + let expected = r##"# This should start here +# STATEMENT +# override the previous statement +# and end here (no spaces both sides) + +n = int(input()) +for i in range(n): + for j in range(n): + for k in range(n): + print("crazy") + print("right?") +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_output() { + let generator = r##"write answer0 + +write answer1 + +OUTPUT +This goes to answer 0 and 1 +OUTPUT +Together with this + +OUTPUT +THis goes to Narnia + +write answer2 + +OUTPUT oops! +This goes to answer 2 but not 3 + +write answer3 +STATEMENT +baited, care spaces + +STATEMENT +Hello world! +"##; + let expected = r##"# Hello world! + +# This goes to answer 0 and 1 +# OUTPUT +# Together with this +print("answer0") +# This goes to answer 0 and 1 +# OUTPUT +# Together with this +print("answer1") +# This goes to answer 2 but not 3 +print("answer2") +print("answer3") +print("STATEMENT") +print("baited, care spaces") +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_input_1() { + let generator = r##"read init:int +read x:int + +INPUT ignored +x: some variable +y: some other inexistent variable + +read x:int + +INPUT +y: some variable that now exists but gets ignored due to previous INPUT + +read two:word(50) words:word(50) +read i:int f:float + +INPUT +init: the first variable +words: some words + +INPUT +two:a number, duh +i: int +f: float +"##; + let expected = r##"init = int(input()) # the first variable +x = int(input()) # some variable +x = int(input()) +# two: a number, duh +# words: some words +two, words = input().split() +inputs = input().split() +i = int(inputs[0]) # int +f = float(inputs[1]) # float +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_input_2() { + let generator = r##"read A:int + +INPUT case sensitive??? +a : NEIN NEIN +A : ???? +"##; + + let expected = r##"a = int(input()) # ???? +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_everything() { + let generator = r##"write many spaces here + +read L:string(20) + +OUTPUT +The spacemaster + +read a:word(50) b:word(50) +read aBc:string(256) +read ROW:string(1024) + +INPUT +ROW: Your boat +This is ignored +aBc: The alphabet + +loop N read EXT:word(100) MT:word(100) +loop N read count:int name:word(50) + +loop Q read FNAME:string(500) + +loop 4 read number:int + +loop 4 write 0 0 + +STATEMENT +Head, shoulders knees and toes +Knees and toes + +read xCount:int +loopline xCount x:int +loopline xCount y:int z:word(50) +"##; + let expected = r##"# Head, shoulders knees and toes +# Knees and toes + +# The spacemaster +print("many spaces here") +l = input() +a, b = input().split() +a_bc = input() # The alphabet +row = input() # Your boat +for i in range(n): + ext, mt = input().split() +for i in range(n): + inputs = input().split() + count = int(inputs[0]) + name = inputs[1] +for i in range(q): + fname = input() +for i in range(4): + number = int(input()) +for i in range(4): + print("0 0") +x_count = int(input()) +for i in input().split(): + x = int(i) +inputs = input().split() +for i in range(x_count): + y = int(inputs[2*i]) + z = inputs[2*i+1] +"##; + + test_stub_builder(generator, expected); +} + +#[test] +fn test_stub_variable_length() { + let generator = r##"read n:int +read k:string(n) +loop n read a:string(5) +write answer + +INPUT +k: this string is n-sized (irrelevant in python but be wary!) +"##; + let expected = r##"n = int(input()) +k = input() # this string is n-sized (irrelevant in python but be wary!) +for i in range(n): + a = input() +print("answer") +"##; + + test_stub_builder(generator, expected); +}