Skip to content

Instantly share code, notes, and snippets.

@mbillingr
Last active February 13, 2019 16:09
Show Gist options
  • Save mbillingr/01ab259ec0a1e5bb8142b77afeb36cfb to your computer and use it in GitHub Desktop.
Save mbillingr/01ab259ec0a1e5bb8142b77afeb36cfb to your computer and use it in GitHub Desktop.
(WIP) A simple but powerful Forth dialect written in Rust.
const INLINE_THRESHOLD: usize = 32; // inline words up to a length of 32 commands
type Stack = Vec<Data>;
#[derive(Debug, Copy, Clone, PartialEq)]
struct WordId(usize);
type Result<T> = std::result::Result<T, ForthError>;
#[derive(Debug)]
enum ForthError {
NotInCompileMode,
EmptyStack,
UnknownWord(String),
ParseError,
MathError,
TypeError,
BoundsError,
}
#[derive(Debug, Clone)]
enum Data {
True,
False,
I32(i32),
String(String),
Block(Vec<Command>),
WordId(WordId),
}
impl std::fmt::Display for Data {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Data::True => write!(f, "true"),
Data::False => write!(f, "false"),
Data::I32(i) => write!(f, "{}", i),
Data::String(s) => write!(f, "\"{}\"", s),
Data::Block(ops) => write!(f, "[{:?}]", ops),
Data::WordId(id) => write!(f, "{:?}", id),
}
}
}
impl std::ops::Add for Data {
type Output = Result<Data>;
fn add(self, other: Data) -> Result<Data> {
match (self, other) {
(Data::I32(a), Data::I32(b)) => {
a.checked_add(b).map(Data::I32).ok_or(ForthError::MathError)
}
(Data::Block(mut a), Data::Block(b)) => {
a.extend(b);
Ok(a.into_data())
}
(Data::String(mut a), Data::String(b)) => {
a += &b;
Ok(a.into_data())
}
_ => Err(ForthError::TypeError),
}
}
}
impl std::ops::Sub for Data {
type Output = Result<Data>;
fn sub(self, other: Data) -> Result<Data> {
match (self, other) {
(Data::I32(a), Data::I32(b)) => {
a.checked_sub(b).map(Data::I32).ok_or(ForthError::MathError)
}
_ => Err(ForthError::TypeError),
}
}
}
impl std::ops::Mul for Data {
type Output = Result<Data>;
fn mul(self, other: Data) -> Result<Data> {
match (self, other) {
(Data::I32(a), Data::I32(b)) => {
a.checked_mul(b).map(Data::I32).ok_or(ForthError::MathError)
}
_ => Err(ForthError::TypeError),
}
}
}
impl std::ops::Div for Data {
type Output = Result<Data>;
fn div(self, other: Data) -> Result<Data> {
match (self, other) {
(Data::I32(a), Data::I32(b)) => {
a.checked_div(b).map(Data::I32).ok_or(ForthError::MathError)
}
_ => Err(ForthError::TypeError),
}
}
}
impl std::ops::Rem for Data {
type Output = Result<Data>;
fn rem(self, other: Data) -> Result<Data> {
match (self, other) {
(Data::I32(a), Data::I32(b)) => {
a.checked_rem(b).map(Data::I32).ok_or(ForthError::MathError)
}
_ => Err(ForthError::TypeError),
}
}
}
impl Data {
fn exec(self, mut forth: Forth) -> Result<Forth> {
match self {
Data::Block(ops) => forth.exec_block(&ops),
Data::WordId(id) => {
let block = forth.words[id.0].ops.clone();
forth.exec_block(&block)
}
_ => Err(ForthError::TypeError),
}
}
fn call(self, mut forth: Forth) -> Result<Forth> {
match self {
Data::Block(ops) => forth.exec_block(&ops),
Data::WordId(id) => forth.call_wordid(id),
_ => Err(ForthError::TypeError),
}
}
fn into_bool(self) -> Result<bool> {
match self {
Data::True => Ok(true),
Data::False => Ok(false),
_ => Err(ForthError::TypeError),
}
}
fn into_i32(self) -> Result<i32> {
match self {
Data::I32(i) => Ok(i),
_ => Err(ForthError::TypeError),
}
}
fn into_usize(self) -> Result<usize> {
match self {
Data::I32(i) if i >= 0 => Ok(i as usize),
_ => Err(ForthError::TypeError),
}
}
fn into_char(self) -> Result<char> {
match self {
Data::I32(i) if i >= 0 && i <= 255 => Ok(i as u8 as char),
_ => Err(ForthError::TypeError),
}
}
fn into_string(self) -> Result<String> {
match self {
Data::String(s) => Ok(s),
_ => Err(ForthError::TypeError),
}
}
fn into_block(self) -> Result<Vec<Command>> {
match self {
Data::Block(block) => Ok(block),
_ => Err(ForthError::TypeError),
}
}
fn into_wordid(self) -> Result<WordId> {
match self {
Data::WordId(id) => Ok(id),
_ => Err(ForthError::TypeError),
}
}
}
trait IntoData {
fn into_data(self) -> Data;
}
impl IntoData for Data {
fn into_data(self) -> Data {
self
}
}
impl IntoData for bool {
fn into_data(self) -> Data {
if self {
Data::True
} else {
Data::False
}
}
}
impl IntoData for i32 {
fn into_data(self) -> Data {
Data::I32(self)
}
}
impl IntoData for Vec<Command> {
fn into_data(self) -> Data {
Data::Block(self)
}
}
impl IntoData for WordId {
fn into_data(self) -> Data {
Data::WordId(self)
}
}
impl IntoData for String {
fn into_data(self) -> Data {
Data::String(self)
}
}
#[derive(Clone)]
enum Command {
Literal(Data),
Builtin(String, fn(Forth) -> Result<Forth>),
Call(WordId),
}
impl Command {
fn exec(&self, mut forth: Forth) -> Result<Forth> {
match self {
Command::Literal(x) => forth.stack.push(x.clone()),
Command::Builtin(_, func) => return func(forth),
Command::Call(id) => return forth.call_wordid(*id),
}
Ok(forth)
}
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Command::Literal(x) => write!(f, "{:?}", x),
Command::Builtin(name, _) => write!(f, "{}", name),
Command::Call(id) => write!(f, "@{}", id.0),
}
}
}
struct Word {
name: String,
comment: String,
ops: Vec<Command>,
class: Option<WordId>,
}
impl Word {
fn new(name: &str, comment: &str, ops: Vec<Command>, class: Option<WordId>) -> Self {
Word {
name: name.to_string(),
comment: comment.to_string(),
ops,
class,
}
}
}
#[derive(Debug)]
enum Mode {
Execute,
Compile(Vec<Command>),
}
struct Forth {
mode: Vec<Mode>,
stack: Stack,
words: Vec<Word>,
}
impl Forth {
fn new() -> Self {
let forth = Forth {
stack: vec![],
words: vec![],
mode: vec![Mode::Execute],
};
forth.populate_words().unwrap()
}
fn populate_words(mut self) -> Result<Self> {
//
// Tier 0
//
// prefix handlers
self.add_builtin("prefix::", "new word", |mut forth| {
//println!("{:?}", forth.stack);
let default_class = forth.try_lookup("class:default")?;
forth.push(Data::WordId(default_class));
forth = forth.call()?;
let class = forth.pop()?.into_wordid()?;
let name = forth.pop()?.into_string()?;
let ops = forth.pop()?.into_block()?;
forth.words.push(
WordBuilder::new()
.with_name_string(name)
.with_class(class)
.with_ops(ops)
.build(),
);
Ok(forth)
});
self.add_builtin("prefix:\"", "string literal", |mut forth| {
let mut s = forth.pop()?.into_string()?;
match s.pop() {
Some('"') => {}
_ => return Err(ForthError::ParseError),
}
match forth.mode.last_mut().unwrap() {
Mode::Execute => forth.push(Data::String(s)),
Mode::Compile(ref mut target_ops) => {
target_ops.push(Command::Literal(Data::String(s)))
}
}
Ok(forth)
});
self.add_builtin("prefix:(", "comment", |mut forth| {
let mut s = forth.pop()?.into_string()?;
match s.pop() {
Some(')') => {}
_ => return Err(ForthError::ParseError),
}
match forth.mode.last_mut().unwrap() {
Mode::Execute => {},
Mode::Compile(ref mut target_ops) => {}
}
Ok(forth)
});
// word classes
self.add_builtin("class:macro", "class handler", |forth| {
// expect word id on stack
match forth.mode.last().unwrap() {
Mode::Execute => forth.exec(),
Mode::Compile(_) => forth.exec(),
}
});
self.add_builtin("class:word", "class handler", |forth| {
// expect word id on stack
match forth.mode.last().unwrap() {
Mode::Execute => forth.exec(),
Mode::Compile(_) => forth.compile(),
}
});
self.add_word(
WordBuilder::new()
.with_name("class:default")
.with_class(self.lookup("class:macro").unwrap())
.with_comment("default class")
.with_ops(vec![Command::Literal(Data::WordId(
self.lookup("class:word").unwrap(),
))])
.build(),
);
// system commands
self.add_word(
WordBuilder::new()
.with_name("[")
.with_comment("begin block")
.with_class(self.try_lookup("class:macro")?)
.builtin(|mut forth| {
//println!("entering compile mode");
forth.mode.push(Mode::Compile(vec![]));
Ok(forth)
}),
);
self.add_word(
WordBuilder::new()
.with_name("]")
.with_comment("finish block")
.with_class(self.try_lookup("class:macro")?)
.builtin(|mut forth| {
//println!("leaving compile mode");
if let Mode::Compile(ops) = forth.mode.pop().unwrap() {
match forth.mode.last_mut().unwrap() {
Mode::Execute => forth.push(Data::Block(ops)),
Mode::Compile(ref mut target_ops) => {
target_ops.push(Command::Literal(Data::Block(ops)))
}
}
Ok(forth)
} else {
Err(ForthError::NotInCompileMode)
}
}),
);
self.add_builtin("lookup", "s -- (w) b", |mut forth| {
let name = forth.pop()?.into_string()?;
match forth.lookup(&name) {
None => forth.push(Data::False),
Some(id) => {
forth.push(id);
forth.push(Data::True);
}
}
Ok(forth)
});
self.add_builtin("handle_class", "w -- ?", Forth::handle_class);
self.add_builtin("handle_literal", "s -- x", Forth::handle_literal);
self.add_macro("process_token", "", Forth::process_token);
// dictionary and word commands
// debug, info, and i/o commands
self.add_builtin(".", "x --", |mut forth| {
println!("{}", forth.pop()?);
Ok(forth)
});
self.add_builtin(".s", "--", |forth| {
println!("{:?}", forth.stack);
Ok(forth)
});
self.add_builtin("emit", "ch --", |mut forth| {
let ch = forth.pop()?.into_char()?;
print!("{:?}", ch);
Ok(forth)
});
self.add_builtin("spaces", "n --", |mut forth| {
let n = forth.pop()?.into_i32()?;
for _ in 0..n {
print!(" ");
}
Ok(forth)
});
// stack operations
self.add_builtin("drop", "x --", |mut forth| forth.pop().map(|_| forth));
self.add_builtin("dup", "x -- x x", |mut forth| {
let x = forth.pop()?;
forth.push(x.clone());
forth.push(x);
Ok(forth)
});
self.add_builtin("swap", "x1 x2 -- x2 x1", |mut forth| {
let b = forth.pop()?;
let a = forth.pop()?;
forth.push(b);
forth.push(a);
Ok(forth)
});
self.add_builtin("rot", "x1 x2 x3 -- x2 x3 x1", |mut forth| {
let c = forth.pop()?;
let b = forth.pop()?;
let a = forth.pop()?;
forth.push(b);
forth.push(c);
forth.push(a);
Ok(forth)
});
self.add_builtin("n_dup", "n -- x", |mut forth| {
let n = forth.pop()?.into_usize()?;
let i = forth
.stack
.len()
.checked_sub(n)
.ok_or(ForthError::BoundsError)?;
let x = forth.stack[i].clone();
forth.push(x);
Ok(forth)
});
// branching
self.add_builtin("call", "w -- ?", Forth::call);
self.add_builtin("if", "b f1 f2 -- ?", |mut forth| {
let else_branch = forth.pop()?;
let if_branch = forth.pop()?;
let cond = forth.pop()?.into_bool()?;
let branch = if cond { if_branch } else { else_branch };
branch.call(forth)
});
// math operations
self.add_builtin("+", "a b -- sum", |mut forth| {
let b = forth.pop()?;
let a = forth.pop()?;
forth.push((a + b)?);
Ok(forth)
});
self.add_builtin("-", "a b -- sub", |mut forth| {
let b = forth.pop()?;
let a = forth.pop()?;
forth.push((a - b)?);
Ok(forth)
});
self.add_builtin("*", "a b -- prod", |mut forth| {
let b = forth.pop()?;
let a = forth.pop()?;
forth.push((a * b)?);
Ok(forth)
});
self.add_builtin("/", "a b -- quot", |mut forth| {
let b = forth.pop()?;
let a = forth.pop()?;
forth.push((a / b)?);
Ok(forth)
});
// string operations
self.add_builtin("str:split_at", "s n -- s s", |mut forth| {
let n = forth.pop()?.into_usize()?;
let s = forth.pop()?.into_string()?;
match s.char_indices().skip(n).next() {
None => forth.push2(s, String::new()),
Some((i, _)) => {
let (a, b) = s.split_at(i);
forth.push2(a.to_string(), b.to_string());
}
}
Ok(forth)
});
//
// Tier 1
//
self = self.run(" [ 42 ] :fourty_two")?;
self = self.run(" [ \"prefix:\" swap + lookup ] :lookup_prefix")?;
// this does not work atm...
/*self = self.run("[ (token)
dup 1 str:split_at swap lookup_prefix
[ (token tail handler) rot drop call ]
[ (token tail) drop dup lookup
[ (token id) swap drop handle_class ]
[ (token) handle_literal ]
if ]
if ] :process_token")?;*/
Ok(self)
}
fn run(mut self, input: &str) -> Result<Self> {
for token in tokenize(input) {
println!("token: {:?}", token);
self.push(token.to_string());
//self = self.process_token()?;
self.push(self.try_lookup("process_token")?);
self = self.call()?;
}
Ok(self)
}
fn process_token(mut self) -> Result<Self> {
let token = self.pop()?.into_string()?;
println!("token: {:?}", token);
match token.chars().next() {
None => Ok(self), // ignore empty tokens
Some(prefix) => {
if let Some(word_id) = self.lookup_prefix(prefix) {
let tail = token.splitn(2, prefix).skip(1).next().unwrap();
self.push(Data::String(tail.to_string()));
self.handle_prefix(word_id)
} else if let Some(word_id) = self.lookup(&token) {
self.push(word_id);
self.handle_class()
} else {
self.push(token);
self.handle_literal()
}
}
}
}
fn lookup(&self, name: &str) -> Option<WordId> {
self.words
.iter()
.enumerate()
.rev()
.find(|(_, word)| word.name == name)
.map(|(i, _)| WordId(i))
}
fn try_lookup(&self, name: &str) -> Result<WordId> {
self.lookup(name)
.ok_or_else(|| ForthError::UnknownWord(name.to_string()))
}
fn lookup_prefix(&self, prefix: char) -> Option<WordId> {
let mut name = "prefix:".to_string();
name.push(prefix);
self.lookup(&name)
}
fn get_word(&self, id: WordId) -> &Word {
&self.words[id.0]
}
fn add_word(&mut self, word: Word) -> WordId {
let id = WordId(self.words.len());
self.words.push(word);
id
}
fn add_builtin(
&mut self,
name: &str,
comment: &str,
func: fn(Forth) -> Result<Forth>,
) -> WordId {
self.add_word(Word::new(
name,
comment,
vec![Command::Builtin(name.to_string(), func)],
self.lookup("class:word"), // that's ugly... maybe add_builtin should return a result instead?
))
}
fn add_macro(
&mut self,
name: &str,
comment: &str,
func: fn(Forth) -> Result<Forth>,
) -> WordId {
self.add_word(Word::new(
name,
comment,
vec![Command::Builtin(name.to_string(), func)],
self.lookup("class:macro"), // that's ugly... maybe add_builtin should return a result instead?
))
}
fn handle_literal(mut self) -> Result<Self> {
let token = self.pop()?.into_string()?;
let x = token
.parse()
.map_err(|_| ForthError::UnknownWord(token.to_string()))?;
let cmd = Command::Literal(Data::I32(x));
match self.mode.last_mut().unwrap() {
Mode::Execute => cmd.exec(self),
Mode::Compile(ref mut ops) => {
ops.push(cmd);
Ok(self)
}
}
}
fn handle_prefix(self, id: WordId) -> Result<Self> {
Command::Call(id).exec(self)
}
fn handle_class(mut self) -> Result<Self> {
let id = self.top()?.clone().into_wordid()?;
let word = self.get_word(id);
match word.class {
None => unimplemented!(),//Command::Call(id).exec(self),
Some(cls) => Command::Call(cls).exec(self),
}
}
fn exec(mut self) -> Result<Self> {
println!("{:?} call", self.stack);
self.pop()?.exec(self)
}
fn exec_block(mut self, block: &[Command]) -> Result<Self> {
println!("exec_block({:?})", block);
for op in block {
self = op.exec(self)?;
}
Ok(self)
}
fn call(mut self) -> Result<Self> {
println!("{:?} call", self.stack);
self.pop()?.call(self)
}
fn call_wordid(mut self, id: WordId) -> Result<Self> {
println!("call_wordid({:?})", id);
let word = self.get_word(id);
match word.class {
None => {
let ops = word.ops.clone();
self.exec_block(&ops)
}
Some(cls) => {
self.push(id);
self.call_wordid(cls)
}
}
}
fn compile(mut self) -> Result<Self> {
println!("compile");
let id = self.pop()?.into_wordid()?;
if let Mode::Compile(ref mut ops) = self.mode.last_mut().unwrap() {
if self.words[id.0].ops.len() <= INLINE_THRESHOLD {
ops.extend(self.words[id.0].ops.iter().cloned());
} else {
ops.push(Command::Call(id));
}
Ok(self)
} else {
Err(ForthError::NotInCompileMode)
}
}
fn push<T: IntoData>(&mut self, x: T) {
self.stack.push(x.into_data());
}
fn push2<S: IntoData, T: IntoData>(&mut self, x: S, y: T) {
self.stack.push(x.into_data());
self.stack.push(y.into_data());
}
fn top(&mut self) -> Result<&Data> {
self.stack.last().ok_or(ForthError::EmptyStack)
}
fn pop(&mut self) -> Result<Data> {
self.stack.pop().ok_or(ForthError::EmptyStack)
}
}
fn tokenize(input: &str) -> impl Iterator<Item = &str> {
let mut it = input.char_indices().peekable();
std::iter::repeat(())
.map(move |_| {
while let Some(true) = it.peek().map(|(_, ch)| ch.is_whitespace()) {
it.next();
}
match it.peek() {
None => return None,
Some((_, '"')) => {
let (a, _) = it.next().unwrap();
while let Some(false) = it.peek().map(|(_, ch)| *ch == '"') {
it.next();
}
it.next();
match it.peek() {
Some((b, _)) => return Some(&input[a..*b]),
None => return Some(&input[a..]),
}
}
Some((_, '(')) => {
let (a, _) = it.next().unwrap();
while let Some(false) = it.peek().map(|(_, ch)| *ch == ')') {
it.next();
}
it.next();
match it.peek() {
Some((b, _)) => return Some(&input[a..*b]),
None => return Some(&input[a..]),
}
}
Some((i, _)) => {
let a = *i;
while let Some(false) = it.peek().map(|(_, ch)| ch.is_whitespace()) {
it.next();
}
match it.peek() {
Some((b, _)) => return Some(&input[a..*b]),
None => return Some(&input[a..]),
}
}
}
})
.take_while(Option::is_some)
.map(Option::unwrap)
}
struct WordBuilder {
class: Option<WordId>,
comment: String,
name: String,
ops: Vec<Command>,
}
impl WordBuilder {
fn new() -> Self {
WordBuilder {
class: None,
comment: String::new(),
name: String::new(),
ops: vec![],
}
}
fn build(self) -> Word {
Word {
class: self.class,
comment: self.comment,
name: self.name,
ops: self.ops,
}
}
fn builtin(mut self, func: fn(Forth) -> Result<Forth>) -> Word {
assert_eq!(self.ops.len(), 0);
self.ops = vec![Command::Builtin(self.name.clone(), func)];
self.build()
}
fn with_comment(mut self, c: &str) -> Self {
self.comment = c.to_string();
self
}
fn with_class(mut self, class: WordId) -> Self {
self.class = Some(class);
self
}
fn with_class_opt(mut self, class: Option<WordId>) -> Self {
self.class = class;
self
}
fn with_name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
fn with_name_string(mut self, name: String) -> Self {
self.name = name;
self
}
fn with_ops<I: IntoIterator<Item = Command>>(mut self, ops: I) -> Self {
self.ops.extend(ops);
self
}
}
fn main() -> Result<()> {
let mut forth = Forth::new();
forth = forth.run(".s")?;
forth = forth.run("17 25 + .")?;
forth = forth.run("[ dup * ] :sqr")?;
forth = forth.run("[ sqr sqr ] :sqr")?;
forth = forth.run("[ sqr sqr ] :sqr")?;
forth = forth.run("[ sqr sqr ] :sqr")?;
/*forth = forth.run("[ [ dup * ] ] :sqrmaker")?;
forth = forth.run("sqrmaker :sqr .s")?;
forth = forth.run("[ sqr sqr ] :quad")?;
forth = forth.run("\"sqr\" lookup .s")?;
forth = forth.run("[ [ \"say true\" ] [ \"say no\" ] if . ] :say_what")?;
forth.push(false);
forth = forth.run("say_what")?;
forth.push(true);
forth = forth.run("say_what")?;
forth = forth.run("[ quad quad ] :superquad")?;
forth = forth.run("\"abc def\" 42")?;
forth = forth.run(".s")?;
forth = forth.run("[ quad quad ] :superquad")?;
forth = forth.run("[ superquad superquad ] :megaquad")?;
forth = forth.run("[ 42 ] :the_answer")?;
forth = forth.run("the_answer sqr .")?;
forth = forth.run("10 3 / .")?;
forth = forth.run("[ 42 . ]")?;
forth = forth.run("[ 42 . ] dup call swap call")?;*/
/*forth.run(".s ( comment )")?;
forth.run("42 emit")?;
forth.run(": star 42 emit")?;
forth.run(": margin cr 30 spaces")?;
forth.run(": blip margin star")?;
forth.run(": stars [ star ] repeat")?;
forth.run(": bar margin 5 stars")?;
forth.run(": f bar blip bar blip blip cr")?;
forth.run("f f f")?;
forth.run("cr cr")?;
forth.run(": yards->in ( n -- n ) 36 *")?;
forth.run(": ft->in ( n -- n ) 12 *")?;
forth.run("10 yards->in .")?;
forth.run("2 ft->in .")?;
forth.run(": yards ( n -- n ) yards->in")?;
forth.run(": feet ( n -- n ) ft->in")?;
forth.run(": inches ( -- ) ")?;
forth.run("10 yards 2 feet 9 inches + + .");
forth.run("22 4 /mod . .")?;
forth.run("1 2 3 4 .s")?;
forth.run("rot .s")?;
forth.run(": flip ( a b c -- c b a ) swap rot")?;
forth.run("flip .s")?;
forth.run(": over ( a b -- a b a ) swap dup rot swap")?;
forth.run("over .s")?;
forth.run(": -rot ( a b c -- c a b ) rot rot")?;
forth.run("-rot .s")?;
forth.run(": eq1 ( n -- result ) dup 1 + swap /")?;
forth.run(": eq2 ( x -- result ) dup 7 * 5 + *")?;
forth.run(": eq3 ( a b -- result ) over 9 * swap - *")?;
forth.run("drop drop drop 3 4 .s 2swap .s")?;
forth.run("2over .s")?;
forth.run("swap 2swap swap .s")?;
forth.run("forget eq1")?;*/
for (
id,
Word {
name,
comment,
ops,
class,
..
},
) in forth.words.iter().enumerate()
{
let class = match class {
None => "",
Some(id) => &forth.words[id.0].name,
};
print!("{:>5}: {:<32} {:<16} {:<16} [", id, name, comment, class);
for op in ops {
match op {
Command::Call(id) => print!(" {}", forth.words[id.0].name),
_ => print!(" {:?}", op),
}
}
println!(" ]")
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment