4101 Final
What are the key steps in a compiler/ interpreter?
- Compiler Process 1. Lexical Analysis 2. Syntax Analysis 3. Semantic Analysis 4. Intermediate code generation 5.. Code Optimization 6. Code Gen 7. Linking - Interpreter 1. Reading Code 2. Lexical/ Syntax Analysis 3. Execution
What are the key differences between NFA and DFA?
- NFAs can have more than one transition leaving a state on the same symbol. - DFAs allow one transition per symbol. - Transition function must be a valid function. DFA is a special case of NFA - NFAs may have transitions with empty string labels. May move to new state without consuming character - DFA transition must be labeled with symbol -DFA is a special kind of NFA
parse tree
- a data structure that shows how a statement in a language is derived from - the context-free grammar of the language; it may be annotated with additional information, e.g. for compilation purposes. - Shows how a string is produced by a grammar.
Start State
- state with incoming transition from no other state. - Can have only one start state
What are the rules of reference in Rust?
1. At any given time, you can have either but not both of - One mutable reference- Any number of immutable references 2. References must always be valid- A reference must never out live its referent
What are the differences between Parse Tree and AST?
A parse tree and AST are similar, but not the same. The former describes parsing, the latter is a result of it
Compiler
A program that translates code in a high-level language into machine language, binary code, or a intermediate language
type system
A type system is a series of rules that ascribe types to expressions.
What is the output? use std::collections::HashMap; fn main(){ let mut h = HashMap::new(); h.insert("Alice", "1"); h.insert("Bob", "2"); match h.get(&"Alice") { Some(&id) => println!("Alice:{}",id), _ => println!("Not Found"), } } A. Alice:1 B. Not Found C. Error
A. Alice:1
What is the output? #[derive(Debug)] enum Auth { Enabled(i32), Disabled(i32) } fn main() { let yes = Auth::Enabled(1); let no = Auth::Disabled(0); println!("{:?}", yes); } A. Enabled(1) B. Disabled(0) C. 1 D. 0
A. Enabled(1)
What is the output? #[derive(Debug)] struct Point { x: i32, y: i32, } impl Point { fn m(&mut self){ self.x += 1 self.y += 1 }} fn main() {let mut p = Point{ x: 0, y: 0 }; p.m(); println!("{:?}",p); } A. Point{x:1, y:1} B: Point{x:0, y:} C: Point{0,0} D. Point {1,1 }
A. Point{x:1, y:1}
When is the type of a variable determined in a statically typed language? A. When the program is compiled B. At run-time, when the variable is used C. At run-time, when that variable is first assigned to D. At run-time, when the variable is last assigned to
A. When the program is compiled
What is the output? let mut s1 = String::from("Hello"); let s2 = " World"; s1.push_str(s2);print!("{}",s2); A. World B. Hello World C. Error because s2 transferred the ownership
A. World push_str() function does not take the ownership of the parameter
What does this evaluate to? let mut x = 1; for i in 1..6 { let x = x + 1; } x A.1 B. 6 C.0 D. error
A.1
What is the output? let s = String::from("Rust is fun!"); let h = &s[0..4];println!("{}",h); A. RustB. isC. fun!D. Type Error
A.Rust
Which string is NOT accepted by this NFA? A. ab B. abaa C. abab D. abaab
B. abaa
Consider the grammar S → bS | T T → aT | U U → cU | ε • Which of the following regular expressions accepts the same language as this grammar? A. (a|b|c)* B. b*a*c* C. (b|ba|bac)* D. bac*
B. b*a*c*
What is printed? fn foo(s: &mut String) -> usize{ s.push_str("Bob"); } s.len() fn main() {let mut s1 = String::from("Alice"); println!("{}",foo(&mut s1)) } A.0 B. 8 C. Error D.5
B.8
Which string is NOT in L3? -L1 ={a,ab,c,d,ε} where Σ={a,b,c,d} -L2 ={d} -L3 =L1 ∪L2 A. a B. ad C. ε D. d
B.ad
Consider the grammar S → bS | T T → aT | U U → cU | ε Which of the following strings is generated by this grammar? A. aba B. ccc C. bab D. ca
B.ccc
Which string is not in L3 • L1 ={a,ab,c,d,ε} • L2 = {d}• L3 = L1(L2*) A. a B. abd C. adad D. abdd
C. adad
Output of following code enum Number { Zero, One, Two, } use Number::Zero; let t = Number::One; match t { Zero => println!("0"),} Number::One => println!("1"), A.0 B.1 C.Compile Error
C.Compile Error Pattern `Two` not covered
What does this evaluate to? A. "Hello!" B. "Hello! World!" C. Error D. "Hello!World!" { let mut s1 = String::from("Hello!"); {let s2 = &s1; s2.push_str("World!"); } println!("{}", s2) }
C.Error : s2 is not mut
Static vs. Dynamic Type Checking Claim 1
Claim 1: Dynamic is more convenient • Dynamic typing lets you build a heterogeneous list or return a "number or a string" without workarounds Claim 1: Static is more convenient • Can assume data has the expected type without cluttering code with dynamic checks or having errors far from the logical mistake
Static vs. Dynamic Type Checking Claim 2
Claim 2: Static catches bugs earlier • Static typing catches many simple bugs as soon as "compiled". Claim 2: Static catches only easy bugs • But static often catches only "easy" bugs, so you still have to test your functions, which should find the "easy" bugs too
Static vs. Dynamic Type Checking Claim 3
Claim 3: Static typing is faster. • Language implementation: Claim 3: Dynamic typing is not too much slower
Static vs. Dynamic Type Checking Claim 4
Claim 4: Code reuse easier with dynamic • Without a restrictive type system, more code can just be reused with data of different types Claim 4: Code reuse easier with static • The type system serves as "checked documentation," making the "contract" with others' code easier to understand and use correctly
When is the type of a variable determined in a dynamically typed language? A. When the program is compiled B. At run-time, when the variable is used C. At run-time, when that variable is first assigned to D. At run-time, when the variable is last assigned to
D. At run-time, when the variable is last assigned to
What does this evaluate to? { let x = 6; let y = "hi";if x == 5 { y} else { 5 }; 7 } A.6 B. 7 C.5 D. Error
D. Error - if and else have incompatible types
What is printed? { let mut s = &String::from("dog"); {let y = String::from("hi"); } s = &y; println!("s: {}",s); } A. dog B. hi C. Error - y is immutable D. Error - y dropped while still borrowed
D. Error - y dropped while still borrowed
What is the output? let s1 = String::from("CSC"); let s3; //deferred init{ let s2 = String::from("4101"); s3 = s1+&s2; } print!("{}",s3); print!("{}",s1); A. CSC4101 B. CSC C. CSC4101CSC D. Error
D. Error s1 lost ownership
What does this evaluate to? { let x = 6; let y = 4; y = x; x == y } A.6 B. true C. false D. error
D.error - y is immutable
What are the benefits of static type system?
Error Detection at Compile Time: Performance Optimization: Improved Code Readability and Maintainability: Better Tooling Support: Documentation and Design Clarity: . Easier Refactoring: Safer Code Evolution: Early Feedback and Rapid Development:
All NFAs are DFAs, but not all DFAs are NFAs (T/F)
False
Ocaml has dynamic type system since it does not require type annotations(T/F)
False
Select (TRUE/FALSE) if the following finite automaton will accept "accabc", where ∑ = {a,b,c}
False
You can have two mutable references to the same object in Rust(T/F)
False
LetL1 ={a,b},L2 ={1,2,3}. What is L1L2 ? WhatisL1 ∪L2 ? WhatisL1*?
L1L2: { a1, a2, a3, b1, b2, b3 } L1 ∪L2: { a, b, 1, 2, 3 } L1 *: { ε, a, b, aa, bb, ab, ba, aaa, aab, bba, bbb, aba, abb, baa, bab, ... }
Non-Deterministic Automaton
May have many sequences of steps for each string. Accepts if path ends in final state at end of string. More compact than DFA. More Expensive to test whether a string matches
Point out at least two errors? fn add_elems(arr : &[i32]) -> i32{ let sum = 0; for I in arr.iter() { sum += arr[I];} sum; }
Mutable Variable Issue: The variable sum is declared as immutable, but it is intended to be used for accumulation. In Rust, variables are immutable by default. To modify sum within the loop, it should be declared mutable using the mut keyword. Incorrect Loop Variable Usage: In the loop, for I in arr.iter(), I is an iterator over references to the elements of arr, not an index. Therefore, using arr[I] is incorrect. Instead, you should directly use I, which is a reference to each element of the array.
Final State
State with double circle. - Can have 0 or more final states - Any state including the start state can be final
What are the alternatives to OCaml records and variants in Rust?
Structs and Enums
CFGs subsume DFA, NFA, and RE(T/F)
True
Declarations are immutable by default in Rust(T/F)
True
Dynamic analysis requires execution(T/F)
True
Ocaml and Ruby use garbage collection(T/F)
True
deterministic finite automaton
a finite automaton that has at most one transition from a state for each input symbol and no empty transitions. Abbreviated DFA. Exactly one sequence of steps for each string. Easy to implement acceptance check
context-free grammar
a grammar in which the left-hand side of each production consists of a single nonterminal symbol. A way of describing set of strings (= languages) - Write L(G) the language of strings defined by grammar - Grammar is the same as regex (0|1)*
sound analysis
cannot give false negatives
Complete analysis
cannot give false-positives
Let two languages, L1 = {a,b,c}, L2 = {1,2,3}, What is L1L2 (concatenation operation)? a. {a1,a2,a3,b1,b2,b3} b. {a, b,1,2,3} c. {a,b,c,1,2,3} d. {a1,a2,a3,b1,b2,b3, c1,2,3}
d. {a1,a2,a3,b1,b2,b3, c1,2,3}
Interpreter
directly execute instructions written in a programming language or scripting language without previously converting them into object or machine code.
Abstract Syntax Tree
is a data structure that represents a parsed input - used by the compiler or interpreter
Finite Automata (FA)
is a machine for recognizing a language. Elements: State S(start, final) Alphabet, Transition Edges.
Can the following compile, if not what is the error, otherwise write the output. fn main(){ let a = 3; let b = a; let &mut c = b; *b = 5; println! (" ", a + c) ; }
let &mut c = b; is an attempt to create a mutable reference to b, but the syntax is incorrect. Moreover, b itself is not mutable. *b = 5; is trying to dereference b and assign a new value to it, which is not valid because b is not a pointer or a reference; it's an integer. The println! macro is not being used correctly. The string literal should contain placeholders for the variables' values, and there should not be a space before the macro name
What are the key advantages of dynamic type systems?
lexibility and Ease of Use: Less Boilerplate Code: Easier Code Reuse: Dynamic Behaviors: Ease of Interactive Development: Late Binding: Duck Typing: Rapid Prototyping and Iterative Development:
Rust
• Key properties: Type safety, and no data races, despite use of concurrency and manual memory management
Soundness and Completeness
• Type safety is a soundness property- • Static type systems are rarely complete - Dynamic type systems are often complete