From e093c11558dadfa4d8864db241a50d2e1c9da2f2 Mon Sep 17 00:00:00 2001 From: TudbuT Date: Mon, 6 Mar 2023 03:45:16 +0100 Subject: [PATCH] bug fixes, improve speed, add basic http support, other minor improvements --- http.spl | 77 ++++++++++++++++++++++++++ iter.spl | 4 +- spl.vim | 2 +- src/dyn_fns.rs | 26 ++++++++- src/lexer.rs | 15 +++++- src/runtime.rs | 106 +++++++++++++++++++++++++----------- src/std_fns.rs | 143 +++++++++++++++++++++++++++++++++++++------------ src/stream.rs | 47 +++++++++++++++- std.spl | 53 +++++++++--------- stream.spl | 17 +++++- test.spl | 25 +++++---- 11 files changed, 403 insertions(+), 112 deletions(-) create mode 100644 http.spl diff --git a/http.spl b/http.spl new file mode 100644 index 0000000..be89377 --- /dev/null +++ b/http.spl @@ -0,0 +1,77 @@ +"stream.spl" import + +construct http namespace { + Request + Response +} + +construct http:Request { + host port + method path + headers + body + ; + construct { this | with host port method path this ; + host this:=host + port this:=port + method this:=method + path this:=path + List:new this:=headers + "" this:=body + this + } + add-header { this | with header this ; + header this:headers:push + this + } + set-body { this | with body this ; + body this:=body + this + } + send { http:Response | with this ; + def stream this:host this:port StreamTypes:tcp:create =stream + def response http:Response:new =response + + this:method:to-bytes stream:write-exact; + " " _:to-bytes stream:write-exact; + this:path:to-bytes stream:write-exact; + " HTTP/1.0\r\n" _:to-bytes stream:write-exact; + + "Host: " _:to-bytes stream:write-exact; + this:host:to-bytes stream:write-exact; + "\r\nConnection: Close\r\nUser-Agent: http.spl v0.1 2023-03 (spl@mail.tudbut.de)\r\n" + _:to-bytes stream:write-exact; + + { | with header ; + header:to-bytes stream:write-exact; + "\r\n" stream:write-exact; + } this:headers:foreach + + "Content-Length: " _:to-bytes stream:write-exact; + def body this:body:to-bytes =body + body:len _str:to-bytes stream:write-exact; + "\r\n\r\n" _:to-bytes stream:write-exact; + + body stream:write-exact; + stream:flush; + + 1024 stream:read-to-end:to-str println + + stream:close; + + "todo" panic + } +} + +construct http:Response { + state-num state-msg + headers + body + ; + construct { this | with this ; + List:new this:=headers + "" this:=body + this + } +} + diff --git a/iter.spl b/iter.spl index 3b693ec..e1e8b8a 100644 --- a/iter.spl +++ b/iter.spl @@ -101,7 +101,7 @@ construct ReduceIter { } =itm this:accumulator null eq if { itm dup this:=accumulator - null 2 stop + 2 stop } this:accumulator itm this:reduce-function call dup this:=accumulator } @@ -165,7 +165,7 @@ construct ChainIter { next-iters ; construct { this | with other origin this ; - [ other ] List:new this:=next-iters + [ other ] List:new:from this:=next-iters origin this:=current this } diff --git a/spl.vim b/spl.vim index 2d3ac7d..5dbf8a2 100644 --- a/spl.vim +++ b/spl.vim @@ -6,7 +6,7 @@ endif syn match Comment /".*?";/ syn match Number /\<[0-9._]*\>/ syn match Function /\/ diff --git a/src/dyn_fns.rs b/src/dyn_fns.rs index 11c5743..7168d7b 100644 --- a/src/dyn_fns.rs +++ b/src/dyn_fns.rs @@ -39,7 +39,28 @@ pub fn dyn_construct(stack: &mut Stack) -> OError { return stack.err(ErrorKind::InvalidCall("dyn-construct".to_owned())) }; Words { - words: vec![Word::Key(Keyword::Construct(s, Vec::new(), Vec::new()))], + words: vec![Word::Key(Keyword::Construct( + s, + Vec::new(), + Vec::new(), + false, + ))], + } + .exec(stack)?; + Ok(()) +} + +pub fn dyn_namespace(stack: &mut Stack) -> OError { + let Value::Str(s) = stack.pop().lock_ro().native.clone() else { + return stack.err(ErrorKind::InvalidCall("dyn-construct".to_owned())) + }; + Words { + words: vec![Word::Key(Keyword::Construct( + s, + Vec::new(), + Vec::new(), + true, + ))], } .exec(stack)?; Ok(()) @@ -245,11 +266,12 @@ pub(crate) fn wrap(f: fn(&mut Stack) -> OError) -> impl Fn(&mut Stack) -> OError pub fn register(r: &mut Stack, o: Arc) { type Fn = fn(&mut Stack) -> OError; - let fns: [(&str, Fn, u32); 14] = [ + let fns: [(&str, Fn, u32); 15] = [ ("dyn-__dump", dyn_dump, 0), ("dyn-def", dyn_def, 0), ("dyn-func", dyn_func, 0), ("dyn-construct", dyn_construct, 0), + ("dyn-namespace", dyn_namespace, 0), ("dyn-def-field", dyn_def_field, 0), ("dyn-def-method", dyn_def_method, 0), ("dyn-include", dyn_include, 0), diff --git a/src/lexer.rs b/src/lexer.rs index c8c9e38..dfc3006 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -64,6 +64,12 @@ fn read_block(str_words: &[String], isfn: bool) -> Result<(Option, Words, u } "construct" => { let name = str_words[i + 1].to_owned(); + let is_namespace = if str_words[i + 2] == "namespace" { + i += 1; + true + } else { + false + }; if str_words[i + 2] != "{" { return Err(LexerError::InvalidConstructBlock); } @@ -91,10 +97,15 @@ fn read_block(str_words: &[String], isfn: bool) -> Result<(Option, Words, u i += 1; } } - if !has_construct { + if !has_construct && !is_namespace { methods.push(("construct".to_string(), (1, Words { words: vec![] }))); } - words.push(Word::Key(Keyword::Construct(name, fields, methods))); + words.push(Word::Key(Keyword::Construct( + name, + fields, + methods, + is_namespace, + ))); } "include" => { if let Some(x) = readf( diff --git a/src/runtime.rs b/src/runtime.rs index db8f81b..f95d6fb 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -360,6 +360,7 @@ impl Frame { pub struct Stack { frames: Vec>, object_stack: Vec, + files: Vec, pub return_accumultor: u32, } @@ -394,6 +395,7 @@ impl Stack { let mut r = Stack { frames: vec![o.clone()], object_stack: Vec::new(), + files: Vec::new(), return_accumultor: 0, }; @@ -409,6 +411,7 @@ impl Stack { let mut r = Stack { frames: vec![o.clone()], object_stack: Vec::new(), + files: Vec::new(), return_accumultor: 0, }; @@ -591,6 +594,15 @@ impl Stack { pub unsafe fn push_frame(&mut self, frame: Arc) { self.frames.push(frame); } + + pub(crate) fn include_file(&mut self, s: &String) -> bool { + if self.files.contains(s) { + false + } else { + self.files.push(s.to_owned()); + true + } + } } /// An SPL keyword. Used to deviate from normal linear code structure. @@ -621,7 +633,7 @@ pub enum Keyword { /// equivalent to /// "" dyn-construct; "" "" dyn-def-field { | } "" /// "" dyn-def-method - Construct(String, Vec, Vec<(String, (u32, Words))>), + Construct(String, Vec, Vec<(String, (u32, Words))>, bool), /// include in /// /// Adds as a parent type of . @@ -949,6 +961,19 @@ impl Object { Value::Str(x) => !x.is_empty(), } } + + pub fn field(&self, name: &str, stack: &mut Stack) -> Result { + Ok(self + .property_map + .get(name) + .ok_or_else(|| { + stack.error(ErrorKind::PropertyNotFound( + self.kind.lock_ro().name.to_owned(), + name.to_owned(), + )) + })? + .clone()) + } } impl From for Object { @@ -1025,38 +1050,55 @@ impl Words { name, }), ), - Keyword::Construct(name, fields, methods) => { + Keyword::Construct(name, fields, methods, is_namespace) => { let origin = stack.get_frame(); - stack.define_var(name.clone()); - stack.set_var( - name.clone(), - Value::Str( - runtime_mut(move |mut rt| { - rt.make_type(name.clone(), move |mut t| { - for field in fields { - t.add_property(field, origin.clone())?; - } - t.functions.extend(methods.into_iter().map(|(k, v)| { - ( - k.clone(), - Arc::new(Func { - ret_count: v.0, - to_call: FuncImpl::SPL(v.1), - origin: origin.clone(), - run_as_base: false, - fname: None, - name: name.clone() + ":" + &k, - }), - ) - })); - Ok(t) - }) - })? - .lock_ro() - .get_name(), - ) - .spl(), - )?; + if !name.contains(':') { + stack.define_var(name.clone()); + } + let t = runtime_mut(|mut rt| { + rt.make_type(name.clone(), |mut t| { + for field in fields { + t.add_property(field, origin.clone())?; + } + t.functions.extend(methods.into_iter().map(|(k, v)| { + ( + k.clone(), + Arc::new(Func { + ret_count: v.0, + to_call: FuncImpl::SPL(v.1), + origin: origin.clone(), + run_as_base: false, + fname: None, + name: name.clone() + ":" + &k, + }), + ) + })); + Ok(t) + }) + })?; + + let to_set: Object = if is_namespace { + let mut obj: Object = Value::Null.into(); + obj.kind = t.clone(); + t.lock_ro().write_into(&mut obj); + obj + } else { + Value::Str(t.lock_ro().get_name()).into() + }; + if name.contains(':') { + let Some((a, mut name)) = name.split_once(':') else { unreachable!() }; + let mut f = stack.get_var(a.to_owned())?; + while let Some((a, b)) = name.split_once(':') { + name = b; + let o = f.lock_ro(); + let nf = o.field(a, stack)?; + mem::drop(o); + f = nf; + } + *f.lock_ro().field(name, stack)?.lock() = to_set; + } else { + stack.set_var(name.clone(), to_set.spl())?; + } } Keyword::Include(ta, tb) => { let rstack = &stack; diff --git a/src/std_fns.rs b/src/std_fns.rs index 0b64bfc..e9f1ea1 100644 --- a/src/std_fns.rs +++ b/src/std_fns.rs @@ -4,11 +4,17 @@ use std::{ fs, io::{stdin, stdout, Write}, mem, + ops::{Add, Div, Mul, Rem, Sub}, process::{self, Stdio}, sync::Arc, }; -use crate::{dyn_fns, mutex::Mut, runtime::*, *}; +use crate::{ + dyn_fns, + mutex::Mut, + runtime::*, + *, +}; #[macro_export] macro_rules! type_err { @@ -185,14 +191,30 @@ pub fn or(stack: &mut Stack) -> OError { Ok(()) } +macro_rules! impl_op { + ($a:expr, $b:expr, $op:tt, $err:expr, $($kind:tt,)*) => { + match ($a, $b) { + $( + (Value::$kind(a), Value::$kind(b)) => Value::$kind(a.$op(b)), + )* + _ => $err?, + } + }; +} + pub fn plus(stack: &mut Stack) -> OError { let b = stack.pop().lock_ro().native.clone(); let a = stack.pop().lock_ro().native.clone(); stack.push( - match (a, b) { - (Value::Mega(a), Value::Mega(b)) => Value::Mega(a + b), - _x => stack.err(ErrorKind::InvalidCall("plus".to_owned()))?, - } + impl_op!( + a, + b, + add, + stack.err(ErrorKind::InvalidCall("plus".to_owned())), + Mega, + Long, + Int, + ) .spl(), ); Ok(()) @@ -202,10 +224,15 @@ pub fn minus(stack: &mut Stack) -> OError { let b = stack.pop().lock_ro().native.clone(); let a = stack.pop().lock_ro().native.clone(); stack.push( - match (a, b) { - (Value::Mega(a), Value::Mega(b)) => Value::Mega(a - b), - _ => todo!(), - } + impl_op!( + a, + b, + sub, + stack.err(ErrorKind::InvalidCall("minus".to_owned())), + Mega, + Long, + Int, + ) .spl(), ); Ok(()) @@ -215,10 +242,15 @@ pub fn slash(stack: &mut Stack) -> OError { let b = stack.pop().lock_ro().native.clone(); let a = stack.pop().lock_ro().native.clone(); stack.push( - match (a, b) { - (Value::Mega(a), Value::Mega(b)) => Value::Mega(a / b), - _ => todo!(), - } + impl_op!( + a, + b, + div, + stack.err(ErrorKind::InvalidCall("slash".to_owned())), + Mega, + Long, + Int, + ) .spl(), ); Ok(()) @@ -228,10 +260,15 @@ pub fn star(stack: &mut Stack) -> OError { let b = stack.pop().lock_ro().native.clone(); let a = stack.pop().lock_ro().native.clone(); stack.push( - match (a, b) { - (Value::Mega(a), Value::Mega(b)) => Value::Mega(a * b), - _ => todo!(), - } + impl_op!( + a, + b, + mul, + stack.err(ErrorKind::InvalidCall("star".to_owned())), + Mega, + Long, + Int, + ) .spl(), ); Ok(()) @@ -241,10 +278,15 @@ pub fn percent(stack: &mut Stack) -> OError { let b = stack.pop().lock_ro().native.clone(); let a = stack.pop().lock_ro().native.clone(); stack.push( - match (a, b) { - (Value::Mega(a), Value::Mega(b)) => Value::Mega(a % b), - _ => todo!(), - } + impl_op!( + a, + b, + rem, + stack.err(ErrorKind::InvalidCall("star".to_owned())), + Mega, + Long, + Int, + ) .spl(), ); Ok(()) @@ -589,18 +631,26 @@ pub fn import(stack: &mut Stack) -> OError { + "/" + &s; } - stack.push(Value::Str(s).spl()); - dup(stack)?; - read_file(stack).or_else(|x| { - if let Some(fallback) = fallback { - stack.push(Value::Str(fallback.to_owned()).spl()); - Ok(()) - } else { - Err(x) - } - })?; - dyn_fns::wrap(dyn_fns::dyn_readf)(stack)?; - call(stack)?; + if stack.include_file( + &(*fs::canonicalize(s.clone()) + .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))? + .as_os_str() + .to_string_lossy()) + .to_owned(), + ) { + stack.push(Value::Str(s).spl()); + dup(stack)?; + read_file(stack).or_else(|x| { + if let Some(fallback) = fallback { + stack.push(Value::Str(fallback.to_owned()).spl()); + Ok(()) + } else { + Err(x) + } + })?; + dyn_fns::wrap(dyn_fns::dyn_readf)(stack)?; + call(stack)?; + } Ok(()) } @@ -710,9 +760,33 @@ pub fn bytes_to_str(stack: &mut Stack) -> OError { Ok(()) } +pub fn acopy(stack: &mut Stack) -> OError { + require_on_stack!(len, Mega, stack, "acopy"); + require_on_stack!(idx_dest, Mega, stack, "acopy"); + require_on_stack!(idx_src, Mega, stack, "acopy"); + let dest_array = stack.pop(); + { + require_mut_array!(dest, dest_array, stack, "acopy"); + require_array_on_stack!(src, stack, "acopy"); + let offset = idx_dest - idx_src; + if (src.len() as i128) < idx_src + len + || idx_src < 0 + || (dest.len() as i128) < idx_dest + len + || idx_dest < 0 + { + stack.err(ErrorKind::InvalidCall("acopy".to_owned()))?; + } + for i in idx_src..idx_src + len { + *dest.get_mut((i + offset) as usize).unwrap() = src.get(i as usize).unwrap().clone(); + } + } + stack.push(dest_array); + Ok(()) +} + pub fn register(r: &mut Stack, o: Arc) { type Fn = fn(&mut Stack) -> OError; - let fns: [(&str, Fn, u32); 48] = [ + let fns: [(&str, Fn, u32); 49] = [ ("pop", pop, 0), ("dup", dup, 2), ("clone", clone, 1), @@ -761,6 +835,7 @@ pub fn register(r: &mut Stack, o: Arc) { ("command-wait", command_wait, 1), ("str-to-bytes", str_to_bytes, 1), ("bytes-to-str", bytes_to_str, 1), + ("acopy", acopy, 1), ]; for f in fns { r.define_func( diff --git a/src/stream.rs b/src/stream.rs index edc5327..bb3fa3d 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -4,7 +4,7 @@ use std::{ io::Read, io::Write, mem, - net::{Shutdown, TcpStream}, + net::{Shutdown, TcpStream, UdpSocket}, sync::Arc, }; @@ -187,6 +187,19 @@ pub fn write_all_stream(stack: &mut Stack) -> OError { Ok(()) } +pub fn flush_stream(stack: &mut Stack) -> OError { + require_on_stack!(id, Mega, stack, "flush-stream"); + let stream = runtime(|rt| { + rt.get_stream(id as u128) + .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) + })?; + stream + .lock() + .flush() + .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; + Ok(()) +} + pub fn read_stream(stack: &mut Stack) -> OError { require_on_stack!(id, Mega, stack, "read-stream"); let array = stack.pop(); @@ -285,18 +298,48 @@ fn stream_tcp(stack: &mut Stack) -> Result { )) } +fn stream_udp(stack: &mut Stack) -> Result { + require_int_on_stack!(port, stack, "UDP new-stream"); + require_on_stack!(ip, Str, stack, "UDP new-stream"); + require_int_on_stack!(self_port, stack, "UDP new-stream"); + require_on_stack!(self_ip, Str, stack, "UDP new-stream"); + fn close_udp(_stream: &mut Stream) {} + let sock = UdpSocket::bind((self_ip, self_port as u16)) + .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; + sock.connect((ip, port as u16)) + .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; + struct UdpRW(UdpSocket); + impl Write for UdpRW { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.send(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + impl Read for UdpRW { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.recv(buf) + } + } + Ok(Stream::new(UdpRW(sock), close_udp)) +} + pub fn register(r: &mut Stack, o: Arc) { if !*IS_INITIALIZED.lock_ro() { register_stream_type("file", stream_file); register_stream_type("tcp", stream_tcp); + register_stream_type("udp", stream_udp); *IS_INITIALIZED.lock() = true; } type Fn = fn(&mut Stack) -> OError; - let fns: [(&str, Fn, u32); 6] = [ + let fns: [(&str, Fn, u32); 7] = [ ("new-stream", new_stream, 1), ("write-stream", write_stream, 1), ("write-all-stream", write_all_stream, 0), + ("flush-stream", flush_stream, 0), ("read-stream", read_stream, 1), ("read-all-stream", read_all_stream, 0), ("close-stream", close_stream, 0), diff --git a/std.spl b/std.spl index b75dd12..bbe7d0b 100644 --- a/std.spl +++ b/std.spl @@ -11,7 +11,7 @@ func println { | construct _str_ext { ; new { any | with this ; - null clone this settype "construct" dyn-objcall + null clone this settype:construct } to-bytes { [int] | str-to-bytes } } include _str_ext in str @@ -56,6 +56,9 @@ construct _array-ext { while { i this:len lt } { i this:get callable call i ++ =i } } to-str { str | bytes-to-str } + sub { [any] | with begin end this ; + this (end begin - anew) begin 0 (end begin -) acopy + } 0 { any | with this ; 0 this:get } @@ -78,14 +81,22 @@ construct _array-ext { construct List { array ; - construct { this | with array this ; + construct { this | with this ; + 0 anew this:=array + this + } + from { this | with array this ; array this:=array this } + foreach { | _:array:foreach } get { any | _:array:get } sget { any|null | _:array:sget } len { mega | _:array:len } set { any | _:array:set } + to-stack { .. | _:array:to-stack } + to-str { str | _:array:to-str } + sub { [any] | _:array:sub } } construct _GrowingArray { ; @@ -159,11 +170,12 @@ include _IterableArray in array construct MicroMap { pairs ; - construct { this | with pairs this ; - pairs null eq if { - 0 anew List:new =pairs - } - pairs:unwrap this:=pairs + construct { this | with this ; + List:new this:=pairs + this + } + from { this | with pairs this ; + pairs this:=pairs this } get-entry { [any,any]|null | with key this ; @@ -189,12 +201,15 @@ construct MicroMap { this:pairs:iter { mega | 0 swap:get key eq not } swap:filter _:collect - List:new + List:new:from =pairs } iter { ArrayIter | with this ; this:pairs:iter } + foreach { | with callable this ; + callable this:pairs:foreach + } } construct Range { @@ -246,24 +261,12 @@ include _Iter in RangeIter construct shadow { } -"Copy array"; -func acopy { array | with arr1 arr2 idx1 idx2 len ; - - def i 0 =i - while { i len lt } { - (( i idx1 + ) arr1:get) (i idx2 +) arr2:set; - i ++ =i - } - - arr2 -} - func aadd { array | with arr1 arr2 ; def newarr arr1:len arr2:len + anew =newarr - arr1 newarr 0 0 arr1:len acopy =newarr - arr2 newarr 0 arr1:len arr2:len acopy =newarr + arr1 newarr 0 0 arr1:len acopy; + arr2 newarr 0 arr1:len arr2:len acopy; newarr } @@ -281,8 +284,8 @@ func panic { | with msg ; { | with it ; it println } trace:foreach - "Panic message:" println - " " print msg println + "\nPanic message:" println + " " print msg println def map env =map "SPL_PANIC_DUMP" env:get dup if { "Dumping because SPL_PANIC_DUMP is set." println @@ -332,7 +335,7 @@ func ] { array | } func env { MicroMap | - get-env List:new MicroMap:new + get-env List:new:from MicroMap:new:from } func ++ { mega | diff --git a/stream.spl b/stream.spl index 90e0418..ff65be3 100644 --- a/stream.spl +++ b/stream.spl @@ -21,20 +21,32 @@ construct Stream { } "the buffer is written to in-place."; read { mega [int] | with buf this ; - buf gettype "int" eq if { buf anew =buf } + buf gettype "mega" eq if { buf anew =buf } buf this:id read-stream buf } "the buffer is written to in-place."; read-exact { [int] | with buf this ; - buf gettype "int" eq if { buf anew =buf } + buf gettype "mega" eq if { buf anew =buf } buf this:id read-all-stream buf } + read-to-end { [int] | with buf this ; + def full 0 anew =full + buf gettype "mega" eq if { buf anew =buf } + def read + while { buf this:id read-stream pop _mega dup =read } { + full (0 read buf:sub) aadd =full + } + full + } write { mega | with buf this ; buf this:id write-stream } write-exact { | with buf this ; buf this:id write-all-stream } + flush { | with this ; + this:id flush-stream + } close { | with this ; this:id close-stream } @@ -71,6 +83,7 @@ func register-stream-type { | with id ; } "tcp" register-stream-type +"udp" register-stream-type "file" register-stream-type func StreamTypes { _StreamType | diff --git a/test.spl b/test.spl index aa77d5a..db868e0 100644 --- a/test.spl +++ b/test.spl @@ -1,5 +1,6 @@ "stream.spl" import +"http.spl" import func main { int | with args ; def thing @@ -8,7 +9,7 @@ func main { int | with args ; "hi" 0 thing:unwrap:set; - def thing2 thing:unwrap List:new =thing2 + def thing2 thing:unwrap List:new:from =thing2 "world" thing2:unwrap:push "hello" 0 thing2:unwrap:insert @@ -80,18 +81,17 @@ func main { int | with args ; "" println "testing MicroMap" println - def map null MicroMap:new =map + def map MicroMap:new =map "hey" "hello" map:set; "helloworld" "Hello, World" map:set; "{ " print - map:iter - { | with item ; - "'" print - 0 item:get print - "': '" print - 1 item:get print - "', " print - } swap:foreach + { | with item ; + "'" print + 0 item:get print + "': '" print + 1 item:get print + "', " print + } map:foreach "}" println "" println @@ -109,5 +109,10 @@ func main { int | with args ; "" println + "testing http" println + def req "tudbut.de" 80 "GET" "/" http:Request:new =req + req:send + "" println + 100 }