mirror of
https://github.com/danbulant/rush
synced 2026-05-27 14:02:17 +00:00
feat: some progress
This commit is contained in:
parent
d04d10cb9a
commit
845f00df0b
12 changed files with 404 additions and 43 deletions
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
7
.idea/discord.xml
Normal file
7
.idea/discord.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="PROJECT" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/fileColors.xml
Normal file
7
.idea/fileColors.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SharedFileColors">
|
||||||
|
<fileColor scope="Non-Project Files" color="Yellow" />
|
||||||
|
<fileColor scope="Tests" color="Green" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/rush.iml" filepath="$PROJECT_DIR$/.idea/rush.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/rush.iml
Normal file
14
.idea/rush.iml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
7
.idea/vagrant.xml
Normal file
7
.idea/vagrant.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VagrantProjectSettings">
|
||||||
|
<option name="instanceFolder" value="" />
|
||||||
|
<option name="provider" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
130
src/main.rs
130
src/main.rs
|
|
@ -1,73 +1,139 @@
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Stdout, Write};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
use termion::raw::IntoRawMode;
|
use termion::raw::{IntoRawMode, RawTerminal};
|
||||||
use termion::input::TermRead;
|
use termion::input::TermRead;
|
||||||
use termion::cursor::{self, DetectCursorPos};
|
use termion::cursor::{self, DetectCursorPos};
|
||||||
use termion::event::*;
|
use termion::event::*;
|
||||||
use termion::input::{MouseTerminal};
|
use termion::input::{MouseTerminal};
|
||||||
|
|
||||||
|
struct Term {
|
||||||
|
input: String,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Term {
|
||||||
|
fn new() -> Term {
|
||||||
|
return Term {
|
||||||
|
input: String::new(),
|
||||||
|
idx: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print(self: &Self, stdout: &mut RawTerminal<Stdout>) {
|
||||||
|
print!("{}", self.format(stdout));
|
||||||
|
}
|
||||||
|
fn format(self: &Self, stdout: &mut RawTerminal<Stdout>) -> String {
|
||||||
|
let (_, y) = stdout.cursor_pos().unwrap();
|
||||||
|
format!(
|
||||||
|
"{}{}{}{}{}",
|
||||||
|
termion::clear::CurrentLine,
|
||||||
|
termion::cursor::Goto(1, y),
|
||||||
|
&self.input,
|
||||||
|
termion::cursor::Left((self.input.len() - self.idx).try_into().unwrap()),
|
||||||
|
termion::cursor::Right(if self.input.len() > 0 { 1 } else { 0 } )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(self: &mut Self, idx: usize, char: char) {
|
||||||
|
self.input.insert(idx, char);
|
||||||
|
}
|
||||||
|
fn insert_str(self: &mut Self, idx: usize, str: &str) {
|
||||||
|
self.input.insert_str(idx, str);
|
||||||
|
}
|
||||||
|
fn remove(self: &mut Self, idx: usize) {
|
||||||
|
self.input.remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shell {
|
||||||
|
term: Term,
|
||||||
|
ctx: parser::vars::Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
fn new() -> Shell {
|
||||||
|
return Shell {
|
||||||
|
term: Term::new(),
|
||||||
|
ctx: parser::vars::Context {
|
||||||
|
scopes: Vec::new(),
|
||||||
|
parent_context: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
loop {
|
||||||
|
let mut shell = editor();
|
||||||
|
shell.term.input += "\n";
|
||||||
|
parser::exec(&mut shell.term.input.as_bytes(), shell.ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor() -> Shell {
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let mut stdout = io::stdout().into_raw_mode().unwrap();
|
let mut stdout = io::stdout().into_raw_mode().unwrap();
|
||||||
let ctx = parser::vars::Context {
|
let mut shell = Shell::new();
|
||||||
scopes: Vec::new(),
|
let mut should_execute = true;
|
||||||
parent_context: None
|
|
||||||
};
|
|
||||||
let mut output = String::new();
|
|
||||||
let mut idx = 0;
|
|
||||||
for c in stdin.keys() {
|
for c in stdin.keys() {
|
||||||
let c = c.unwrap();
|
let c = c.unwrap();
|
||||||
match c {
|
match c {
|
||||||
Key::Char('\n') => {
|
Key::Char('\n') => {
|
||||||
break;
|
if shell.term.input.chars().nth(shell.term.idx).unwrap_or(' ') == '\\' {
|
||||||
|
shell.term.insert_str(shell.term.idx, "\\\n");
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Key::Backspace => {
|
Key::Backspace => {
|
||||||
if output.len() > 0 && idx > 0 {
|
if shell.term.input.len() > 0 && shell.term.idx > 0 {
|
||||||
output.remove(idx - 1);
|
if shell.term.idx == shell.term.input.len() - 1 {
|
||||||
idx -= 1;
|
shell.term.input.pop();
|
||||||
print!("{}{} ", cursor::Left((output.len() - idx + 2).try_into().unwrap()), &output[idx..]);
|
} else {
|
||||||
|
shell.term.remove(shell.term.idx - 1);
|
||||||
|
}
|
||||||
|
shell.term.idx -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Key::Delete => {
|
Key::Delete => {
|
||||||
if idx < output.len() {
|
if shell.term.idx < shell.term.input.len() {
|
||||||
output.remove(idx);
|
shell.term.remove(shell.term.idx);
|
||||||
print!("{} {}", &output[idx..], cursor::Left((output.len() - idx + 1).try_into().unwrap()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Key::End => {
|
Key::End => {
|
||||||
let start = idx;
|
shell.term.idx = cmp::max(shell.term.input.len(), 1) - 1;
|
||||||
idx = cmp::max(output.len(), 1) - 1;
|
|
||||||
print!("{}", cursor::Right((idx - start).try_into().unwrap()));
|
|
||||||
}
|
}
|
||||||
Key::Home => {
|
Key::Home => {
|
||||||
let start = idx;
|
shell.term.idx = 0;
|
||||||
idx = 0;
|
|
||||||
print!("{}", cursor::Left(start.try_into().unwrap()));
|
|
||||||
}
|
}
|
||||||
Key::Left => {
|
Key::Left => {
|
||||||
if idx > 0 {
|
if shell.term.idx > 0 {
|
||||||
idx -= 1;
|
shell.term.idx -= 1;
|
||||||
}
|
}
|
||||||
print!("{}", cursor::Left(1));
|
|
||||||
}
|
}
|
||||||
Key::Right => {
|
Key::Right => {
|
||||||
if idx < output.len() - 1 {
|
if shell.term.idx < shell.term.input.len() - 1 {
|
||||||
idx += 1;
|
shell.term.idx += 1;
|
||||||
}
|
}
|
||||||
print!("{}", cursor::Right(1));
|
}
|
||||||
|
Key::Ctrl('c') => {
|
||||||
|
process::exit(1);
|
||||||
}
|
}
|
||||||
Key::Char(char) => {
|
Key::Char(char) => {
|
||||||
output.insert(idx, char);
|
shell.term.insert(shell.term.idx, char);
|
||||||
idx += 1;
|
shell.term.idx += 1;
|
||||||
print!("{}", char);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
shell.term.print(&mut stdout);
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
}
|
}
|
||||||
parser::read_parse(&mut output.as_bytes(), ctx).unwrap();
|
stdout.suspend_raw_mode();
|
||||||
|
shell
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
src/parser/ast.rs
Normal file
59
src/parser/ast.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::parser::vars::Variable;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LetExpression {
|
||||||
|
pub key: String,
|
||||||
|
pub value: Value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IfExpression {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub contents: Vec<Expression>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WhileExpression {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub contents: Vec<Expression>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ForExpression {
|
||||||
|
pub key: String,
|
||||||
|
pub list: Value,
|
||||||
|
pub contents: Vec<Expression>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ForValue {
|
||||||
|
Value(Value),
|
||||||
|
Range(Some(u32), Some(u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Value {
|
||||||
|
Literal(String),
|
||||||
|
Variable(Variable),
|
||||||
|
Expression(Expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FunctionExpression {
|
||||||
|
pub args: Vec<Value>,
|
||||||
|
pub body: Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RedirectTargetExpression {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Expression {
|
||||||
|
LetExpression(LetExpression),
|
||||||
|
Command(Vec<Value>),
|
||||||
|
Function(FunctionExpression),
|
||||||
|
IfExpression(IfExpression),
|
||||||
|
WhileExpression(WhileExpression),
|
||||||
|
ForExpression(ForExpression)
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,96 @@
|
||||||
pub mod vars;
|
pub mod vars;
|
||||||
|
pub mod ast;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
//use std::io::prelude::*;
|
//use std::io::prelude::*;
|
||||||
use utf8_chars::BufReadCharsExt;
|
use utf8_chars::BufReadCharsExt;
|
||||||
|
|
||||||
pub fn read_parse(reader: &mut dyn std::io::BufRead, ctx: vars::Context) -> io::Result<()> {
|
pub fn exec(reader: &mut dyn std::io::BufRead, ctx: vars::Context) -> io::Result<()> {
|
||||||
|
println!("got result");
|
||||||
|
let mut word = String::new();
|
||||||
|
let mut if_active = false;
|
||||||
|
let mut quote_active = false;
|
||||||
|
let mut double_quote_active = false;
|
||||||
|
let mut var_active = false;
|
||||||
|
let mut var_parens_active = false;
|
||||||
|
let mut command = String::new();
|
||||||
|
let mut args: Vec<String> = Vec::new();
|
||||||
|
let mut var_name = String::new();
|
||||||
|
let mut prev_char: Option<char> = None;
|
||||||
|
|
||||||
for c in reader.chars().map(|x| x.unwrap()) {
|
for c in reader.chars().map(|x| x.unwrap()) {
|
||||||
println!("char {}\r", c);
|
print!("{}\r", c);
|
||||||
|
// loop runs once, allows early exit
|
||||||
|
loop {
|
||||||
|
match c {
|
||||||
|
'$' => {
|
||||||
|
if quote_active {
|
||||||
|
word.push('$');
|
||||||
|
}
|
||||||
|
var_active = true;
|
||||||
|
}
|
||||||
|
'{' => {
|
||||||
|
if var_active {
|
||||||
|
var_parens_active = true;
|
||||||
|
} else {
|
||||||
|
word.push('{');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'}' => {
|
||||||
|
if !var_parens_active {
|
||||||
|
word.push('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' ' => {
|
||||||
|
if quote_active || double_quote_active {
|
||||||
|
word.push(' ');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if var_parens_active { break; }
|
||||||
|
if var_active {}
|
||||||
|
if command.len() == 0 {
|
||||||
|
if word == "if" {
|
||||||
|
if_active = true;
|
||||||
|
} else if word == "end" {
|
||||||
|
ctx.pop_scope();
|
||||||
|
} else {
|
||||||
|
command = word;
|
||||||
|
word = String::new();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args.push(word.clone());
|
||||||
|
word = String::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';' | '\n' => {
|
||||||
|
let mut arg_strings: Vec<&str> = Vec::new();
|
||||||
|
for arg in &args {
|
||||||
|
arg_strings.push(&*arg);
|
||||||
|
}
|
||||||
|
println!("Will execute {} with {}", command, arg_strings.join(" "));
|
||||||
|
if if_active {
|
||||||
|
println!("Will execute below only if the previous command succeeded");
|
||||||
|
}
|
||||||
|
if_active = false;
|
||||||
|
|
||||||
|
let exit_code = 0;
|
||||||
|
ctx.add_scope(exit_code == 0);
|
||||||
|
}
|
||||||
|
char => {
|
||||||
|
if var_active {
|
||||||
|
var_name.push(char);
|
||||||
|
} else {
|
||||||
|
word.push(char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev_char = Some(c);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn escape(str: String) -> String {
|
||||||
|
str
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Add, Deref};
|
||||||
|
use crate::parser::ast::FunctionExpression;
|
||||||
|
use crate::parser::escape;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Variable {
|
pub enum Variable {
|
||||||
|
|
@ -11,34 +14,90 @@ pub enum Variable {
|
||||||
U128(u128),
|
U128(u128),
|
||||||
F32(f32),
|
F32(f32),
|
||||||
F64(f64),
|
F64(f64),
|
||||||
hmap(HashMap<String, Variable>),
|
HMap(HashMap<String, Variable>),
|
||||||
array(Vec<Variable>)
|
Array(Vec<Variable>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variable {
|
||||||
|
pub fn to_string(self: &Self) -> String {
|
||||||
|
match self {
|
||||||
|
Variable::String(var) => String::from(var),
|
||||||
|
Variable::I32(num) => num.to_string(),
|
||||||
|
Variable::I64(num) => num.to_string(),
|
||||||
|
Variable::I128(num) => num.to_string(),
|
||||||
|
Variable::U32(num) => num.to_string(),
|
||||||
|
Variable::U64(num) => num.to_string(),
|
||||||
|
Variable::U128(num) => num.to_string(),
|
||||||
|
Variable::F32(num) => num.to_string(),
|
||||||
|
Variable::F64(num) => num.to_string(),
|
||||||
|
Variable::HMap(map) => {
|
||||||
|
String::from("[Object object]")
|
||||||
|
},
|
||||||
|
Variable::Array(vars) => {
|
||||||
|
let mut str = String::new();
|
||||||
|
for var in vars {
|
||||||
|
str += &*var.clone().to_string();
|
||||||
|
str += " ";
|
||||||
|
}
|
||||||
|
str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(self: &Self) -> &Variable {
|
||||||
|
match self {
|
||||||
|
_ => panic!("Cannot index unsupported types")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
active: bool,
|
pub active: bool,
|
||||||
vars: HashMap<String, Variable>
|
pub vars: HashMap<String, Variable>,
|
||||||
|
pub func: HashMap<String, FunctionExpression>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub scopes: Vec<Scope>,
|
pub scopes: Vec<Scope>,
|
||||||
pub parent_context: Option<Box<Context>>
|
pub exports: HashMap<String, String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new() -> Context {
|
pub fn new() -> Context {
|
||||||
Context {
|
Context {
|
||||||
scopes: Vec::new(),
|
scopes: Vec::new()
|
||||||
parent_context: None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add_scope(self: &mut Context, active: bool) {
|
pub fn pop_scope(self: &mut Self) -> Option<Scope> {
|
||||||
|
self.scopes.pop()
|
||||||
|
}
|
||||||
|
pub fn add_scope(self: &mut Self, active: bool) {
|
||||||
let scope = Scope {
|
let scope = Scope {
|
||||||
active: active,
|
active,
|
||||||
|
func: HashMap::new(),
|
||||||
vars: HashMap::new()
|
vars: HashMap::new()
|
||||||
};
|
};
|
||||||
self.scopes.push(scope);
|
self.scopes.push(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_var(self: &mut Self, var: &str) -> Option<&mut Variable> {
|
||||||
|
for mut scope in self.scopes.iter_mut().rev() {
|
||||||
|
let mut vars = &mut scope.vars;
|
||||||
|
let val = vars.get_mut(var);
|
||||||
|
match val {
|
||||||
|
None => {},
|
||||||
|
Some(val) => {
|
||||||
|
return Some(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_var(&mut self, key: String, val: Variable) {
|
||||||
|
let mut vars = &mut self.scopes.last_mut().unwrap().vars;
|
||||||
|
vars.insert(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
39
test/simple.rush
Normal file
39
test/simple.rush
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
echo test
|
||||||
|
|
||||||
|
echo single; echo line
|
||||||
|
|
||||||
|
echo test > cat
|
||||||
|
|
||||||
|
grep echo < simple.rush
|
||||||
|
|
||||||
|
let test = val
|
||||||
|
echo var $test
|
||||||
|
let test = $test
|
||||||
|
echo var2 ${test}
|
||||||
|
echo last exit code $?
|
||||||
|
|
||||||
|
if true
|
||||||
|
echo condition true
|
||||||
|
end
|
||||||
|
|
||||||
|
if false
|
||||||
|
echo condition false
|
||||||
|
end
|
||||||
|
|
||||||
|
if true
|
||||||
|
echo condition true
|
||||||
|
else
|
||||||
|
echo else
|
||||||
|
end
|
||||||
|
|
||||||
|
if false
|
||||||
|
echo condition false
|
||||||
|
else if true
|
||||||
|
echo condition else true
|
||||||
|
end
|
||||||
|
|
||||||
|
while true
|
||||||
|
echo loop
|
||||||
|
break
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue